From f7752a34c35d2b1b833a5f348b6f9aa38e5d0496 Mon Sep 17 00:00:00 2001 From: WendyYang Date: Fri, 21 Apr 2023 14:22:13 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=93=E5=AE=B6=E6=8A=BD=E5=8F=96=E8=AF=AD?= =?UTF-8?q?=E9=9F=B3=E4=BB=BB=E5=8A=A1=E5=8F=8A=E7=9F=AD=E4=BF=A1=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ningdatech/pmapi/leave/manage/LeaveManage.java | 8 +- .../meeting/helper/MeetingCallOrMsgHelper.java | 190 ++++++++++++ .../pmapi/meeting/helper/MeetingMsgHelper.java | 167 ---------- .../pmapi/meeting/helper/YxtCallOrSmsHelper.java | 18 +- .../pmapi/meeting/manage/ExpertInviteManage.java | 6 +- .../pmapi/meeting/manage/MeetingManage.java | 22 +- .../pmapi/meeting/task/ExpertInviteTask.java | 338 --------------------- .../pmapi/meeting/task/ExpertRandomInviteTask.java | 336 ++++++++++++++++++++ 8 files changed, 544 insertions(+), 541 deletions(-) create mode 100644 pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/MeetingCallOrMsgHelper.java delete mode 100644 pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/MeetingMsgHelper.java delete mode 100644 pmapi/src/main/java/com/ningdatech/pmapi/meeting/task/ExpertInviteTask.java create mode 100644 pmapi/src/main/java/com/ningdatech/pmapi/meeting/task/ExpertRandomInviteTask.java diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/leave/manage/LeaveManage.java b/pmapi/src/main/java/com/ningdatech/pmapi/leave/manage/LeaveManage.java index aaae8c4..64d0c2b 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/leave/manage/LeaveManage.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/leave/manage/LeaveManage.java @@ -41,12 +41,10 @@ import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum; import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertInviteTypeEnum; import com.ningdatech.pmapi.meeting.entity.enumeration.MeetingStatusEnum; -import com.ningdatech.pmapi.meeting.helper.YxtCallOrSmsHelper; import com.ningdatech.pmapi.meeting.service.IMeetingExpertService; import com.ningdatech.pmapi.meeting.service.IMeetingService; -import com.ningdatech.pmapi.meeting.task.ExpertInviteTask; +import com.ningdatech.pmapi.meeting.task.ExpertRandomInviteTask; import com.ningdatech.pmapi.sms.utils.DateUtil; -import com.ningdatech.pmapi.user.service.IUserInfoService; import com.ningdatech.pmapi.user.util.LoginUserUtil; import lombok.AllArgsConstructor; import org.springframework.aop.framework.AopContext; @@ -71,7 +69,7 @@ import java.util.stream.Collectors; @AllArgsConstructor public class LeaveManage { - private final ExpertInviteTask inviteTask; + private final ExpertRandomInviteTask inviteTask; private final IMeetingService meetingService; private final IMeetingExpertService meetingExpertService; private final IExpertLeaveService leaveService; @@ -79,8 +77,6 @@ public class LeaveManage { private final IExpertMetaApplyService metaApplyService; private final FileService fileService; private final IExpertUserFullInfoService userFullInfoService; - private final YxtCallOrSmsHelper yxtCallOrSmsHelper; - private final IUserInfoService userInfoService; private static final int HOURS_BEFORE_MEETING = 2; diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/MeetingCallOrMsgHelper.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/MeetingCallOrMsgHelper.java new file mode 100644 index 0000000..0955e82 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/MeetingCallOrMsgHelper.java @@ -0,0 +1,190 @@ +package com.ningdatech.pmapi.meeting.helper; + +import cn.hutool.core.util.StrUtil; +import com.alibaba.fastjson.JSON; +import com.baomidou.mybatisplus.core.toolkit.Wrappers; +import com.ningdatech.basic.util.CollUtils; +import com.ningdatech.pmapi.meeting.constant.MeetingMsgTemplateConst; +import com.ningdatech.pmapi.meeting.entity.domain.Meeting; +import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; +import com.ningdatech.pmapi.meeting.entity.enumeration.MeetingReviewTypeEnum; +import com.ningdatech.pmapi.organization.model.entity.DingEmployeeInfo; +import com.ningdatech.pmapi.organization.model.entity.DingOrganization; +import com.ningdatech.pmapi.organization.service.IDingEmployeeInfoService; +import com.ningdatech.pmapi.organization.service.IDingOrganizationService; +import com.ningdatech.pmapi.sms.constant.VoiceSmsTemplateConst; +import com.ningdatech.pmapi.sms.utils.DateUtil; +import com.ningdatech.pmapi.staging.enums.MsgTypeEnum; +import com.ningdatech.pmapi.staging.service.INdWorkNoticeStagingService; +import com.ningdatech.pmapi.sys.model.entity.Notify; +import com.ningdatech.pmapi.sys.service.INotifyService; +import com.ningdatech.pmapi.todocenter.bean.entity.WorkNoticeInfo; +import com.ningdatech.pmapi.user.entity.UserInfo; +import com.ningdatech.pmapi.user.service.IUserInfoService; +import com.ningdatech.yxt.model.cmd.SendSmsCmd.SendSmsContext; +import com.ningdatech.yxt.model.cmd.SubmitTaskCallCmd.SubmitTaskCallContext; +import lombok.AllArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + *

+ * MeetingMsgHelper + *

+ * + * @author WendyYang + * @since 2023/4/20 + **/ +@Component +@AllArgsConstructor +public class MeetingCallOrMsgHelper { + + private final IUserInfoService userInfoService; + private final YxtCallOrSmsHelper yxtCallOrSmsHelper; + private final INdWorkNoticeStagingService workNoticeStagingService; + private final IDingEmployeeInfoService dingEmployeeInfoService; + private final IDingOrganizationService dingOrganizationService; + private final INotifyService notifyService; + + private static String officialTime(LocalDateTime time) { + return time.format(DateUtil.DTF_YMD_HM); + } + + private Notify getNotify(Long userId, String msg, MsgTypeEnum type, Map extraPara) { + Notify notify = new Notify(); + notify.setUserId(userId); + notify.setContent(msg); + notify.setType(type.name()); + notify.setReaded(Boolean.FALSE); + notify.setCreateTime(LocalDateTime.now()); + String extraJson = JSON.toJSONString(extraPara); + notify.setExtraInfo(extraJson); + return notify; + } + + private WorkNoticeInfo getSendWorkNoticeInfo(Long accountId) { + WorkNoticeInfo workNoticeInfo = new WorkNoticeInfo(); + workNoticeInfo.setAccountId(accountId); + // 根据浙政钉用户ID获取部门code + DingEmployeeInfo employeeInfo = dingEmployeeInfoService.getOne(Wrappers.lambdaQuery(DingEmployeeInfo.class) + .eq(DingEmployeeInfo::getAccountId, accountId) + .eq(DingEmployeeInfo::getMainJob, String.valueOf(Boolean.TRUE)) + .last("limit 1")); + String organizationCode = employeeInfo.getOrganizationCode(); + workNoticeInfo.setOrganizationCode(organizationCode); + // 根据部门code获取部门名称 + DingOrganization dingOrganization = dingOrganizationService.getOne(Wrappers.lambdaQuery(DingOrganization.class) + .eq(DingOrganization::getOrganizationCode, organizationCode)); + String organizationName = dingOrganization.getOrganizationName(); + workNoticeInfo.setOrganizationName(organizationName); + // 构建唯一的消息ID + String bizMsgId = "ZD_WORK_NOTICE_" + StrUtil.UNDERLINE + organizationCode + StrUtil.UNDERLINE + + organizationName + accountId + StrUtil.UNDERLINE + System.currentTimeMillis(); + workNoticeInfo.setBizMsgId(bizMsgId); + String receiverUserId = String.valueOf(accountId); + workNoticeInfo.setReceiverUserId(receiverUserId); + return workNoticeInfo; + } + + @Transactional(rollbackFor = Exception.class) + public void sendInviteStopMsg(Long userId, Long meetingId, String meetingName) { + UserInfo info = userInfoService.getById(userId); + String msgContent = String.format(MeetingMsgTemplateConst.INVITE_END, meetingName); + // 音信通消息 + SendSmsContext yxtContent = new SendSmsContext(); + yxtContent.setContent(msgContent); + yxtContent.setReceiveNumber(info.getMobile()); + yxtCallOrSmsHelper.sendSms(yxtContent); + // 发送工作通知 + if (info.getAccountId() != null) { + WorkNoticeInfo swn = getSendWorkNoticeInfo(info.getAccountId()); + swn.setMsg(msgContent); + workNoticeStagingService.addByWorkNotice(swn, MsgTypeEnum.REVIEW_MEETING); + Map map = new HashMap<>(2); + map.put("meetingId", meetingId); + Notify notify = getNotify(userId, msgContent, MsgTypeEnum.REVIEW_MEETING, map); + notifyService.save(notify); + } + } + + @Transactional(rollbackFor = Exception.class) + public void sendConfirmedRosterMsg(List experts, Meeting meeting) { + List userIds = CollUtils.fieldList(experts, MeetingExpert::getExpertId); + List userInfos = userInfoService.listByIds(userIds); + Map userMap = CollUtils.listToMap(userInfos, UserInfo::getId); + String sTime = officialTime(meeting.getStartTime()); + String eTime = officialTime(meeting.getEndTime()); + String meetingTime = sTime + "至" + eTime; + List yxtContents = new ArrayList<>(); + List notifies = new ArrayList<>(); + List workingNotices = new ArrayList<>(); + experts.forEach(w -> { + String msgContent = String.format(MeetingMsgTemplateConst.CONFIRMED_ROSTER, + w.getExpertName(), officialTime(w.getCreateOn()), meetingTime, meeting.getMeetingAddress(), + meeting.getConnecter(), meeting.getContact()); + // 音信通消息 + SendSmsContext yxtContent = new SendSmsContext(); + yxtContent.setContent(msgContent); + yxtContent.setReceiveNumber(w.getMobile()); + yxtContents.add(yxtContent); + UserInfo info = userMap.get(w.getExpertId()); + // 发送工作通知 + if (info.getAccountId() != null) { + WorkNoticeInfo swn = getSendWorkNoticeInfo(info.getAccountId()); + swn.setMsg(msgContent); + workingNotices.add(swn); + Map map = new HashMap<>(2); + map.put("meetingId", meeting.getId()); + Notify notify = getNotify(info.getId(), msgContent, MsgTypeEnum.EXPERT_REVIEW, map); + notifies.add(notify); + } + }); + notifyService.saveBatch(notifies); + yxtCallOrSmsHelper.sendSms(yxtContents); + workNoticeStagingService.addByWorkNotice(workingNotices, MsgTypeEnum.EXPERT_REVIEW); + } + + + public void sendCancelMeetingMsg(List experts, Meeting meeting) { + String startTime = officialTime(meeting.getStartTime()); + String meetingType = MeetingReviewTypeEnum.getValue(meeting.getType()); + List contexts = experts.stream().map(w -> { + String content = String.format(MeetingMsgTemplateConst.MEETING_CANCEL, + w.getExpertName(), startTime, meetingType, meeting.getContact()); + SendSmsContext context = new SendSmsContext(); + context.setReceiveNumber(w.getMobile()); + context.setContent(content); + return context; + }).collect(Collectors.toList()); + yxtCallOrSmsHelper.sendSms(contexts); + } + + /** + * 提交专家通知音信通任务 + * + * @param meeting 会议信息 + * @param experts 待通知专家 + * @author WendyYang + **/ + public void callExpertByMeeting(Meeting meeting, List experts) { + String voiceContent = String.format(VoiceSmsTemplateConst.OFFLINE_TEMPLATE, + meeting.getHoldOrg(), meeting.getName(), officialTime(meeting.getStartTime()), + meeting.getMeetingAddress()); + List callContexts = CollUtils.convert(experts, w -> { + SubmitTaskCallContext context = new SubmitTaskCallContext(); + context.setContent(voiceContent); + context.setReceiveNumber(w.getMobile()); + return context; + }); + String submitKey = yxtCallOrSmsHelper.submitCallTask(callContexts); + experts.forEach(w -> w.setSubmitKey(submitKey)); + } + +} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/MeetingMsgHelper.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/MeetingMsgHelper.java deleted file mode 100644 index 27ffb72..0000000 --- a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/MeetingMsgHelper.java +++ /dev/null @@ -1,167 +0,0 @@ -package com.ningdatech.pmapi.meeting.helper; - -import cn.hutool.core.date.DatePattern; -import cn.hutool.core.util.StrUtil; -import com.alibaba.fastjson.JSON; -import com.baomidou.mybatisplus.core.toolkit.Wrappers; -import com.ningdatech.basic.util.CollUtils; -import com.ningdatech.pmapi.meeting.constant.MeetingMsgTemplateConst; -import com.ningdatech.pmapi.meeting.entity.domain.Meeting; -import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; -import com.ningdatech.pmapi.meeting.entity.enumeration.MeetingReviewTypeEnum; -import com.ningdatech.pmapi.organization.model.entity.DingEmployeeInfo; -import com.ningdatech.pmapi.organization.model.entity.DingOrganization; -import com.ningdatech.pmapi.organization.service.IDingEmployeeInfoService; -import com.ningdatech.pmapi.organization.service.IDingOrganizationService; -import com.ningdatech.pmapi.staging.enums.MsgTypeEnum; -import com.ningdatech.pmapi.staging.service.INdWorkNoticeStagingService; -import com.ningdatech.pmapi.sys.model.entity.Notify; -import com.ningdatech.pmapi.sys.service.INotifyService; -import com.ningdatech.pmapi.todocenter.bean.entity.WorkNoticeInfo; -import com.ningdatech.pmapi.user.entity.UserInfo; -import com.ningdatech.pmapi.user.service.IUserInfoService; -import com.ningdatech.yxt.model.cmd.SendSmsCmd.SendSmsContext; -import lombok.AllArgsConstructor; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; - -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; - -/** - *

- * MeetingMsgHelper - *

- * - * @author WendyYang - * @since 2023/4/20 - **/ -@Component -@AllArgsConstructor -public class MeetingMsgHelper { - - private final IUserInfoService userInfoService; - private final YxtCallOrSmsHelper yxtCallOrSmsHelper; - private final INdWorkNoticeStagingService workNoticeStagingService; - private final IDingEmployeeInfoService dingEmployeeInfoService; - private final IDingOrganizationService dingOrganizationService; - private final INotifyService notifyService; - - private static String officialTime(LocalDateTime time) { - return time.format(DatePattern.NORM_DATETIME_MINUTE_FORMATTER); - } - - private Notify getNotify(Long userId, String msg, MsgTypeEnum type, Map extraPara) { - Notify notify = new Notify(); - notify.setUserId(userId); - notify.setContent(msg); - notify.setType(type.name()); - notify.setReaded(Boolean.FALSE); - notify.setCreateTime(LocalDateTime.now()); - String extraJson = JSON.toJSONString(extraPara); - notify.setExtraInfo(extraJson); - return notify; - } - - private WorkNoticeInfo getSendWorkNoticeInfo(Long accountId) { - WorkNoticeInfo workNoticeInfo = new WorkNoticeInfo(); - workNoticeInfo.setAccountId(accountId); - // 根据浙政钉用户ID获取部门code - DingEmployeeInfo employeeInfo = dingEmployeeInfoService.getOne(Wrappers.lambdaQuery(DingEmployeeInfo.class) - .eq(DingEmployeeInfo::getAccountId, accountId) - .eq(DingEmployeeInfo::getMainJob, String.valueOf(Boolean.TRUE)) - .last("limit 1")); - String organizationCode = employeeInfo.getOrganizationCode(); - workNoticeInfo.setOrganizationCode(organizationCode); - // 根据部门code获取部门名称 - DingOrganization dingOrganization = dingOrganizationService.getOne(Wrappers.lambdaQuery(DingOrganization.class) - .eq(DingOrganization::getOrganizationCode, organizationCode)); - String organizationName = dingOrganization.getOrganizationName(); - workNoticeInfo.setOrganizationName(organizationName); - // 构建唯一的消息ID - String bizMsgId = "ZD_WORK_NOTICE_" + StrUtil.UNDERLINE + organizationCode + StrUtil.UNDERLINE - + organizationName + accountId + StrUtil.UNDERLINE + System.currentTimeMillis(); - workNoticeInfo.setBizMsgId(bizMsgId); - String receiverUserId = String.valueOf(accountId); - workNoticeInfo.setReceiverUserId(receiverUserId); - return workNoticeInfo; - } - - @Transactional(rollbackFor = Exception.class) - public void sendInviteStopMsg(Long userId, Long meetingId, String meetingName) { - UserInfo info = userInfoService.getById(userId); - String msgContent = String.format(MeetingMsgTemplateConst.INVITE_END, meetingName); - // 音信通消息 - SendSmsContext yxtContent = new SendSmsContext(); - yxtContent.setContent(msgContent); - yxtContent.setReceiveNumber(info.getMobile()); - yxtCallOrSmsHelper.sendSms(yxtContent); - // 发送工作通知 - if (info.getAccountId() != null) { - WorkNoticeInfo swn = getSendWorkNoticeInfo(info.getAccountId()); - swn.setMsg(msgContent); - workNoticeStagingService.addByWorkNotice(swn, MsgTypeEnum.REVIEW_MEETING); - Map map = new HashMap<>(2); - map.put("meetingId", meetingId); - Notify notify = getNotify(userId, msgContent, MsgTypeEnum.REVIEW_MEETING, map); - notifyService.save(notify); - } - } - - @Transactional(rollbackFor = Exception.class) - public void sendConfirmedRosterMsg(List experts, Meeting meeting) { - List userIds = CollUtils.fieldList(experts, MeetingExpert::getExpertId); - List userInfos = userInfoService.listByIds(userIds); - Map userMap = CollUtils.listToMap(userInfos, UserInfo::getId); - String sTime = officialTime(meeting.getStartTime()); - String eTime = officialTime(meeting.getEndTime()); - String meetingTime = sTime + "至" + eTime; - List yxtContents = new ArrayList<>(); - List notifies = new ArrayList<>(); - List workingNotices = new ArrayList<>(); - experts.forEach(w -> { - String msgContent = String.format(MeetingMsgTemplateConst.CONFIRMED_ROSTER, - w.getExpertName(), officialTime(w.getCreateOn()), meetingTime, meeting.getMeetingAddress(), - meeting.getConnecter(), meeting.getContact()); - // 音信通消息 - SendSmsContext yxtContent = new SendSmsContext(); - yxtContent.setContent(msgContent); - yxtContent.setReceiveNumber(w.getMobile()); - yxtContents.add(yxtContent); - UserInfo info = userMap.get(w.getExpertId()); - // 发送工作通知 - if (info.getAccountId() != null) { - WorkNoticeInfo swn = getSendWorkNoticeInfo(info.getAccountId()); - swn.setMsg(msgContent); - workingNotices.add(swn); - Map map = new HashMap<>(2); - map.put("meetingId", meeting.getId()); - Notify notify = getNotify(info.getId(), msgContent, MsgTypeEnum.EXPERT_REVIEW, map); - notifies.add(notify); - } - }); - notifyService.saveBatch(notifies); - yxtCallOrSmsHelper.sendSms(yxtContents); - workNoticeStagingService.addByWorkNotice(workingNotices, MsgTypeEnum.EXPERT_REVIEW); - } - - - public void sendCancelMeetingMsg(List experts, Meeting meeting) { - String startTime = officialTime(meeting.getStartTime()); - String meetingType = MeetingReviewTypeEnum.getValue(meeting.getType()); - List contexts = experts.stream().map(w -> { - String content = String.format(MeetingMsgTemplateConst.MEETING_CANCEL, - w.getExpertName(), startTime, meetingType, meeting.getContact()); - SendSmsContext context = new SendSmsContext(); - context.setReceiveNumber(w.getMobile()); - context.setContent(content); - return context; - }).collect(Collectors.toList()); - yxtCallOrSmsHelper.sendSms(contexts); - } - -} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/YxtCallOrSmsHelper.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/YxtCallOrSmsHelper.java index 3a1f4b0..ce511c7 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/YxtCallOrSmsHelper.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/helper/YxtCallOrSmsHelper.java @@ -1,10 +1,5 @@ package com.ningdatech.pmapi.meeting.helper; -import com.ningdatech.basic.util.CollUtils; -import com.ningdatech.pmapi.meeting.entity.domain.Meeting; -import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; -import com.ningdatech.pmapi.sms.constant.VoiceSmsTemplateConst; -import com.ningdatech.pmapi.sms.utils.DateUtil; import com.ningdatech.yxt.client.YxtClient; import com.ningdatech.yxt.constants.YxtSmsSignEnum; import com.ningdatech.yxt.model.cmd.SendSmsCmd; @@ -33,18 +28,9 @@ public class YxtCallOrSmsHelper { private final YxtClient yxtClient; - public void callByMeetingExperts(Meeting meeting, List experts) { - String callContent = String.format(VoiceSmsTemplateConst.OFFLINE_TEMPLATE, - meeting.getHoldOrg(), meeting.getName(), - meeting.getStartTime().format(DateUtil.DTF_YMD_HM), meeting.getMeetingAddress()); - List callContexts = CollUtils.convert(experts, w -> { - SubmitTaskCallContext context = new SubmitTaskCallContext(); - context.setContent(callContent); - context.setReceiveNumber(w.getMobile()); - return context; - }); + public String submitCallTask(List callContexts) { SubmitTaskCallResponse callResponse = yxtClient.submitTaskCall(of(callContexts)); - experts.forEach(w -> w.setSubmitKey(callResponse.getSubmitKey())); + return callResponse.getSubmitKey(); } public void sendSms(List smsList) { diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/ExpertInviteManage.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/ExpertInviteManage.java index 69134e4..0b575c3 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/ExpertInviteManage.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/ExpertInviteManage.java @@ -19,7 +19,7 @@ import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; import com.ningdatech.pmapi.meeting.entity.dto.*; import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum; import com.ningdatech.pmapi.meeting.helper.ExpertInviteHelper; -import com.ningdatech.pmapi.meeting.helper.YxtCallOrSmsHelper; +import com.ningdatech.pmapi.meeting.helper.MeetingCallOrMsgHelper; import com.ningdatech.pmapi.meeting.service.IExpertInviteRuleService; import com.ningdatech.pmapi.meeting.service.IMeetingExpertService; import com.ningdatech.pmapi.meeting.service.IMeetingService; @@ -65,7 +65,7 @@ public class ExpertInviteManage { private final IExpertUserFullInfoService expertUserFullInfoService; private final IMeetingService meetingService; private final IExpertAvoidCompanyService expertAvoidCompanyService; - private final YxtCallOrSmsHelper yxtCallOrSmsHelper; + private final MeetingCallOrMsgHelper meetingCallOrMsgHelper; @Value("#{randomInviteProperties.recentMeetingCount}") private Integer recentMeetingCount; @@ -547,7 +547,7 @@ public class ExpertInviteManage { expertInserts.add(expert); }); } - yxtCallOrSmsHelper.callByMeetingExperts(meeting, expertInserts); + meetingCallOrMsgHelper.callExpertByMeeting(meeting, expertInserts); } meetingExpertService.saveBatch(expertInserts); } diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/MeetingManage.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/MeetingManage.java index aa8f9be..434460f 100644 --- a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/MeetingManage.java +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/manage/MeetingManage.java @@ -36,10 +36,10 @@ import com.ningdatech.pmapi.meeting.entity.req.*; import com.ningdatech.pmapi.meeting.entity.vo.*; import com.ningdatech.pmapi.meeting.helper.ExpertInviteHelper; import com.ningdatech.pmapi.meeting.helper.MeetingManageHelper; -import com.ningdatech.pmapi.meeting.helper.MeetingMsgHelper; +import com.ningdatech.pmapi.meeting.helper.MeetingCallOrMsgHelper; import com.ningdatech.pmapi.meeting.helper.YxtCallOrSmsHelper; import com.ningdatech.pmapi.meeting.service.*; -import com.ningdatech.pmapi.meeting.task.ExpertInviteTask; +import com.ningdatech.pmapi.meeting.task.ExpertRandomInviteTask; import com.ningdatech.pmapi.meta.helper.DictionaryCache; import com.ningdatech.pmapi.meta.helper.TagCache; import com.ningdatech.pmapi.organization.service.IDingOrganizationService; @@ -85,7 +85,7 @@ public class MeetingManage { private final DictionaryCache dictionaryCache; private final IMeetingExpertService meetingExpertService; private final ExpertInviteManage expertInviteManage; - private final ExpertInviteTask expertInviteTask; + private final ExpertRandomInviteTask expertRandomInviteTask; private final MeetingManageHelper meetingManageHelper; private final YxtCallOrSmsHelper yxtCallOrSmsHelper; private final DistributedLock distributedLock; @@ -97,7 +97,7 @@ public class MeetingManage { private final IDingOrganizationService dingOrganizationService; private final IExpertReviewService expertReviewService; private final ExpertInviteHelper expertInviteHelper; - private final MeetingMsgHelper meetingMsgHelper; + private final MeetingCallOrMsgHelper meetingCallOrMsgHelper; private static final String INVITED_RULE_CREATE = "INVITED_RULE_CREATE:"; private static final String MEETING_CREATE_KEY = "MEETING_CREATE:"; @@ -183,7 +183,7 @@ public class MeetingManage { if (!invitedContinue) { throw BizException.wrap("抽取人员数量已满足抽取规则"); } - expertInviteTask.notifyInviteTask(meetingId); + expertRandomInviteTask.notifyInviteTask(meetingId); } finally { distributedLock.releaseLock(key); } @@ -200,7 +200,7 @@ public class MeetingManage { if (!MeetingStatusEnum.NORMAL.eq(meeting.getStatus())) { throw BizException.wrap("转换失败,请刷新后重试"); } - expertInviteTask.cancelByMeetingId(meetingId); + expertRandomInviteTask.cancelByMeetingId(meetingId); LambdaUpdateWrapper meetingUpdate = Wrappers.lambdaUpdate(Meeting.class) .set(Meeting::getInviteType, APPOINT.getCode()) .eq(Meeting::getId, meetingId); @@ -241,7 +241,7 @@ public class MeetingManage { // 随机抽取的话则需进行抽取数量校验 LocalDateTime now = LocalDateTime.now(); expertInviteManage.expertInviteByMeetingCreate(meeting, randomRules, avoidInfo); - expertInviteTask.addInviteTaskByMeetingCreate(meeting.getId(), now); + expertRandomInviteTask.addInviteTaskByMeetingCreate(meeting.getId(), now); LambdaUpdateWrapper mUpdate = Wrappers.lambdaUpdate(Meeting.class) .set(Meeting::getInviteStatus, false) .eq(Meeting::getId, meeting.getId()); @@ -638,7 +638,7 @@ public class MeetingManage { } public void stopRandomInvite(Long meetingId) { - expertInviteTask.cancelByMeetingId(meetingId); + expertRandomInviteTask.cancelByMeetingId(meetingId); } @Transactional(rollbackFor = Exception.class) @@ -660,11 +660,11 @@ public class MeetingManage { .set(Meeting::getStatus, MeetingStatusEnum.CANCELED.getCode()) .eq(Meeting::getId, meetingId); meetingService.update(meetingUpdate); - expertInviteTask.cancelByMeetingId(meetingId); + expertRandomInviteTask.cancelByMeetingId(meetingId); // 发送通知给专家 List experts = meetingExpertService.listAgreedExperts(meetingId); if (!experts.isEmpty()) { - meetingMsgHelper.sendCancelMeetingMsg(experts, meeting); + meetingCallOrMsgHelper.sendCancelMeetingMsg(experts, meeting); } } finally { distributedLock.releaseLock(key); @@ -824,7 +824,7 @@ public class MeetingManage { .in(MeetingExpert::getId, currConfirmedMeIds) .set(MeetingExpert::getConfirmedRoster, Boolean.TRUE); meetingExpertService.update(meUpdate); - meetingMsgHelper.sendConfirmedRosterMsg(expertNoticing, meeting); + meetingCallOrMsgHelper.sendConfirmedRosterMsg(expertNoticing, meeting); } finally { distributedLock.releaseLock(key); } diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/task/ExpertInviteTask.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/task/ExpertInviteTask.java deleted file mode 100644 index 6fd806c..0000000 --- a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/task/ExpertInviteTask.java +++ /dev/null @@ -1,338 +0,0 @@ -package com.ningdatech.pmapi.meeting.task; - -import cn.hutool.core.util.ArrayUtil; -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.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.Meeting; -import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; -import com.ningdatech.pmapi.meeting.entity.dto.AvoidRuleDTO; -import com.ningdatech.pmapi.meeting.entity.dto.ExpertChooseDTO; -import com.ningdatech.pmapi.meeting.entity.dto.InviteCacheDTO; -import com.ningdatech.pmapi.meeting.entity.dto.RandomInviteRuleDTO; -import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum; -import com.ningdatech.pmapi.meeting.helper.MeetingMsgHelper; -import com.ningdatech.pmapi.meeting.helper.YxtCallOrSmsHelper; -import com.ningdatech.pmapi.meeting.manage.ExpertInviteManage; -import com.ningdatech.pmapi.meeting.service.IExpertInviteAvoidRuleService; -import com.ningdatech.pmapi.meeting.service.IExpertInviteRuleService; -import com.ningdatech.pmapi.meeting.service.IMeetingExpertService; -import com.ningdatech.pmapi.meeting.service.IMeetingService; -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.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.stereotype.Component; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.util.Assert; - -import javax.annotation.PostConstruct; -import javax.annotation.Resource; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDateTime; -import java.time.ZoneId; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ScheduledFuture; -import java.util.concurrent.atomic.AtomicInteger; - - -/** - *

- * ExpertInviteTask - *

- * - * @author WendyYang - * @since 21:25 2022/8/15 - */ -@Slf4j -@Component -@AllArgsConstructor -public class ExpertInviteTask { - - private final RandomInviteProperties properties; - private static final String MEETING_ID_INVITE_RANDOM = "MEETING_ID_INVITE_RANDOM"; - - private static final Duration EXPIRE_TIME = Duration.ofDays(60); - - private final CachePlusOps cachePlusOps; - @Resource(name = "expertInviteScheduler") - private ThreadPoolTaskScheduler scheduler; - private final IMeetingExpertService meetingExpertService; - private final IExpertInviteRuleService inviteRuleService; - private final IMeetingService meetingService; - private final ExpertInviteManage expertInviteManage; - private final IExpertInviteAvoidRuleService inviteAvoidRuleService; - private final YxtCallOrSmsHelper yxtCallOrSmsHelper; - private final MeetingMsgHelper meetingMsgHelper; - - /** - * 用来存入线程执行句柄, 停止定时任务时使用 - */ - private static final ConcurrentMap> INVITE_TASK_MAP = new ConcurrentHashMap<>(); - - private ExpertInviteTask currProxy() { - return (ExpertInviteTask) AopContext.currentProxy(); - } - - private CacheHashKey getCacheKey(Long meetingId) { - String field = meetingId == null ? null : meetingId.toString(); - return new CacheHashKey(MEETING_ID_INVITE_RANDOM, field, EXPIRE_TIME); - } - - @PostConstruct - public void initTask() { - if (!properties.getEnable()) { - log.warn("随机邀请已关闭……"); - return; - } - initInviteTaskAfterStarted(); - } - - /** - * 项目重启之后重新初始化邀请任务 - */ - private void initInviteTaskAfterStarted() { - Map caches = cachePlusOps.hGetAll(getCacheKey(null)); - if (MapUtils.isEmpty(caches)) { - log.info("暂无需要初始化的抽取会议信息"); - return; - } - Integer inviteDelay = properties.getInviteDelay(); - for (InviteCacheDTO cache : caches.values()) { - Boolean reInvite = cache.getReInvite(); - LocalDateTime tsTime = cache.getTaskStartTime(); - boolean added = addInviteTask(cache.getMeetingId(), true, inviteDelay, reInvite, tsTime); - if (!added) { - cachePlusOps.hDel(getCacheKey(cache.getMeetingId())); - } - } - } - - /** - * 专家抽取校验(抽取数量是否不足) - * - * @param meetingId 会议ID - * @return boolean - * @author WendyYang - **/ - private boolean inviteCountCheck(Long meetingId) { - Map ruleMap = inviteRuleService.randomRuleByMeetingId(meetingId); - if (ruleMap.isEmpty()) { - return Boolean.FALSE; - } - LambdaQueryWrapper query = Wrappers.lambdaQuery(MeetingExpert.class) - .select(MeetingExpert::getRuleId, MeetingExpert::getStatus) - .in(MeetingExpert::getRuleId, ruleMap.keySet()) - .eq(MeetingExpert::getStatus, ExpertAttendStatusEnum.AGREED.getCode()); - List experts = meetingExpertService.list(query); - if (experts.isEmpty()) { - return Boolean.TRUE; - } - Map cntMap = CollUtils.groupCount(experts, MeetingExpert::getRuleId); - for (Map.Entry entry : ruleMap.entrySet()) { - Long agreeCnt = cntMap.getOrDefault(entry.getKey(), 0L); - if (agreeCnt < entry.getValue().getCount()) { - return Boolean.TRUE; - } - } - return Boolean.FALSE; - } - - /** - * 唤醒某个会议的抽取任务 - * - * @param meetingId 会议ID - * @param reInvite 是否可邀请已拒绝的专家 - * @author WendyYang - **/ - public void notifyInviteTask(Long meetingId, boolean... reInvite) { - boolean tmpReInvite = ArrayUtil.isEmpty(reInvite) || reInvite[0]; - if (!INVITE_TASK_MAP.containsKey(meetingId)) { - if (addInviteTask(meetingId, false, properties.getInviteDelay(), tmpReInvite, LocalDateTime.now())) { - log.info("重置会议的随机抽取状态:{}", meetingId); - LambdaUpdateWrapper update = Wrappers.lambdaUpdate(Meeting.class); - update.set(Meeting::getInviteStatus, false); - update.eq(Meeting::getId, meetingId); - meetingService.update(update); - InviteCacheDTO cacheVal = InviteCacheDTO.of(meetingId, tmpReInvite, LocalDateTime.now()); - cachePlusOps.hSet(getCacheKey(meetingId), cacheVal); - } - } - } - - /** - * 添加专家抽取校验任务 - * - * @param meetingId 会议ID - * @param checked 是否前置校验 - * @param delayTime 延迟执行时间 - * @param reInvite 是否可以邀请被拒绝的专家 - * @param tsTime 任务启动时间 - * @return 是否添加任务成功 - * @author WendyYang - **/ - private boolean addInviteTask(Long meetingId, boolean checked, int delayTime, boolean reInvite, LocalDateTime tsTime) { - if (checked && !inviteCountCheck(meetingId)) { - // 如果抽取数量满足直接返回 - return Boolean.FALSE; - } - Instant startTime = LocalDateTime.now().plusMinutes(delayTime).atZone(ZoneId.systemDefault()).toInstant(); - ScheduledFuture future = scheduler.scheduleAtFixedRate(() -> { - ExpertInviteTask bean = SpringContextHolder.getBean(ExpertInviteTask.class); - try { - bean.invite(meetingId, reInvite, tsTime); - } catch (Exception e) { - log.error("执行专家邀请任务异常:{}", meetingId, e); - } - }, startTime, Duration.ofMinutes(properties.getInviteFixedRate())); - INVITE_TASK_MAP.putIfAbsent(meetingId, future); - log.info("添加专家抽取后台任务:{}", meetingId); - return Boolean.TRUE; - } - - /** - * 创建会议时添加抽取任务 - * - * @param meetingId 会议ID - * @param tsTime 开始时间 - * @author WendyYang - **/ - public void addInviteTaskByMeetingCreate(Long meetingId, LocalDateTime tsTime) { - Assert.isTrue(properties.getEnable(), "随机邀请已关闭"); - addInviteTask(meetingId, false, properties.getInviteDelay(), false, tsTime); - InviteCacheDTO cacheVal = InviteCacheDTO.of(meetingId, false, tsTime); - cachePlusOps.hSet(getCacheKey(meetingId), cacheVal); - } - - /** - * 抽取过程 - * - * @param meetingId 会议ID - * @param reInvite 是否可以邀请已拒绝的专家 - * @param tsTime 任务开启时间 - */ - @Transactional(rollbackFor = Exception.class) - public void invite(Long meetingId, Boolean reInvite, LocalDateTime tsTime) { - log.info("开始进行专家后台抽取:{}", meetingId); - Meeting meeting = meetingService.getById(meetingId); - if (meeting.getStartTime().isBefore(LocalDateTime.now())) { - log.info("会议已开始停止抽取:{}", meeting); - cancelByMeetingId(meetingId); - return; - } - // 随机邀请规则 - Map ruleMap = inviteRuleService.randomRuleByMeetingId(meetingId); - // 回避规则 - AvoidRuleDTO avoidRule = inviteAvoidRuleService.getAvoidInfoDto(meetingId); - // 还需要抽取的规则数量 - AtomicInteger notIgnoreCnt = new AtomicInteger(ruleMap.size()); - AtomicInteger notSupportCnt = new AtomicInteger(0); - LocalDateTime msTime = meeting.getStartTime(); - LocalDateTime meTime = meeting.getStartTime(); - ruleMap.forEach((ruleId, value) -> { - List tmpExperts = meetingExpertService.listExpertLastByMeetingId(meetingId); - Map expertMap = CollUtils.listToMap(tmpExperts, MeetingExpert::getExpertId); - // 统计通知中与同意参加专家数量 - Map countMap = countByAttendStatus(expertMap); - ExpertCntBO cnt = countMap.getOrDefault(ruleId, ExpertCntBO.zeroInit()); - int wouldAttendCnt = cnt.getAgreeCnt() + cnt.getNoticeCnt(); - if (wouldAttendCnt == value.getCount()) { - if (cnt.getAgreeCnt().equals(value.getCount())) { - notIgnoreCnt.decrementAndGet(); - } - return; - } - int needInviteCnt = value.getCount() - wouldAttendCnt; - ExpertChooseDTO expertChoose = expertInviteManage.expertReplaceByRandomRule(avoidRule, value, - tmpExperts, needInviteCnt, msTime, meTime, tsTime, reInvite); - - if (expertChoose.getTotal() > 0) { - List expertMeetings = CollUtils.convert(expertChoose.getExperts(), w -> { - MeetingExpert expert = ExpertInviteBuilder.getExpertByRandom(meetingId, w, ruleId); - expert.setStatus(ExpertAttendStatusEnum.NOTICING.getCode()); - return expert; - }); - yxtCallOrSmsHelper.callByMeetingExperts(meeting, expertMeetings); - log.info("会议:{} 后台抽取专家:{}名", meetingId, expertMeetings.size()); - meetingExpertService.saveBatch(expertMeetings); - } else { - // 抽取人数不够 - notSupportCnt.incrementAndGet(); - } - }); - if (notIgnoreCnt.get() == 0 || notIgnoreCnt.get() == notSupportCnt.get()) { - log.info("停止会议随机邀请:{} 未完成抽取规则数量 {} 无可抽取专家规则数量 {}", meetingId, notIgnoreCnt, notSupportCnt); - currProxy().cancelByMeetingId(meetingId); - meetingMsgHelper.sendInviteStopMsg(meeting.getCreateBy(), meetingId, meeting.getName()); - } - } - - @Transactional(rollbackFor = Exception.class) - public void cancelByMeetingId(Long meetingId) { - log.info("终止专家抽取:{}", meetingId); - meetingService.stopRandomInvite(meetingId); - cachePlusOps.hDel(getCacheKey(meetingId)); - ScheduledFuture future = INVITE_TASK_MAP.get(meetingId); - if (future != null) { - INVITE_TASK_MAP.remove(meetingId); - if (!future.isCancelled()) { - future.cancel(true); - } - } - } - - //================================================================================================================== - - private Map countByAttendStatus(Map expertMap) { - Map cntMap = new HashMap<>(8); - expertMap.values().forEach(w -> { - Long ruleId = w.getRuleId(); - ExpertCntBO cnt = cntMap.computeIfAbsent(ruleId, k -> ExpertCntBO.zeroInit()); - if (ExpertAttendStatusEnum.AGREED.eq(w.getStatus())) { - cnt.incrAgreeCnt(); - } else if (ExpertAttendStatusEnum.NOTICING.eq(w.getStatus())) { - cnt.incrNoticeCnt(); - } - }); - return cntMap; - } - - @Data - public static class ExpertCntBO { - - private Integer agreeCnt; - - private Integer noticeCnt; - - public ExpertCntBO(Integer agreeCnt, Integer noticeCnt) { - this.agreeCnt = agreeCnt; - this.noticeCnt = noticeCnt; - } - - public void incrAgreeCnt() { - agreeCnt++; - } - - public void incrNoticeCnt() { - noticeCnt++; - } - - public static ExpertCntBO zeroInit() { - return new ExpertCntBO(0, 0); - } - - } - -} diff --git a/pmapi/src/main/java/com/ningdatech/pmapi/meeting/task/ExpertRandomInviteTask.java b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/task/ExpertRandomInviteTask.java new file mode 100644 index 0000000..de14ae1 --- /dev/null +++ b/pmapi/src/main/java/com/ningdatech/pmapi/meeting/task/ExpertRandomInviteTask.java @@ -0,0 +1,336 @@ +package com.ningdatech.pmapi.meeting.task; + +import cn.hutool.core.util.ArrayUtil; +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.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.Meeting; +import com.ningdatech.pmapi.meeting.entity.domain.MeetingExpert; +import com.ningdatech.pmapi.meeting.entity.dto.AvoidRuleDTO; +import com.ningdatech.pmapi.meeting.entity.dto.ExpertChooseDTO; +import com.ningdatech.pmapi.meeting.entity.dto.InviteCacheDTO; +import com.ningdatech.pmapi.meeting.entity.dto.RandomInviteRuleDTO; +import com.ningdatech.pmapi.meeting.entity.enumeration.ExpertAttendStatusEnum; +import com.ningdatech.pmapi.meeting.helper.MeetingCallOrMsgHelper; +import com.ningdatech.pmapi.meeting.manage.ExpertInviteManage; +import com.ningdatech.pmapi.meeting.service.IExpertInviteAvoidRuleService; +import com.ningdatech.pmapi.meeting.service.IExpertInviteRuleService; +import com.ningdatech.pmapi.meeting.service.IMeetingExpertService; +import com.ningdatech.pmapi.meeting.service.IMeetingService; +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.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.stereotype.Component; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.util.Assert; + +import javax.annotation.PostConstruct; +import javax.annotation.Resource; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.atomic.AtomicInteger; + + +/** + *

+ * 专家随机邀请任务 + *

+ * + * @author WendyYang + * @since 21:25 2022/8/15 + */ +@Slf4j +@Component +@AllArgsConstructor +public class ExpertRandomInviteTask { + + private final RandomInviteProperties properties; + private static final String MEETING_ID_INVITE_RANDOM = "MEETING_ID_INVITE_RANDOM"; + + private static final Duration EXPIRE_TIME = Duration.ofDays(60); + + private final CachePlusOps cachePlusOps; + @Resource(name = "expertInviteScheduler") + private ThreadPoolTaskScheduler scheduler; + private final IMeetingExpertService meetingExpertService; + private final IExpertInviteRuleService inviteRuleService; + private final IMeetingService meetingService; + private final ExpertInviteManage expertInviteManage; + private final IExpertInviteAvoidRuleService inviteAvoidRuleService; + private final MeetingCallOrMsgHelper meetingCallOrMsgHelper; + + /** + * 用来存入线程执行句柄, 停止定时任务时使用 + */ + private static final ConcurrentMap> INVITE_TASK_MAP = new ConcurrentHashMap<>(); + + private ExpertRandomInviteTask currProxy() { + return (ExpertRandomInviteTask) AopContext.currentProxy(); + } + + private CacheHashKey getCacheKey(Long meetingId) { + String field = meetingId == null ? null : meetingId.toString(); + return new CacheHashKey(MEETING_ID_INVITE_RANDOM, field, EXPIRE_TIME); + } + + @PostConstruct + public void initTask() { + if (!properties.getEnable()) { + log.warn("随机邀请已关闭……"); + return; + } + initInviteTaskAfterStarted(); + } + + /** + * 项目重启之后重新初始化邀请任务 + */ + private void initInviteTaskAfterStarted() { + Map caches = cachePlusOps.hGetAll(getCacheKey(null)); + if (MapUtils.isEmpty(caches)) { + log.info("暂无需要初始化的抽取会议信息"); + return; + } + Integer inviteDelay = properties.getInviteDelay(); + for (InviteCacheDTO cache : caches.values()) { + Boolean reInvite = cache.getReInvite(); + LocalDateTime tsTime = cache.getTaskStartTime(); + boolean added = addInviteTask(cache.getMeetingId(), true, inviteDelay, reInvite, tsTime); + if (!added) { + cachePlusOps.hDel(getCacheKey(cache.getMeetingId())); + } + } + } + + /** + * 专家抽取校验(抽取数量是否不足) + * + * @param meetingId 会议ID + * @return boolean + * @author WendyYang + **/ + private boolean inviteCountCheck(Long meetingId) { + Map ruleMap = inviteRuleService.randomRuleByMeetingId(meetingId); + if (ruleMap.isEmpty()) { + return Boolean.FALSE; + } + LambdaQueryWrapper query = Wrappers.lambdaQuery(MeetingExpert.class) + .select(MeetingExpert::getRuleId, MeetingExpert::getStatus) + .in(MeetingExpert::getRuleId, ruleMap.keySet()) + .eq(MeetingExpert::getStatus, ExpertAttendStatusEnum.AGREED.getCode()); + List experts = meetingExpertService.list(query); + if (experts.isEmpty()) { + return Boolean.TRUE; + } + Map cntMap = CollUtils.groupCount(experts, MeetingExpert::getRuleId); + for (Map.Entry entry : ruleMap.entrySet()) { + Long agreeCnt = cntMap.getOrDefault(entry.getKey(), 0L); + if (agreeCnt < entry.getValue().getCount()) { + return Boolean.TRUE; + } + } + return Boolean.FALSE; + } + + /** + * 唤醒某个会议的抽取任务 + * + * @param meetingId 会议ID + * @param reInvite 是否可邀请已拒绝的专家 + * @author WendyYang + **/ + public void notifyInviteTask(Long meetingId, boolean... reInvite) { + boolean tmpReInvite = ArrayUtil.isEmpty(reInvite) || reInvite[0]; + if (!INVITE_TASK_MAP.containsKey(meetingId)) { + if (addInviteTask(meetingId, false, properties.getInviteDelay(), tmpReInvite, LocalDateTime.now())) { + log.info("重置会议的随机抽取状态:{}", meetingId); + LambdaUpdateWrapper update = Wrappers.lambdaUpdate(Meeting.class); + update.set(Meeting::getInviteStatus, false); + update.eq(Meeting::getId, meetingId); + meetingService.update(update); + InviteCacheDTO cacheVal = InviteCacheDTO.of(meetingId, tmpReInvite, LocalDateTime.now()); + cachePlusOps.hSet(getCacheKey(meetingId), cacheVal); + } + } + } + + /** + * 添加专家抽取校验任务 + * + * @param meetingId 会议ID + * @param checked 是否前置校验 + * @param delayTime 延迟执行时间 + * @param reInvite 是否可以邀请被拒绝的专家 + * @param tsTime 任务启动时间 + * @return 是否添加任务成功 + * @author WendyYang + **/ + private boolean addInviteTask(Long meetingId, boolean checked, int delayTime, boolean reInvite, LocalDateTime tsTime) { + if (checked && !inviteCountCheck(meetingId)) { + // 如果抽取数量满足直接返回 + return Boolean.FALSE; + } + Instant startTime = LocalDateTime.now().plusMinutes(delayTime).atZone(ZoneId.systemDefault()).toInstant(); + ScheduledFuture future = scheduler.scheduleAtFixedRate(() -> { + ExpertRandomInviteTask bean = SpringContextHolder.getBean(ExpertRandomInviteTask.class); + try { + bean.invite(meetingId, reInvite, tsTime); + } catch (Exception e) { + log.error("执行专家邀请任务异常:{}", meetingId, e); + } + }, startTime, Duration.ofMinutes(properties.getInviteFixedRate())); + INVITE_TASK_MAP.putIfAbsent(meetingId, future); + log.info("添加专家抽取后台任务:{}", meetingId); + return Boolean.TRUE; + } + + /** + * 创建会议时添加抽取任务 + * + * @param meetingId 会议ID + * @param tsTime 开始时间 + * @author WendyYang + **/ + public void addInviteTaskByMeetingCreate(Long meetingId, LocalDateTime tsTime) { + Assert.isTrue(properties.getEnable(), "随机邀请已关闭"); + addInviteTask(meetingId, false, properties.getInviteDelay(), false, tsTime); + InviteCacheDTO cacheVal = InviteCacheDTO.of(meetingId, false, tsTime); + cachePlusOps.hSet(getCacheKey(meetingId), cacheVal); + } + + /** + * 抽取过程 + * + * @param meetingId 会议ID + * @param reInvite 是否可以邀请已拒绝的专家 + * @param tsTime 任务开启时间 + */ + @Transactional(rollbackFor = Exception.class) + public void invite(Long meetingId, Boolean reInvite, LocalDateTime tsTime) { + log.info("开始进行专家后台抽取:{}", meetingId); + Meeting meeting = meetingService.getById(meetingId); + if (meeting.getStartTime().isBefore(LocalDateTime.now())) { + log.info("会议已开始停止抽取:{}", meeting); + cancelByMeetingId(meetingId); + return; + } + // 随机邀请规则 + Map ruleMap = inviteRuleService.randomRuleByMeetingId(meetingId); + // 回避规则 + AvoidRuleDTO avoidRule = inviteAvoidRuleService.getAvoidInfoDto(meetingId); + // 还需要抽取的规则数量 + AtomicInteger notIgnoreCnt = new AtomicInteger(ruleMap.size()); + AtomicInteger notSupportCnt = new AtomicInteger(0); + LocalDateTime msTime = meeting.getStartTime(); + LocalDateTime meTime = meeting.getStartTime(); + ruleMap.forEach((ruleId, value) -> { + List tmpExperts = meetingExpertService.listExpertLastByMeetingId(meetingId); + Map expertMap = CollUtils.listToMap(tmpExperts, MeetingExpert::getExpertId); + // 统计通知中与同意参加专家数量 + Map countMap = countByAttendStatus(expertMap); + ExpertCntBO cnt = countMap.getOrDefault(ruleId, ExpertCntBO.zeroInit()); + int wouldAttendCnt = cnt.getAgreeCnt() + cnt.getNoticeCnt(); + if (wouldAttendCnt == value.getCount()) { + if (cnt.getAgreeCnt().equals(value.getCount())) { + notIgnoreCnt.decrementAndGet(); + } + return; + } + int needInviteCnt = value.getCount() - wouldAttendCnt; + ExpertChooseDTO expertChoose = expertInviteManage.expertReplaceByRandomRule(avoidRule, value, + tmpExperts, needInviteCnt, msTime, meTime, tsTime, reInvite); + + if (expertChoose.getTotal() > 0) { + List expertMeetings = CollUtils.convert(expertChoose.getExperts(), w -> { + MeetingExpert expert = ExpertInviteBuilder.getExpertByRandom(meetingId, w, ruleId); + expert.setStatus(ExpertAttendStatusEnum.NOTICING.getCode()); + return expert; + }); + meetingCallOrMsgHelper.callExpertByMeeting(meeting, expertMeetings); + log.info("会议:{} 后台抽取专家:{}名", meetingId, expertMeetings.size()); + meetingExpertService.saveBatch(expertMeetings); + } else { + // 抽取人数不够 + notSupportCnt.incrementAndGet(); + } + }); + if (notIgnoreCnt.get() == 0 || notIgnoreCnt.get() == notSupportCnt.get()) { + log.info("停止会议随机邀请:{} 未完成抽取规则数量 {} 无可抽取专家规则数量 {}", meetingId, notIgnoreCnt, notSupportCnt); + currProxy().cancelByMeetingId(meetingId); + meetingCallOrMsgHelper.sendInviteStopMsg(meeting.getCreateBy(), meetingId, meeting.getName()); + } + } + + @Transactional(rollbackFor = Exception.class) + public void cancelByMeetingId(Long meetingId) { + log.info("终止专家抽取:{}", meetingId); + meetingService.stopRandomInvite(meetingId); + cachePlusOps.hDel(getCacheKey(meetingId)); + ScheduledFuture future = INVITE_TASK_MAP.get(meetingId); + if (future != null) { + INVITE_TASK_MAP.remove(meetingId); + if (!future.isCancelled()) { + future.cancel(true); + } + } + } + + //================================================================================================================== + + private Map countByAttendStatus(Map expertMap) { + Map cntMap = new HashMap<>(8); + expertMap.values().forEach(w -> { + Long ruleId = w.getRuleId(); + ExpertCntBO cnt = cntMap.computeIfAbsent(ruleId, k -> ExpertCntBO.zeroInit()); + if (ExpertAttendStatusEnum.AGREED.eq(w.getStatus())) { + cnt.incrAgreeCnt(); + } else if (ExpertAttendStatusEnum.NOTICING.eq(w.getStatus())) { + cnt.incrNoticeCnt(); + } + }); + return cntMap; + } + + @Data + public static class ExpertCntBO { + + private Integer agreeCnt; + + private Integer noticeCnt; + + public ExpertCntBO(Integer agreeCnt, Integer noticeCnt) { + this.agreeCnt = agreeCnt; + this.noticeCnt = noticeCnt; + } + + public void incrAgreeCnt() { + agreeCnt++; + } + + public void incrNoticeCnt() { + noticeCnt++; + } + + public static ExpertCntBO zeroInit() { + return new ExpertCntBO(0, 0); + } + + } + +}