@@ -39,6 +39,13 @@ public class MeetingController { | |||
return meetingManage.meetingCreateAndInviteExpert(req); | |||
} | |||
@PostMapping("/continueInvite") | |||
@ApiOperation(value = "续抽专家") | |||
@WebLog(value = "续抽专家") | |||
public void continueInvite(@Valid @RequestBody MeetingIdReq req) { | |||
meetingManage.continueInvite(req.getMeetingId()); | |||
} | |||
@PostMapping("/expertInviteByCreate") | |||
@ApiOperation(value = "新建会议-专家抽取", hidden = true) | |||
@WebLog(value = "新建会议-专家抽取") | |||
@@ -94,13 +101,6 @@ public class MeetingController { | |||
return meetingManage.inviteRuleDetail(meetingId); | |||
} | |||
@ApiOperation("专家替换") | |||
@PostMapping("/expertReplace") | |||
@WebLog(value = "专家替换") | |||
public void expertReplace(@RequestBody ExpertRemoveReq po) { | |||
meetingManage.expertReplace(po); | |||
} | |||
@ApiOperation("停止抽取") | |||
@GetMapping("/stopInvite/{meetingId}") | |||
@WebLog(value = "停止抽取") | |||
@@ -55,11 +55,6 @@ public class MeetingExpert implements Serializable { | |||
@ApiModelProperty("是否是专家组长") | |||
private Boolean isHeadman; | |||
@ApiModelProperty("前一个状态") | |||
private Integer preStatus; | |||
private Long preId; | |||
@ApiModelProperty("邀请类型") | |||
private Integer inviteType; | |||
@@ -5,7 +5,6 @@ import io.swagger.annotations.ApiModelProperty; | |||
import lombok.Data; | |||
import javax.validation.constraints.NotEmpty; | |||
import javax.validation.constraints.NotNull; | |||
import java.util.List; | |||
/** | |||
@@ -18,7 +17,7 @@ import java.util.List; | |||
*/ | |||
@Data | |||
@ApiModel("回避信息") | |||
public class AvoidInfoDTO { | |||
public class AvoidRuleDTO { | |||
@ApiModelProperty("回避单位") | |||
@NotEmpty(message = "回避单位不能为空", groups = {AbstractInviteRule.RuleSave.class}) |
@@ -0,0 +1,27 @@ | |||
package com.ningdatech.pmapi.meeting.entity.dto; | |||
import lombok.Data; | |||
/** | |||
* <p> | |||
* MeetingInviteDTO | |||
* </p> | |||
* | |||
* @author WendyYang | |||
* @since 19:21 2023/3/7 | |||
*/ | |||
@Data | |||
public final class MeetingInviteCacheDTO { | |||
private Long meetingId; | |||
private Boolean invitedRefused; | |||
public static MeetingInviteCacheDTO of(Long meetingId, Boolean invitedRefused) { | |||
MeetingInviteCacheDTO bo = new MeetingInviteCacheDTO(); | |||
bo.setMeetingId(meetingId); | |||
bo.setInvitedRefused(invitedRefused); | |||
return bo; | |||
} | |||
} |
@@ -1,7 +1,7 @@ | |||
package com.ningdatech.pmapi.meeting.entity.req; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AppointInviteRuleDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidInfoDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidRuleDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.RandomInviteRuleDTO; | |||
import io.swagger.annotations.ApiModel; | |||
import io.swagger.annotations.ApiModelProperty; | |||
@@ -33,7 +33,7 @@ public class ExpertInviteReq { | |||
@Valid | |||
@NotNull(message = "回避信息不能为空") | |||
@ApiModelProperty("回避信息") | |||
private AvoidInfoDTO avoidRule; | |||
private AvoidRuleDTO avoidRule; | |||
@Valid | |||
@ApiModelProperty("随机抽取规则") | |||
@@ -0,0 +1,20 @@ | |||
package com.ningdatech.pmapi.meeting.entity.req; | |||
import io.swagger.annotations.ApiModelProperty; | |||
import lombok.Data; | |||
/** | |||
* <p> | |||
* MeetingIdReq | |||
* </p> | |||
* | |||
* @author WendyYang | |||
* @since 17:10 2023/3/7 | |||
*/ | |||
@Data | |||
public class MeetingIdReq { | |||
@ApiModelProperty("会议ID") | |||
private Long meetingId; | |||
} |
@@ -11,7 +11,7 @@ import com.ningdatech.pmapi.meeting.entity.domain.Meeting; | |||
import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AbstractInviteRule; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AppointInviteRuleDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidInfoDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidRuleDTO; | |||
import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum; | |||
import com.ningdatech.pmapi.meeting.service.IMeetingExpertService; | |||
import com.ningdatech.pmapi.meeting.service.IMeetingService; | |||
@@ -69,7 +69,7 @@ public class ExpertInviteHelper { | |||
return new HashSet<>(listInvitedExpertByTime(start, end)); | |||
} | |||
public Set<Long> getAvoidExpert(List<Long> appoints, AvoidInfoDTO avoid, LocalDateTime start, LocalDateTime end) { | |||
public Set<Long> getAvoidExpert(List<Long> appoints, AvoidRuleDTO avoid, LocalDateTime start, LocalDateTime end) { | |||
Set<Long> expertIds = new HashSet<>(); | |||
Optional.ofNullable(appoints).ifPresent(expertIds::addAll); | |||
Optional.ofNullable(avoid) | |||
@@ -1,5 +1,6 @@ | |||
package com.ningdatech.pmapi.meeting.helper; | |||
import cn.hutool.core.collection.CollUtil; | |||
import cn.hutool.core.lang.Assert; | |||
import cn.hutool.core.util.StrUtil; | |||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |||
@@ -14,9 +15,10 @@ import com.ningdatech.pmapi.expert.service.IExpertUserFullInfoService; | |||
import com.ningdatech.pmapi.meeting.entity.domain.ExpertInviteAvoidRule; | |||
import com.ningdatech.pmapi.meeting.entity.domain.Meeting; | |||
import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidInfoDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidRuleDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.MeetingAndAttendStatusDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.MeetingBasicDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.RandomInviteRuleDTO; | |||
import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum; | |||
import com.ningdatech.pmapi.meeting.entity.req.MeetingListReq; | |||
import com.ningdatech.pmapi.meeting.entity.vo.ExpertBasicInfoVO; | |||
@@ -30,7 +32,10 @@ import lombok.AllArgsConstructor; | |||
import org.apache.commons.collections4.CollectionUtils; | |||
import org.springframework.stereotype.Component; | |||
import java.util.*; | |||
import java.util.Collection; | |||
import java.util.Comparator; | |||
import java.util.List; | |||
import java.util.Map; | |||
/** | |||
* <p> | |||
@@ -60,6 +65,36 @@ public class MeetingManageHelper { | |||
} | |||
/** | |||
* 校验会议是否还可继续抽取 | |||
*/ | |||
public boolean checkCouldBeInvitedContinue(Long meetingId) { | |||
Map<Long, RandomInviteRuleDTO> ruleMap = inviteRuleService.randomRuleByMeetingId(meetingId); | |||
List<MeetingExpert> experts = meetingExpertService.listExpertLastByMeetingId(meetingId); | |||
if (experts.isEmpty()) { | |||
return Boolean.TRUE; | |||
} | |||
Map<Long, List<MeetingExpert>> expertMap = CollUtils.group(experts, MeetingExpert::getRuleId); | |||
for (Map.Entry<Long, RandomInviteRuleDTO> entry : ruleMap.entrySet()) { | |||
List<MeetingExpert> tmpExperts = expertMap.get(entry.getKey()); | |||
if (CollUtil.isEmpty(tmpExperts)) { | |||
return Boolean.TRUE; | |||
} | |||
int count = 0; | |||
for (MeetingExpert w : tmpExperts) { | |||
boolean status = ExpertAttendStatusEnum.AGREED.eq(w.getStatus()) || | |||
ExpertAttendStatusEnum.NOTICING.eq(w.getStatus()); | |||
if (status) { | |||
count++; | |||
} | |||
} | |||
if (count < entry.getValue().getCount()) { | |||
return Boolean.TRUE; | |||
} | |||
} | |||
return Boolean.FALSE; | |||
} | |||
/** | |||
* 获取专家出席会议的状态 | |||
* | |||
* @param info 会议状态及评价信息 | |||
@@ -104,7 +139,7 @@ public class MeetingManageHelper { | |||
query.and(q1 -> q1.exists("select 1 from nd_project np inner join meeting_inner_project mip on mip.project_id = np.id" + | |||
" where mip.meeting_id = meeting.id and np.project_name like {0}", projectName) | |||
.or(q2 -> q2.exists("select 1 from meeting_outer_project mop where mop.meeting_id = meeting.id" + | |||
"and mop.project_name like {0}", projectName))); | |||
" and mop.project_name like {0}", projectName))); | |||
} | |||
} | |||
@@ -140,9 +175,9 @@ public class MeetingManageHelper { | |||
return null; | |||
} | |||
public AvoidInfoDTO getAvoidInfoDto(Long meetingId) { | |||
public AvoidRuleDTO getAvoidInfoDto(Long meetingId) { | |||
ExpertInviteAvoidRule avoidRule = inviteAvoidRuleService.getByMeetingId(meetingId); | |||
AvoidInfoDTO result = new AvoidInfoDTO(); | |||
AvoidRuleDTO result = new AvoidRuleDTO(); | |||
result.setAvoidOrgIdList(StrUtil.split(avoidRule.getAvoidOrgIds(), ",")); | |||
result.setExpertIds(BizUtils.splitToLong(avoidRule.getAvoidExpertIds())); | |||
result.setAvoidUnitIdList(StrUtil.split(avoidRule.getAvoidUnitIds(), ",")); | |||
@@ -165,7 +200,7 @@ public class MeetingManageHelper { | |||
**/ | |||
public List<ExpertUserFullInfo> appointExpertCheck(Long meetingId, List<Long> expertIds) { | |||
List<ExpertUserFullInfo> experts = expertUserFullInfoService.listByUserId(expertIds); | |||
AvoidInfoDTO avoidRule = getAvoidInfoDto(meetingId); | |||
AvoidRuleDTO avoidRule = getAvoidInfoDto(meetingId); | |||
experts.forEach(expert -> { | |||
if (avoidRule.getAvoidUnitIdList().contains(expert.getCompany())) { | |||
throw BizException.wrap("请移除已回避单位的专家"); | |||
@@ -193,16 +228,6 @@ public class MeetingManageHelper { | |||
throw BizException.wrap("专家%s已拒绝参加", expertName); | |||
case REMOVED: | |||
throw BizException.wrap("专家%s已被移除", expertName); | |||
case REPLACED: | |||
switch (ExpertAttendStatusEnum.getByCode(w.getPreStatus())) { | |||
case REFUSED: | |||
throw BizException.wrap("专家%s已拒绝参加", expertName); | |||
case REMOVED: | |||
throw BizException.wrap("专家%s已被移除", expertName); | |||
default: | |||
break; | |||
} | |||
break; | |||
case AGREED: | |||
throw BizException.wrap("专家%s已同意参加", expertName); | |||
case NOTICING: | |||
@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |||
import com.baomidou.mybatisplus.core.toolkit.Wrappers; | |||
import com.ningdatech.basic.util.CollUtils; | |||
import com.ningdatech.pmapi.common.util.BizUtils; | |||
import com.ningdatech.pmapi.common.util.StrUtils; | |||
import com.ningdatech.pmapi.expert.constant.ExpertAccountStatusEnum; | |||
import com.ningdatech.pmapi.expert.entity.ExpertAvoidCompany; | |||
import com.ningdatech.pmapi.expert.entity.ExpertUserFullInfo; | |||
@@ -15,7 +16,7 @@ import com.ningdatech.pmapi.meeting.builder.ExpertInviteBuilder; | |||
import com.ningdatech.pmapi.meeting.entity.domain.ExpertInviteRule; | |||
import com.ningdatech.pmapi.meeting.entity.domain.Meeting; | |||
import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidInfoDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidRuleDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.ExpertChooseDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.ExpertDictChooseDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.RandomInviteRuleDTO; | |||
@@ -135,7 +136,7 @@ public class ExpertInviteManage { | |||
return expertAvoidCompanyList.stream().map(ExpertAvoidCompany::getUserId).distinct().collect(Collectors.toList()); | |||
} | |||
private List<Long> mergeExpertIdsByCondition(RandomInviteRuleDTO rule, AvoidInfoDTO avoidInfo) { | |||
private List<Long> mergeExpertIdsByCondition(RandomInviteRuleDTO rule, AvoidRuleDTO avoidInfo) { | |||
// 处理履职意向地 | |||
List<Long> expertIdsByIntentionRegion = expertIdsByRegion(rule); | |||
if (COLL_EMPTY.evaluate(expertIdsByIntentionRegion)) { | |||
@@ -246,34 +247,31 @@ public class ExpertInviteManage { | |||
/** | |||
* 专家抽取(随机) | |||
* | |||
* @param avoidInfo 回避信息 | |||
* @param avoidRule 回避信息 | |||
* @param randomRule 抽取规则 | |||
* @param appointExpertIds 指定抽取专家ID | |||
* @return 满足抽取条件的专家 | |||
* @author WendyYang | |||
**/ | |||
public ExpertChooseDTO expertInviteByRandomRule(AvoidInfoDTO avoidInfo, | |||
public ExpertChooseDTO expertInviteByRandomRule(AvoidRuleDTO avoidRule, | |||
RandomInviteRuleDTO randomRule, | |||
List<Long> appointExpertIds, | |||
LocalDateTime start, | |||
LocalDateTime end) { | |||
ExpertChooseDTO result = new ExpertChooseDTO(new ArrayList<>(), 0); | |||
List<Long> expertIdsIn = mergeExpertIdsByCondition(randomRule, avoidInfo); | |||
List<Long> expertIdsIn = mergeExpertIdsByCondition(randomRule, avoidRule); | |||
if (expertIdsIn == null) { | |||
return result; | |||
} | |||
boolean avoid = avoidInfo != null; | |||
boolean avoidExpert = avoid && CollUtil.isNotEmpty(avoidInfo.getExpertIds()); | |||
boolean avoidCompany = avoid && CollUtil.isNotEmpty(avoidInfo.getAvoidUnitIdList()); | |||
boolean avoidExpert = CollUtil.isNotEmpty(avoidRule.getExpertIds()); | |||
boolean avoidCompany = CollUtil.isNotEmpty(avoidRule.getAvoidUnitIdList()); | |||
Set<String> tmpAvoidCompany = new HashSet<>(); | |||
if (avoidCompany) { | |||
List<String> companyIds = avoidInfo.getAvoidUnitIdList(); | |||
List<String> companyIds = avoidRule.getAvoidUnitIdList(); | |||
for (String companyId : companyIds) { | |||
if (companyId.contains(",")) { | |||
String[] splitCompanyIds = companyId.split(","); | |||
for (String splitCompanyId : splitCompanyIds) { | |||
tmpAvoidCompany.add(splitCompanyId); | |||
} | |||
Collections.addAll(tmpAvoidCompany, splitCompanyIds); | |||
} else { | |||
tmpAvoidCompany.add(companyId); | |||
} | |||
@@ -284,14 +282,13 @@ public class ExpertInviteManage { | |||
query.notIn(!tmpAvoidCompany.isEmpty(), ExpertUserFullInfo::getCompany, tmpAvoidCompany); | |||
if (avoidCompany) { | |||
query.notExists("select 1 from expert_avoid_company eac where eac.user_id = expert_user_full_info.user_id" + | |||
" and company_name in ({0})", CollUtils.joinByComma(avoidInfo.getAvoidUnitIdList())); | |||
" and company_name in ({0})", CollUtils.joinByComma(avoidRule.getAvoidUnitIdList())); | |||
} | |||
// 处理专家层级 | |||
addRegionLimit(query, randomRule); | |||
if (!expertIdsIn.isEmpty()) { | |||
if (avoidExpert) { | |||
expertIdsIn.removeIf(w -> avoidInfo.getExpertIds().contains(w)); | |||
expertIdsIn.removeIf(w -> avoidRule.getExpertIds().contains(w)); | |||
if (expertIdsIn.isEmpty()) { | |||
// 字典、标签、履职意向地筛选出的专家ID移除需要回避的专家ID | |||
// 如果为空则说明没有符合条件的 | |||
@@ -312,7 +309,7 @@ public class ExpertInviteManage { | |||
} | |||
query.in(ExpertUserFullInfo::getUserId, expertIdsIn); | |||
} else if (avoidExpert || CollUtil.isNotEmpty(appointExpertIds)) { | |||
Set<Long> tempExperts = expertInviteHelper.getAvoidExpert(appointExpertIds, avoidInfo, start, end); | |||
Set<Long> tempExperts = expertInviteHelper.getAvoidExpert(appointExpertIds, avoidRule, start, end); | |||
query.notIn(ExpertUserFullInfo::getUserId, tempExperts); | |||
} else { | |||
Set<Long> notInUserIds = expertInviteHelper.listExpertLeaveOrInvited(start, end); | |||
@@ -320,15 +317,15 @@ public class ExpertInviteManage { | |||
query.notIn(ExpertUserFullInfo::getUserId, notInUserIds); | |||
} | |||
} | |||
List<ExpertUserFullInfo> userFullInfos = expertUserFullInfoService.list(query); | |||
if (userFullInfos.isEmpty()) { | |||
List<ExpertUserFullInfo> userInfoList = expertUserFullInfoService.list(query); | |||
if (userInfoList.isEmpty()) { | |||
return result; | |||
} | |||
result.setTotal(userFullInfos.size()); | |||
Map<String, List<ExpertUserFullInfo>> userGroupByUnit = CollUtils.group(userInfoList, ExpertUserFullInfo::getCompany); | |||
result.setTotal(userInfoList.size()); | |||
// count为空表示数量校验 | |||
if (randomRule.getCount() == null || result.getTotal() >= randomRule.getCount()) { | |||
List<ExpertUserFullInfo> userFullInfoList = inviteWithoutCompany(userFullInfos, randomRule.getCount()); | |||
result.setExperts(userFullInfoList); | |||
result.setExperts(inviteGroupByCompany(userGroupByUnit, randomRule.getCount())); | |||
} | |||
return result; | |||
} | |||
@@ -336,39 +333,41 @@ public class ExpertInviteManage { | |||
/** | |||
* 专家替换、补充 | |||
* | |||
* @param avoidInfo 回避信息 | |||
* @param avoidRule 回避信息 | |||
* @param randomRule 随机抽取 | |||
* @param meetingExperts 已抽取人员 | |||
* @param invitedExperts 已抽取人员 | |||
* @param count 抽取数量 | |||
* @return com.ningdatech.emapi.meeting.entity.dto.ExpertChooseDto | |||
* @param start 会议开始时间 | |||
* @param end 会议结束时间 | |||
* @return {@link ExpertChooseDTO} | |||
* @author WendyYang | |||
**/ | |||
public ExpertChooseDTO expertReplaceByRandomRule(AvoidInfoDTO avoidInfo, | |||
public ExpertChooseDTO expertReplaceByRandomRule(AvoidRuleDTO avoidRule, | |||
RandomInviteRuleDTO randomRule, | |||
Collection<MeetingExpert> meetingExperts, | |||
Collection<MeetingExpert> invitedExperts, | |||
Integer count, | |||
LocalDateTime start, | |||
LocalDateTime end, | |||
Long replacedExpertId) { | |||
boolean invitedRefused) { | |||
ExpertChooseDTO result = new ExpertChooseDTO(new ArrayList<>(), 0); | |||
// 合并标签、字典 | |||
List<Long> expertIdsIn = mergeExpertIdsByCondition(randomRule, avoidInfo); | |||
List<Long> expertIdsIn = mergeExpertIdsByCondition(randomRule, avoidRule); | |||
if (expertIdsIn == null) { | |||
return result; | |||
} | |||
LambdaQueryWrapper<ExpertUserFullInfo> query = buildBaseExpertQuery(); | |||
query.notIn(ExpertUserFullInfo::getCompany, avoidInfo.getAvoidUnitIdList()); | |||
query.notIn(ExpertUserFullInfo::getCompany, avoidRule.getAvoidUnitIdList()); | |||
query.notExists("select 1 from expert_avoid_company eac where eac.user_id = expert_user_full_info.user_id" + | |||
" and company_name in ({0})", CollUtils.joinByComma(avoidInfo.getAvoidOrgIdList())); | |||
" and company_name in ({0})", CollUtils.joinByComma(avoidRule.getAvoidOrgIdList())); | |||
// 处理专家层级 | |||
if (ObjectUtils.allNotNull(randomRule.getExpertRegionCode(), randomRule.getExpertRegionLevel())) { | |||
if (StrUtils.isNotBlank(randomRule.getExpertRegionCode())) { | |||
query.eq(ExpertUserFullInfo::getRegionCode, randomRule.getExpertRegionCode()); | |||
query.eq(ExpertUserFullInfo::getRegionLevel, randomRule.getExpertRegionLevel()); | |||
} | |||
if (expertIdsIn.size() > 0) { | |||
if (CollectionUtils.isNotEmpty(avoidInfo.getExpertIds())) { | |||
expertIdsIn.removeIf(w -> avoidInfo.getExpertIds().contains(w)); | |||
if (CollectionUtils.isNotEmpty(avoidRule.getExpertIds())) { | |||
expertIdsIn.removeIf(w -> avoidRule.getExpertIds().contains(w)); | |||
if (expertIdsIn.isEmpty()) { | |||
// 字典、标签、履职意向地筛选出的专家ID移除需要回避的专家ID | |||
// 如果为空则说明没有符合条件的 | |||
@@ -381,8 +380,8 @@ public class ExpertInviteManage { | |||
return result; | |||
} | |||
query.in(ExpertUserFullInfo::getUserId, expertIdsIn); | |||
} else if (CollectionUtils.isNotEmpty(avoidInfo.getExpertIds())) { | |||
Set<Long> notInExpertIds = expertInviteHelper.getAvoidExpert(avoidInfo.getExpertIds(), null, start, end); | |||
} else if (CollUtil.isNotEmpty(avoidRule.getExpertIds())) { | |||
Set<Long> notInExpertIds = expertInviteHelper.getAvoidExpert(avoidRule.getExpertIds(), null, start, end); | |||
query.notIn(ExpertUserFullInfo::getUserId, notInExpertIds); | |||
} else { | |||
Set<Long> notInUserIds = expertInviteHelper.listExpertLeaveOrInvited(start, end); | |||
@@ -394,44 +393,31 @@ public class ExpertInviteManage { | |||
if (userFullInfos.size() == 0) { | |||
return result; | |||
} | |||
Comparator<MeetingExpert> sort = Comparator.comparing(MeetingExpert::getUpdateOn).reversed(); | |||
Map<Long, MeetingExpert> tempExpertIdsMap = BizUtils.groupFirstMap(meetingExperts, MeetingExpert::getExpertId, sort); | |||
Map<ExpertAttendStatusEnum, List<MeetingExpert>> expertIdGroupByStatus = tempExpertIdsMap.values().stream() | |||
Map<ExpertAttendStatusEnum, List<MeetingExpert>> expertGroupByStatus = invitedExperts.stream() | |||
.collect(Collectors.groupingBy(w -> ExpertAttendStatusEnum.getByCode(w.getStatus()))); | |||
// 回避同单位其他专家 | |||
List<MeetingExpert> removeExpertByCompany = new ArrayList<>(); | |||
BizUtils.notEmpty(expertIdGroupByStatus.get(ExpertAttendStatusEnum.AGREED), removeExpertByCompany::addAll); | |||
BizUtils.notEmpty(expertIdGroupByStatus.get(ExpertAttendStatusEnum.NOTICING), removeExpertByCompany::addAll); | |||
BizUtils.notEmpty(expertGroupByStatus.get(ExpertAttendStatusEnum.AGREED), removeExpertByCompany::addAll); | |||
BizUtils.notEmpty(expertGroupByStatus.get(ExpertAttendStatusEnum.NOTICING), removeExpertByCompany::addAll); | |||
List<Long> removeExpertIds = new ArrayList<>(); | |||
// 拒绝参加的不可以被再次抽中 | |||
BizUtils.notEmpty(expertIdGroupByStatus.get(ExpertAttendStatusEnum.REFUSED), w -> { | |||
List<Long> tempRefused = CollUtils.fieldList(w, MeetingExpert::getExpertId); | |||
removeExpertIds.addAll(tempRefused); | |||
}); | |||
if (invitedRefused) { | |||
// 拒绝参加的不可以被再次抽中 | |||
BizUtils.notEmpty(expertGroupByStatus.get(ExpertAttendStatusEnum.REFUSED), w -> { | |||
List<Long> tempRefused = CollUtils.fieldList(w, MeetingExpert::getExpertId); | |||
removeExpertIds.addAll(tempRefused); | |||
}); | |||
} | |||
// 被取消的也不可以被再次抽中 | |||
BizUtils.notEmpty(expertIdGroupByStatus.get(ExpertAttendStatusEnum.REMOVED), w -> { | |||
BizUtils.notEmpty(expertGroupByStatus.get(ExpertAttendStatusEnum.REMOVED), w -> { | |||
List<Long> tempCanceled = CollUtils.fieldList(w, MeetingExpert::getExpertId); | |||
removeExpertIds.addAll(tempCanceled); | |||
}); | |||
// 被替换之前是上述两种状态的不可被再次抽中 | |||
BizUtils.notEmpty(expertIdGroupByStatus.get(ExpertAttendStatusEnum.REPLACED), w -> { | |||
for (MeetingExpert me : w) { | |||
BizUtils.notNull(me.getPreStatus(), preStatus -> { | |||
if (ExpertAttendStatusEnum.REFUSED.eq(preStatus) || ExpertAttendStatusEnum.REMOVED.eq(preStatus)) { | |||
removeExpertIds.add(me.getExpertId()); | |||
} | |||
}); | |||
} | |||
}); | |||
// 不为空时表示单个专家替换否则为专家补抽 | |||
if (replacedExpertId != null) { | |||
removeExpertIds.add(replacedExpertId); | |||
} | |||
List<Long> tempExpertIds = CollUtils.fieldList(removeExpertByCompany, MeetingExpert::getExpertId); | |||
// 移除确认参加、通知中的、拒绝参加、已取消 | |||
userFullInfos.removeIf(w -> tempExpertIds.contains(w.getUserId()) || removeExpertIds.contains(w.getUserId())); | |||
result.setTotal(userFullInfos.size()); | |||
result.setExperts(inviteWithoutCompany(userFullInfos, count)); | |||
Map<String, List<ExpertUserFullInfo>> userGroupByUnit = CollUtils.group(userFullInfos, ExpertUserFullInfo::getCompany); | |||
result.setTotal(userGroupByUnit.size()); | |||
result.setExperts(inviteGroupByCompany(userGroupByUnit, count)); | |||
return result; | |||
} | |||
@@ -455,26 +441,26 @@ public class ExpertInviteManage { | |||
/** | |||
* 每个单位只抽取一人 | |||
* | |||
* @param expertGroupByCompany 需要抽取的人 | |||
* @param count 抽取数量 | |||
* @param expertGroupByUnit 需要抽取的人 | |||
* @param count 抽取数量 | |||
* @return java.util.List<com.ningdatech.emapi.expert.entity.domain.ExpertUserFullInfo> | |||
* @author WendyYang | |||
**/ | |||
private List<ExpertUserFullInfo> inviteGroupByCompany(Map<String, List<ExpertUserFullInfo>> expertGroupByCompany, Integer count) { | |||
if (MapUtils.isEmpty(expertGroupByCompany)) { | |||
private List<ExpertUserFullInfo> inviteGroupByCompany(Map<String, List<ExpertUserFullInfo>> expertGroupByUnit, Integer count) { | |||
if (MapUtils.isEmpty(expertGroupByUnit)) { | |||
return Collections.emptyList(); | |||
} | |||
List<List<MeetingExpert>> meetingExperts = selectMeetingExpertByCount(); | |||
if (meetingExperts.isEmpty()) { | |||
return expertGroupByCompany.values().stream() | |||
return expertGroupByUnit.values().stream() | |||
.map(expertUsers -> expertUsers.get(RandomUtils.nextInt(0, expertUsers.size()))) | |||
.limit(count).collect(Collectors.toList()); | |||
} else { | |||
List<ExpertUserFullInfo> result = new ArrayList<>(); | |||
List<String> keySet = new ArrayList<>(expertGroupByCompany.keySet()); | |||
List<String> keySet = new ArrayList<>(expertGroupByUnit.keySet()); | |||
for (int i = 0; i < count; i++) { | |||
String key = keySet.get(RandomUtils.nextInt(0, keySet.size())); | |||
List<ExpertUserFullInfo> expertUserFullInfos = expertGroupByCompany.get(key); | |||
List<ExpertUserFullInfo> expertUserFullInfos = expertGroupByUnit.get(key); | |||
for (List<MeetingExpert> expertList : meetingExperts) { | |||
List<ExpertUserFullInfo> tempList = expertUserFullInfos.stream() | |||
.filter(w -> expertList.stream().noneMatch(expert -> expert.getExpertId().equals(w.getUserId()))) | |||
@@ -547,12 +533,13 @@ public class ExpertInviteManage { | |||
* 专家抽取(会议创建时抽取) | |||
* | |||
* @param randomRules 随机抽取规则 | |||
* @param avoid 回避信息 | |||
* @param avoidRuled 回避信息 | |||
* @param meeting 会议信息 | |||
* @author WendyYang | |||
**/ | |||
public void expertRandomInviteByMeetingCreate(Meeting meeting, | |||
List<RandomInviteRuleDTO> randomRules, | |||
AvoidInfoDTO avoid) { | |||
public void expertInviteByMeetingCreate(Meeting meeting, | |||
List<RandomInviteRuleDTO> randomRules, | |||
AvoidRuleDTO avoidRuled) { | |||
List<MeetingExpert> expertInserts = new ArrayList<>(); | |||
// 处理随机抽取规则 | |||
if (CollectionUtils.isNotEmpty(randomRules)) { | |||
@@ -562,7 +549,7 @@ public class ExpertInviteManage { | |||
LocalDateTime startTime = meeting.getStartTime(); | |||
LocalDateTime endTime = meeting.getEndTime(); | |||
randomRules.forEach(rule -> { | |||
ExpertChooseDTO tempExperts = expertInviteByRandomRule(avoid, rule, choosedExpertIds, startTime, endTime); | |||
ExpertChooseDTO tempExperts = expertInviteByRandomRule(avoidRuled, rule, choosedExpertIds, startTime, endTime); | |||
expertsByRandom.add(tempExperts); | |||
choosedExpertIds.addAll(CollUtils.fieldList(tempExperts.getExperts(), ExpertUserFullInfo::getUserId)); | |||
randoms.add(getExpertInviteRule(rule, meeting.getId())); | |||
@@ -140,6 +140,29 @@ public class MeetingManage { | |||
} | |||
} | |||
public void continueInvite(Long meetingId) { | |||
String key = "CONTINUE_INVITE:" + meetingId; | |||
if (!distributedLock.lock(key, RETRY_TIMES)) { | |||
throw BizException.wrap("已进行续抽,请勿重复点击"); | |||
} | |||
try { | |||
Meeting meeting = meetingService.getById(meetingId); | |||
if (!meeting.getInviteStatus()) { | |||
throw BizException.wrap("该会议正在抽取专家,暂无法续抽"); | |||
} | |||
if (!MeetingStatusEnum.NORMAL.eq(meeting.getStatus())) { | |||
throw BizException.wrap("续抽失败,请刷新后重试"); | |||
} | |||
boolean invitedContinue = meetingManageHelper.checkCouldBeInvitedContinue(meetingId); | |||
if (!invitedContinue) { | |||
throw BizException.wrap("抽取人员数量已满足抽取规则"); | |||
} | |||
} finally { | |||
distributedLock.releaseLock(key); | |||
} | |||
} | |||
@Transactional(rollbackFor = Exception.class) | |||
public void expertInviteByCreate(ExpertInviteReq req) { | |||
String key = INVITED_RULE_CREATE + req.getMeetingId(); | |||
@@ -151,7 +174,7 @@ public class MeetingManage { | |||
if (ExpertInviteTypeEnum.RANDOM.eq(req.getInviteType())) { | |||
List<RandomInviteRuleDTO> randomRules = req.getRandomRules(); | |||
Assert.notEmpty(randomRules, "随机抽取规则不能为空"); | |||
AvoidInfoDTO avoidInfo = req.getAvoidRule(); | |||
AvoidRuleDTO avoidInfo = req.getAvoidRule(); | |||
Assert.notNull(avoidInfo, "回避信息不能为空"); | |||
// 随机抽取的话则需进行抽取数量校验 | |||
ExpertCountOnChangeVO countOnChange = expertCountOnChange(req); | |||
@@ -163,23 +186,21 @@ public class MeetingManage { | |||
Integer inviteCount = randomRules.get(i).getCount(); | |||
Assert.isTrue(checkCount >= inviteCount, "可供抽取的专家数量不足"); | |||
} | |||
expertInviteManage.expertRandomInviteByMeetingCreate(meeting, randomRules, avoidInfo); | |||
expertInviteManage.expertInviteByMeetingCreate(meeting, randomRules, avoidInfo); | |||
expertInviteTask.addInviteExpertTaskByMeetingCreate(meeting.getId(), 5); | |||
LambdaUpdateWrapper<Meeting> update = Wrappers.lambdaUpdate(Meeting.class); | |||
update.set(Meeting::getInviteStatus, false); | |||
update.eq(Meeting::getId, meeting.getId()); | |||
meetingService.update(update); | |||
// 回避规则 | |||
if (avoidInfo != null) { | |||
ExpertInviteAvoidRule avoidRule = new ExpertInviteAvoidRule(); | |||
avoidRule.setMeetingId(meeting.getId()); | |||
// 未传值时设置为0 表示不限制周参与次数 | |||
avoidRule.setWeekInviteCount(ObjectUtil.defaultIfNull(avoidRule.getWeekInviteCount(), 0)); | |||
avoidRule.setAvoidOrgIds(CollUtils.joinByComma(avoidInfo.getAvoidOrgIdList())); | |||
avoidRule.setAvoidUnitIds(CollUtils.joinByComma(avoidInfo.getAvoidUnitIdList())); | |||
avoidRule.setAvoidExpertIds(CollUtils.joinByComma(avoidInfo.getExpertIds())); | |||
inviteAvoidRuleService.save(avoidRule); | |||
} | |||
ExpertInviteAvoidRule avoidRule = new ExpertInviteAvoidRule(); | |||
avoidRule.setMeetingId(meeting.getId()); | |||
// 未传值时设置为0 表示不限制周参与次数 | |||
avoidRule.setWeekInviteCount(ObjectUtil.defaultIfNull(avoidRule.getWeekInviteCount(), 0)); | |||
avoidRule.setAvoidOrgIds(CollUtils.joinByComma(avoidInfo.getAvoidOrgIdList())); | |||
avoidRule.setAvoidUnitIds(CollUtils.joinByComma(avoidInfo.getAvoidUnitIdList())); | |||
avoidRule.setAvoidExpertIds(CollUtils.joinByComma(avoidInfo.getExpertIds())); | |||
inviteAvoidRuleService.save(avoidRule); | |||
} else { | |||
// 指定邀请 | |||
AppointInviteRuleDTO appointRule = req.getAppointRule(); | |||
@@ -449,7 +470,7 @@ public class MeetingManage { | |||
} | |||
result.getRandomRules().add(randomRule); | |||
}); | |||
AvoidInfoDTO avoidInfo = inviteAvoidRuleService.getAvoidInfoDto(meetingId); | |||
AvoidRuleDTO avoidInfo = inviteAvoidRuleService.getAvoidInfoDto(meetingId); | |||
AvoidInfoVO vo = new AvoidInfoVO(); | |||
vo.setAvoidOrgIds(avoidInfo.getAvoidOrgIdList()); | |||
vo.setAvoidUnitIds(avoidInfo.getAvoidUnitIdList()); | |||
@@ -469,75 +490,6 @@ public class MeetingManage { | |||
return result; | |||
} | |||
public void expertRemove(ExpertRemoveReq po) { | |||
LambdaUpdateWrapper<MeetingExpert> update = Wrappers.lambdaUpdate(MeetingExpert.class) | |||
.eq(MeetingExpert::getId, po.getExpertMeetingId()) | |||
.set(MeetingExpert::getStatus, ExpertAttendStatusEnum.REMOVED.getCode()); | |||
meetingExpertService.update(update); | |||
} | |||
@Transactional(rollbackFor = Exception.class) | |||
public void expertReplace(ExpertRemoveReq po) { | |||
MeetingExpert meetingExpert = meetingExpertService.getById(po.getExpertMeetingId()); | |||
ExpertUserFullInfo expertFullInfo; | |||
Long ruleId = 0L; | |||
Meeting meeting = meetingService.getById(po.getMeetingId()); | |||
if (po.getExpertId() != null) { | |||
// 指定邀请替换 | |||
List<ExpertUserFullInfo> userInfos = meetingManageHelper.appointExpertCheck(po.getMeetingId(), Collections.singletonList(po.getExpertId())); | |||
expertFullInfo = userInfos.get(0); | |||
} else { | |||
List<ExpertInviteRule> inviteRules = inviteRuleService.listByMeetingId(po.getMeetingId()); | |||
// 邀请规则 | |||
RandomInviteRuleDTO randomInviteRuleDto = null; | |||
for (ExpertInviteRule rule : inviteRules) { | |||
if (rule.getInviteType().equals(ExpertInviteTypeEnum.RANDOM.getCode()) && | |||
rule.getId().equals(meetingExpert.getRuleId())) { | |||
randomInviteRuleDto = JSON.parseObject(rule.getInviteRule(), RandomInviteRuleDTO.class); | |||
ruleId = rule.getId(); | |||
break; | |||
} | |||
} | |||
// 回避规则 | |||
AvoidInfoDTO avoidInfoDto = meetingManageHelper.getAvoidInfoDto(po.getMeetingId()); | |||
// 添加回避该替换的专家 | |||
List<Long> expertIds = avoidInfoDto.getExpertIds(); | |||
if (CollUtil.isEmpty(expertIds)) { | |||
expertIds = new ArrayList<>(); | |||
} | |||
expertIds.add(meetingExpert.getExpertId()); | |||
avoidInfoDto.setExpertIds(expertIds); | |||
meetingManageHelper.saveAvoidInfo(po.getMeetingId(), avoidInfoDto.getExpertIds()); | |||
List<MeetingExpert> meetingExperts = meetingExpertService.listByMeetingId(po.getMeetingId()); | |||
ExpertChooseDTO expertChooseDto = expertInviteManage.expertReplaceByRandomRule(avoidInfoDto, randomInviteRuleDto, | |||
meetingExperts, 1, meeting.getStartTime(), meeting.getEndTime(), meetingExpert.getExpertId()); | |||
Assert.isTrue(expertChooseDto.getTotal() > 0, "暂无专家可供替换"); | |||
expertFullInfo = expertChooseDto.getExperts().get(0); | |||
expertInviteTask.addInviteExpertTask(po.getMeetingId()); | |||
} | |||
LambdaUpdateWrapper<MeetingExpert> update = Wrappers.lambdaUpdate(MeetingExpert.class) | |||
.eq(MeetingExpert::getId, po.getExpertMeetingId()) | |||
.set(MeetingExpert::getUpdateOn, LocalDateTime.now()) | |||
.set(MeetingExpert::getPreStatus, meetingExpert.getStatus()) | |||
.set(MeetingExpert::getStatus, ExpertAttendStatusEnum.REPLACED.getCode()); | |||
meetingExpertService.update(update); | |||
MeetingExpert me; | |||
if (po.getExpertId() == null) { | |||
me = ExpertInviteBuilder.getExpertByRandom(po.getMeetingId(), expertFullInfo, ruleId); | |||
} else { | |||
me = ExpertInviteBuilder.getExpertByAppoint(po.getMeetingId(), expertFullInfo, ruleId); | |||
} | |||
me.setStatus(ExpertAttendStatusEnum.NOTICING.getCode()); | |||
me.setPreId(po.getExpertMeetingId()); | |||
yxtCallOrSmsHelper.callByMeetingExperts(meeting, Collections.singletonList(me)); | |||
meetingExpertService.save(me); | |||
// 发送专家替换短信 TODO | |||
// String meetingType = dictionaryCache.getByCode(meeting.getType()).getName(); | |||
// SendSmsContext context = YxtSmsContextBuilder.smsToExpertByReplace(meeting, meetingExpert, meetingType); | |||
// yxtCallOrSmsHelper.sendSms(context); | |||
} | |||
public void batchAppointExperts(BatchAppointExpertsReq req) { | |||
Long meetingId = req.getMeetingId(); | |||
String key = "BATCH_APPOINT_EXPERT:" + meetingId; | |||
@@ -2,7 +2,7 @@ package com.ningdatech.pmapi.meeting.service; | |||
import com.baomidou.mybatisplus.extension.service.IService; | |||
import com.ningdatech.pmapi.meeting.entity.domain.ExpertInviteAvoidRule; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidInfoDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidRuleDTO; | |||
/** | |||
* <p> | |||
@@ -23,6 +23,6 @@ public interface IExpertInviteAvoidRuleService extends IService<ExpertInviteAvoi | |||
* @return com.ningdatech.emapi.meeting.entity.dto.AvoidInfoDto | |||
* @author WendyYang | |||
**/ | |||
AvoidInfoDTO getAvoidInfoDto(Long meetingId); | |||
AvoidRuleDTO getAvoidInfoDto(Long meetingId); | |||
} |
@@ -27,7 +27,7 @@ public interface IMeetingExpertService extends IService<MeetingExpert> { | |||
* 查询专家的参与状态 | |||
* | |||
* @param expertId 专家ID | |||
* @param status 状态{@link MeetingStatus.Expert} | |||
* @param status 状态 | |||
* @param meetingIds 会议ID | |||
* @return 会议参加状态统计 | |||
* @author WendyYang | |||
@@ -35,7 +35,7 @@ public interface IMeetingExpertService extends IService<MeetingExpert> { | |||
List<MeetingAndAttendStatusDTO> listByExpertIdAndStatus(Long expertId, Integer status, List<Long> meetingIds); | |||
/** | |||
* 查询每个事物的确认进度 | |||
* 查询每个会议的确认进度 | |||
* | |||
* @param meetingIds 事务ID | |||
* @return 确认进度 | |||
@@ -132,4 +132,15 @@ public interface IMeetingExpertService extends IService<MeetingExpert> { | |||
**/ | |||
List<MeetingExpert> listExpertLastByMeetingIds(Collection<Long> meetingIds); | |||
/** | |||
* 查询会议的所有被抽取人最后一条记录 | |||
* | |||
* @param meetingId 会议ID | |||
* @return 抽取记录 | |||
* @author WendyYang | |||
**/ | |||
default List<MeetingExpert> listExpertLastByMeetingId(Long meetingId) { | |||
return listExpertLastByMeetingIds(Collections.singletonList(meetingId)); | |||
} | |||
} |
@@ -6,7 +6,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; | |||
import com.ningdatech.pmapi.common.util.BizUtils; | |||
import com.ningdatech.pmapi.common.util.StrUtils; | |||
import com.ningdatech.pmapi.meeting.entity.domain.ExpertInviteAvoidRule; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidInfoDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidRuleDTO; | |||
import com.ningdatech.pmapi.meeting.mapper.ExpertInviteAvoidRuleMapper; | |||
import com.ningdatech.pmapi.meeting.service.IExpertInviteAvoidRuleService; | |||
import org.springframework.stereotype.Service; | |||
@@ -30,12 +30,12 @@ public class ExpertInviteAvoidRuleServiceImpl extends ServiceImpl<ExpertInviteAv | |||
} | |||
@Override | |||
public AvoidInfoDTO getAvoidInfoDto(Long meetingId) { | |||
public AvoidRuleDTO getAvoidInfoDto(Long meetingId) { | |||
ExpertInviteAvoidRule avoidRule = getByMeetingId(meetingId); | |||
if (avoidRule == null) { | |||
return null; | |||
} | |||
AvoidInfoDTO avoidInfo = new AvoidInfoDTO(); | |||
AvoidRuleDTO avoidInfo = new AvoidRuleDTO(); | |||
avoidInfo.setAvoidOrgIdList(StrUtils.split(avoidRule.getAvoidOrgIds())); | |||
avoidInfo.setAvoidUnitIdList(StrUtils.split(avoidRule.getAvoidUnitIds())); | |||
avoidInfo.setExpertIds(BizUtils.splitToLong(avoidRule.getAvoidExpertIds())); | |||
@@ -1,23 +1,20 @@ | |||
package com.ningdatech.pmapi.meeting.task; | |||
import cn.hutool.core.collection.CollUtil; | |||
import cn.hutool.core.convert.Convert; | |||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; | |||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper; | |||
import com.baomidou.mybatisplus.core.toolkit.Wrappers; | |||
import com.ningdatech.basic.util.CollUtils; | |||
import com.ningdatech.cache.model.cache.CacheKey; | |||
import com.ningdatech.cache.model.cache.CacheHashKey; | |||
import com.ningdatech.cache.repository.CachePlusOps; | |||
import com.ningdatech.pmapi.common.util.SpringContextHolder; | |||
import com.ningdatech.pmapi.meeting.builder.ExpertInviteBuilder; | |||
import com.ningdatech.pmapi.meeting.entity.domain.ExpertInviteRule; | |||
import com.ningdatech.pmapi.meeting.entity.domain.Meeting; | |||
import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidInfoDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.AvoidRuleDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.ExpertChooseDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.MeetingInviteCacheDTO; | |||
import com.ningdatech.pmapi.meeting.entity.dto.RandomInviteRuleDTO; | |||
import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum; | |||
import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertInviteTypeEnum; | |||
import com.ningdatech.pmapi.meeting.helper.ExpertInviteHelper; | |||
import com.ningdatech.pmapi.meeting.helper.YxtCallOrSmsHelper; | |||
import com.ningdatech.pmapi.meeting.manage.ExpertInviteManage; | |||
@@ -29,6 +26,7 @@ import com.ningdatech.pmapi.user.util.LoginUserUtil; | |||
import lombok.AllArgsConstructor; | |||
import lombok.Data; | |||
import lombok.extern.slf4j.Slf4j; | |||
import org.apache.commons.collections4.MapUtils; | |||
import org.springframework.aop.framework.AopContext; | |||
import org.springframework.beans.factory.annotation.Qualifier; | |||
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; | |||
@@ -42,15 +40,12 @@ import java.time.Duration; | |||
import java.time.Instant; | |||
import java.time.LocalDateTime; | |||
import java.time.ZoneId; | |||
import java.util.Collections; | |||
import java.util.List; | |||
import java.util.Map; | |||
import java.util.Set; | |||
import java.util.concurrent.ConcurrentHashMap; | |||
import java.util.concurrent.ConcurrentMap; | |||
import java.util.concurrent.ScheduledFuture; | |||
import java.util.concurrent.atomic.AtomicInteger; | |||
import java.util.function.Function; | |||
import java.util.stream.Collectors; | |||
@@ -69,9 +64,13 @@ public class ExpertInviteTask { | |||
private final ExpertInviteHelper expertInviteHelper; | |||
private final RandomInviteProperties properties; | |||
private static final String MEETING_ID_INVITE_RANDOM = "MEETING_ID_INVITE_RANDOM"; | |||
private static final CacheKey CACHE_KEY = new CacheKey(MEETING_ID_INVITE_RANDOM); | |||
private static final CacheHashKey CACHE_KEY = new CacheHashKey(); | |||
static { | |||
CACHE_KEY.setKey(MEETING_ID_INVITE_RANDOM); | |||
CACHE_KEY.setExpire(Duration.ofDays(100)); | |||
} | |||
private final CachePlusOps cachePlusOps; | |||
@Qualifier("expertInviteScheduler") | |||
@@ -81,38 +80,38 @@ public class ExpertInviteTask { | |||
private final IExpertInviteRuleService inviteRuleService; | |||
private final IMeetingService meetingService; | |||
private final ExpertInviteManage expertInviteManage; | |||
private final IExpertInviteAvoidRuleService expertInviteAvoidRuleService; | |||
private final IExpertInviteAvoidRuleService inviteAvoidRuleService; | |||
private final YxtCallOrSmsHelper yxtCallOrSmsHelper; | |||
public ExpertInviteTask currProxy() { | |||
return (ExpertInviteTask) AopContext.currentProxy(); | |||
} | |||
/** | |||
* 用来存入线程执行情况, 方便于停止定时任务时使用 | |||
*/ | |||
protected static final ConcurrentMap<Long, ScheduledFuture<?>> INVITE_MAP = new ConcurrentHashMap<>(); | |||
public ExpertInviteTask currProxy() { | |||
return (ExpertInviteTask) AopContext.currentProxy(); | |||
} | |||
@PostConstruct | |||
public void initTask() { | |||
if (!properties.getEnable()) { | |||
log.warn("随机邀请已关闭……"); | |||
return; | |||
} | |||
// initInviteTaskByStart(); | |||
initInviteTaskAfterAppStarted(); | |||
} | |||
/** | |||
* 项目重启之后重新初始化邀请任务 | |||
*/ | |||
private void initInviteTaskByStart() { | |||
Set<Object> meetingIds = cachePlusOps.sMembers(CACHE_KEY); | |||
if (meetingIds == null || meetingIds.isEmpty()) { | |||
private void initInviteTaskAfterAppStarted() { | |||
Map<Long, MeetingInviteCacheDTO> meetingIdMap = cachePlusOps.hGetAll(CACHE_KEY); | |||
if (MapUtils.isEmpty(meetingIdMap)) { | |||
log.info("暂无需要初始化的抽取会议信息"); | |||
return; | |||
} | |||
for (Object meetingId : meetingIds) { | |||
addInviteExpertTask(Convert.toLong(meetingId), true, properties.getInviteDelay()); | |||
for (MeetingInviteCacheDTO cache : meetingIdMap.values()) { | |||
addInviteExpertTask(cache.getMeetingId(), true, properties.getInviteDelay(), cache.getInvitedRefused()); | |||
} | |||
} | |||
@@ -123,25 +122,27 @@ public class ExpertInviteTask { | |||
* @return boolean | |||
* @author WendyYang | |||
**/ | |||
private boolean inviteCheck(Long meetingId) { | |||
List<ExpertInviteRule> expertInviteRules = inviteRuleService.listByMeetingId(meetingId); | |||
Function<ExpertInviteRule, ExpertInviteTypeEnum> groupKey = w -> ExpertInviteTypeEnum.getByCode(w.getInviteType()); | |||
Map<ExpertInviteTypeEnum, List<ExpertInviteRule>> groupByType = CollUtils.group(expertInviteRules, groupKey); | |||
List<ExpertInviteRule> randomRules = groupByType.get(ExpertInviteTypeEnum.RANDOM); | |||
if (CollUtil.isEmpty(randomRules)) { | |||
return false; | |||
private boolean inviteCountCheck(Long meetingId) { | |||
Map<Long, RandomInviteRuleDTO> ruleMap = inviteRuleService.randomRuleByMeetingId(meetingId); | |||
if (ruleMap.isEmpty()) { | |||
return Boolean.FALSE; | |||
} | |||
Map<Long, ExpertInviteRule> ruleMap = CollUtils.listToMap(randomRules, ExpertInviteRule::getId); | |||
LambdaQueryWrapper<MeetingExpert> query = Wrappers.lambdaQuery(MeetingExpert.class) | |||
.select(MeetingExpert::getRuleId, MeetingExpert::getStatus) | |||
.in(MeetingExpert::getRuleId, ruleMap.keySet()) | |||
.in(MeetingExpert::getStatus, ExpertAttendStatusEnum.AGREED.getCode()); | |||
List<MeetingExpert> meetingExperts = meetingExpertService.list(query); | |||
int totalCount = CollUtils.sum(randomRules, ExpertInviteRule::getInviteCount); | |||
boolean needed = totalCount > meetingExperts.size(); | |||
if (!needed) { | |||
cancelByMeetingId(meetingId); | |||
List<MeetingExpert> experts = meetingExpertService.list(query); | |||
if (experts.isEmpty()) { | |||
return Boolean.FALSE; | |||
} | |||
Map<Long, Long> cntMap = CollUtils.groupCount(experts, MeetingExpert::getRuleId); | |||
for (Map.Entry<Long, RandomInviteRuleDTO> entry : ruleMap.entrySet()) { | |||
Long agreeCnt = cntMap.getOrDefault(entry.getKey(), 0L); | |||
if (agreeCnt < entry.getValue().getCount()) { | |||
return Boolean.FALSE; | |||
} | |||
} | |||
return needed; | |||
return Boolean.TRUE; | |||
} | |||
/** | |||
@@ -151,9 +152,8 @@ public class ExpertInviteTask { | |||
* @author WendyYang | |||
**/ | |||
public void addInviteExpertTask(Long meetingId) { | |||
boolean contains = INVITE_MAP.containsKey(meetingId); | |||
if (!contains) { | |||
addInviteExpertTask(meetingId, false, properties.getInviteDelay()); | |||
if (!INVITE_MAP.containsKey(meetingId)) { | |||
addInviteExpertTask(meetingId, false, properties.getInviteDelay(), false); | |||
log.info("重置会议的随机抽取状态:{}", meetingId); | |||
LambdaUpdateWrapper<Meeting> update = Wrappers.lambdaUpdate(Meeting.class); | |||
update.set(Meeting::getInviteStatus, false); | |||
@@ -170,17 +170,19 @@ public class ExpertInviteTask { | |||
* @param meetingId 会议ID | |||
* @param checked 是否前置校验 | |||
* @param delayedMinutes 延迟执行时间 | |||
* @param invitedRefused 是否可以邀请被拒绝的专家 | |||
* @author WendyYang | |||
**/ | |||
public void addInviteExpertTask(Long meetingId, boolean checked, int delayedMinutes) { | |||
if (checked && !inviteCheck(meetingId)) { | |||
public void addInviteExpertTask(Long meetingId, boolean checked, int delayedMinutes, boolean invitedRefused) { | |||
if (checked && !inviteCountCheck(meetingId)) { | |||
// 如果抽取数量满足直接返回 | |||
return; | |||
} | |||
Instant startTime = LocalDateTime.now().plusMinutes(delayedMinutes).atZone(ZoneId.systemDefault()).toInstant(); | |||
ScheduledFuture<?> future = scheduler.scheduleAtFixedRate(() -> { | |||
ExpertInviteTask bean = SpringContextHolder.getBean(ExpertInviteTask.class); | |||
try { | |||
bean.invite(meetingId); | |||
bean.invite(meetingId, invitedRefused); | |||
} catch (Exception e) { | |||
log.error("执行专家邀请任务异常:{}", meetingId, e); | |||
} | |||
@@ -189,53 +191,55 @@ public class ExpertInviteTask { | |||
log.info("添加专家抽取后台任务:{}", meetingId); | |||
} | |||
/** | |||
* 创建会议时添加抽取任务 | |||
* | |||
* @param meetingId 会议ID | |||
* @param delayedMinutes 延迟时间 | |||
* @author WendyYang | |||
**/ | |||
public void addInviteExpertTaskByMeetingCreate(Long meetingId, int delayedMinutes) { | |||
Assert.isTrue(properties.getEnable(), "随机邀请已关闭"); | |||
addInviteExpertTask(meetingId, false, delayedMinutes); | |||
addInviteExpertTask(meetingId, false, delayedMinutes, false); | |||
cachePlusOps.sAdd(CACHE_KEY, meetingId); | |||
} | |||
@Transactional(rollbackFor = Exception.class) | |||
public void invite(Long meetingId) { | |||
public void invite(Long meetingId, Boolean invitedRefused) { | |||
log.info("开始进行专家后台抽取:{}", meetingId); | |||
Meeting meeting = meetingService.getById(meetingId); | |||
if (meeting.getStartTime().isBefore(LocalDateTime.now())) { | |||
// 会议开始结束随机抽取 | |||
cancelByMeetingId(meetingId); | |||
log.info("会议已开始停止抽取:{}", meeting); | |||
cancelByMeetingId(meetingId); | |||
return; | |||
} | |||
// 随机邀请规则 | |||
Map<Long, RandomInviteRuleDTO> ruleMap = inviteRuleService.randomRuleByMeetingId(meetingId); | |||
// 回避规则 | |||
AvoidInfoDTO avoidInfoDto = expertInviteAvoidRuleService.getAvoidInfoDto(meetingId); | |||
AvoidRuleDTO avoidRule = inviteAvoidRuleService.getAvoidInfoDto(meetingId); | |||
// 还需要抽取的规则数量 | |||
AtomicInteger notIgnoreCnt = new AtomicInteger(ruleMap.size()); | |||
AtomicInteger notSupportCnt = new AtomicInteger(0); | |||
ruleMap.forEach((ruleId, value) -> { | |||
List<Long> singletonList = Collections.singletonList(meetingId); | |||
List<MeetingExpert> tempExperts = meetingExpertService.listExpertLastByMeetingIds(singletonList); | |||
Map<Long, MeetingExpert> expertMap = CollUtils.listToMap(tempExperts, MeetingExpert::getExpertId); | |||
Map<Long, MeetingExpert> replacedMap = expertMap.values().stream().filter(w -> w.getPreId() > 0) | |||
.collect(Collectors.toMap(MeetingExpert::getPreId, w -> w)); | |||
Map<Long, ExpertCntBO> countMap = countByAgree(expertMap, replacedMap); | |||
// 已确认参加、通话中数量 | |||
List<MeetingExpert> tmpExperts = meetingExpertService.listExpertLastByMeetingId(meetingId); | |||
Map<Long, MeetingExpert> expertMap = CollUtils.listToMap(tmpExperts, MeetingExpert::getExpertId); | |||
// 统计通知中与同意参加专家数量 | |||
Map<Long, ExpertCntBO> countMap = countByAgree(expertMap); | |||
ExpertCntBO cnt = countMap.getOrDefault(ruleId, ExpertCntBO.zeroInit()); | |||
int tempCurrent = cnt.getAgreeCnt() + cnt.getNoticeCnt(); | |||
if (tempCurrent == value.getCount()) { | |||
int wouldAttendCnt = cnt.getAgreeCnt() + cnt.getNoticeCnt(); | |||
if (wouldAttendCnt == value.getCount()) { | |||
if (cnt.getAgreeCnt().equals(value.getCount())) { | |||
notIgnoreCnt.decrementAndGet(); | |||
} | |||
return; | |||
} | |||
int currInviteCnt = value.getCount() - tempCurrent; | |||
ExpertChooseDTO expertChoose = expertInviteManage.expertReplaceByRandomRule(avoidInfoDto, value, | |||
expertMap.values(), currInviteCnt, meeting.getStartTime(), meeting.getEndTime(), null); | |||
int needInviteCnt = value.getCount() - wouldAttendCnt; | |||
ExpertChooseDTO expertChoose = expertInviteManage.expertReplaceByRandomRule(avoidRule, value, | |||
tmpExperts, needInviteCnt, meeting.getStartTime(), meeting.getEndTime(), invitedRefused); | |||
if (expertChoose.getTotal() > 0) { | |||
List<MeetingExpert> expertMeetings = CollUtils.convert(expertChoose.getExperts(), w -> { | |||
MeetingExpert expert = ExpertInviteBuilder.getExpertByRandom(meetingId, w, ruleId); | |||
expert.setPreStatus(ExpertAttendStatusEnum.NOTICING.getCode()); | |||
expert.setStatus(ExpertAttendStatusEnum.NOTICING.getCode()); | |||
return expert; | |||
}); | |||
@@ -276,7 +280,7 @@ public class ExpertInviteTask { | |||
//================================================================================================================== | |||
private Map<Long, ExpertCntBO> countByAgree(Map<Long, MeetingExpert> expertMap, Map<Long, MeetingExpert> replacedMap) { | |||
private Map<Long, ExpertCntBO> countByAgree(Map<Long, MeetingExpert> expertMap) { | |||
return expertMap.entrySet().stream() | |||
.collect(Collectors.groupingBy(w -> w.getValue().getRuleId(), | |||
Collectors.collectingAndThen(Collectors.mapping(Map.Entry::getValue, Collectors.toList()), w -> { | |||
@@ -286,11 +290,6 @@ public class ExpertInviteTask { | |||
cnt.incrAgreeCnt(); | |||
} else if (ExpertAttendStatusEnum.NOTICING.eq(expert.getStatus())) { | |||
cnt.incrNoticeCnt(); | |||
} else if (ExpertAttendStatusEnum.REPLACED.eq(expert.getStatus())) { | |||
MeetingExpert replacedExpert = replacedMap.get(expert.getId()); | |||
if (replacedExpert != null && ExpertAttendStatusEnum.AGREED.eq(replacedExpert.getStatus())) { | |||
cnt.incrAgreeCnt(); | |||
} | |||
} | |||
} | |||
return cnt; | |||
@@ -15,7 +15,7 @@ spring: | |||
timeout: 5000 | |||
host: 47.98.125.47 | |||
port: 26379 | |||
database: 0 | |||
database: 4 | |||
password: Ndkj1234 | |||
jedis: | |||
pool: | |||