diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/GenericMessageListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/GenericMessageListener.java index 50c1305..88ad185 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/GenericMessageListener.java +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/GenericMessageListener.java @@ -13,7 +13,7 @@ import java.util.Collections; import java.util.List; /** - * 【通用线上监听器】为即将到来的任务创建内部“我的消息”记录。 知无涯1 + * 【通用线上监听器】为即将到来的任务创建内部“我的消息”记录。 知无涯 *

* 该监听器被设计为在SequenceFlow的'take'事件上触发。 * 它会智能地从流程变量中读取单个接收人(变量名: approval)或多个接收人(变量名: userList), @@ -44,7 +44,7 @@ public class GenericMessageListener implements ExecutionListener { String taskName = execution.getCurrentFlowElement() != null ? execution.getCurrentFlowElement().getName() : "新"; // --- 关键改动:构建带有隐藏标识的消息内容 --- - String messageText = "您有一条新的【" + taskName + "】待办任务需要处理,"; + String messageText = "您有一条新的【" + taskName + "】待办任务需要处理"; // 我们嵌入一个隐藏的span,它包含了流程实例ID和活动ID作为唯一标识 String hiddenIdentifier = String.format("", execution.getProcessInstanceId(), diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/InitiatorNotificationListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/InitiatorNotificationListener.java new file mode 100644 index 0000000..c3e8ff7 --- /dev/null +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/InitiatorNotificationListener.java @@ -0,0 +1,121 @@ +package com.srs.flowable.listener.disciplinary; +import com.srs.common.utils.SecurityUtils; +import com.srs.common.utils.WeChatUtil; +import com.srs.comprehensive.domain.CphMsg; +import com.srs.comprehensive.service.ICphMsgService; +import com.srs.flowable.mapper.DisciplinaryMapper; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.SequenceFlow; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +/** + * 【线上专用监听器】在流程结果返回给发起人时, + * 负责发送企业微信和内部消息两种通知。 + */ +@Component("initiatorNotificationListener") // 使用一个全新的、明确的Bean名称 +@Slf4j +public class InitiatorNotificationListener implements ExecutionListener { + + @Autowired + private DisciplinaryMapper disciplinaryMapper; + + @Autowired + private ICphMsgService cphMsgService; + + @Autowired(required = false) // 允许WeChatUtil为null,增加灵活性 + private WeChatUtil weChatUtil; + + @Override + public void notify(DelegateExecution execution) { + log.info("流程实例 [{}]: 触发 InitiatorNotificationListener,准备向发起人发送整合通知...", execution.getProcessInstanceId()); + + // --- 1. 获取核心数据:发起人ID --- + Object initiatorObj = execution.getVariable("INITIATOR"); + if (initiatorObj == null) { + log.error("在流程实例 [{}] 中未找到发起人(INITIATOR)变量,无法发送任何通知。", execution.getProcessInstanceId()); + return; + } + + Long initiatorUserId; + try { + initiatorUserId = Long.parseLong(initiatorObj.toString()); + } catch (NumberFormatException e) { + log.error("发起人(INITIATOR)变量 '{}' 无法转换为Long类型。", initiatorObj, e); + return; + } + + // --- 2. 准备通知内容 (解决taskName为空的问题) --- + String nextTaskName = getNextTaskName(execution); + String weChatMessage = "您提交的学生违纪处分申请已有最终处理结果,请点击前往处理。"; + String internalMessageText = "您有一条新的【" + nextTaskName + "】待办任务需要处理。"; + String hiddenIdentifier = String.format("", + execution.getProcessInstanceId(), + ((SequenceFlow) execution.getCurrentFlowElement()).getTargetRef()); + String internalMessageContent = internalMessageText + "请点击前往处理。" + hiddenIdentifier; + + // --- 3. 发送企业微信通知 --- + sendWeChatNotification(initiatorUserId, weChatMessage); + + // --- 4. 创建内部消息 --- + createInternalMessage(initiatorUserId, internalMessageContent, execution); + } + + /** + * 发送企业微信通知 + */ + private void sendWeChatNotification(Long userId, String content) { + if (weChatUtil == null) { // 防御性编程 + log.warn("WeChatUtil未配置,跳过发送企业微信。"); + return; + } + try { + String userName = disciplinaryMapper.getUserNameByUserId(userId); + if (userName != null && !userName.isEmpty()) { + weChatUtil.sendTextMessage(userName, content); + log.info("已成功向流程发起人 (userId:{}) 发送企业微信通知。", userId); + } else { + log.warn("找到了发起人(userId:{}),但其企微账号为空,未发送企微通知。", userId); + } + } catch (Exception e) { + log.error("向流程发起人发送企业微信通知时异常: {}", e.getMessage(), e); + } + } + + /** + * 创建内部消息 + */ + private void createInternalMessage(Long receiverId, String content, DelegateExecution execution) { + try { + CphMsg cphMsg = new CphMsg(); + cphMsg.setReceiver(receiverId); + cphMsg.setContent(content); + // 安全地获取发送人,提供默认值 + cphMsg.setSender(SecurityUtils.getUserId() != null ? SecurityUtils.getUserId() : 1L); + + cphMsgService.insertCphMsg(cphMsg); + log.info("已成功为流程发起人 (userId:{}) 创建内部消息。", receiverId); + } catch (Exception e) { + log.error("为流程发起人创建内部消息时异常: {}", e.getMessage(), e); + } + } + + /** + * 安全地获取线上监听器下一个目标节点的名称 + */ + private String getNextTaskName(DelegateExecution execution) { + try { + SequenceFlow sequenceFlow = (SequenceFlow) execution.getCurrentFlowElement(); + FlowElement targetElement = sequenceFlow.getTargetFlowElement(); + if (targetElement != null) { + return targetElement.getName(); + } + } catch (Exception e) { + log.warn("获取下一个任务名称时出错: {}", e.getMessage()); + } + return "结果接收"; + } +} \ No newline at end of file diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/MultiInstanceMessageListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/MultiInstanceMessageListener.java new file mode 100644 index 0000000..475da2b --- /dev/null +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/MultiInstanceMessageListener.java @@ -0,0 +1,95 @@ +package com.srs.flowable.listener.disciplinary; + +import com.srs.common.utils.SecurityUtils; +import com.srs.comprehensive.domain.CphMsg; +import com.srs.comprehensive.service.ICphMsgService; +import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.SequenceFlow; +import org.flowable.engine.delegate.DelegateExecution; +import org.flowable.engine.delegate.ExecutionListener; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; + +import java.util.List; + +/** + * 【多实例专用线上监听器】为即将到来的多实例任务创建内部消息。 + *

+ * 它只从流程变量 `userList` 中读取接收人列表。 + *

+ */ +@Component("multiInstanceMessageListener") // 定义一个全新的Bean名称 +@Slf4j +public class MultiInstanceMessageListener implements ExecutionListener { + + @Autowired + private ICphMsgService cphMsgService; + + @Override + public void notify(DelegateExecution execution) { + log.info("流程实例 [{}]: 触发多实例专用消息监听器 (MultiInstanceMessageListener)...", execution.getProcessInstanceId()); + + // --- 步骤 1: 只从 'userList' 变量获取接收人 --- + Object userListObj = execution.getVariable("userList"); + if (!(userListObj instanceof List) || ((List) userListObj).isEmpty()) { + log.warn("流程实例 [{}]: 未能从 'userList' 变量中找到有效的接收人列表,跳过消息创建。", execution.getProcessInstanceId()); + return; + } + + @SuppressWarnings("unchecked") + List receiverIdList = (List) userListObj; + + // --- 步骤 2: (已修正) 精确获取下一个目标节点的名称和ID --- + FlowElement nextElement = getNextFlowElement(execution); + String taskName = (nextElement != null && nextElement.getName() != null) ? nextElement.getName() : "新的待办"; + String activityId = (nextElement != null) ? nextElement.getId() : "unknownActivity"; + + // --- 步骤 3: 准备消息内容 --- + String messageText = "您有一条新的【" + taskName + "】待办任务需要处理。"; + String link = "请点击前往处理。"; + String hiddenIdentifier = String.format("", + execution.getProcessInstanceId(), + activityId); + String finalContent = messageText + link + hiddenIdentifier; + + Long senderId = getSenderId(); + + // --- 步骤 4: 批量创建消息 --- + for (Long receiverId : receiverIdList) { + try { + CphMsg cphMsg = new CphMsg(); + cphMsg.setReceiver(receiverId); + cphMsg.setSender(senderId); + cphMsg.setContent(finalContent); + cphMsgService.insertCphMsg(cphMsg); + log.info("已成功为用户 [{}] 创建多实例任务的内部消息 (关联 Activity ID: {})。", receiverId, activityId); + } catch (Exception e) { + log.error("为用户 [{}] 创建多实例任务内部消息时发生异常: {}", receiverId, e.getMessage(), e); + } + } + } + + /** + * 安全地获取线上监听器的下一个目标节点 + */ + private FlowElement getNextFlowElement(DelegateExecution execution) { + if (execution.getCurrentFlowElement() instanceof SequenceFlow) { + return ((SequenceFlow) execution.getCurrentFlowElement()).getTargetFlowElement(); + } + return null; + } + + /** + * 安全地获取当前操作的发送人ID。 + */ + private Long getSenderId() { + try { + Long userId = SecurityUtils.getUserId(); + return userId != null ? userId : 1L; + } catch (Exception e) { + log.warn("在监听器中获取发送人ID时发生异常,使用默认系统管理员ID(1L)。"); + return 1L; + } + } +} \ No newline at end of file diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XSJYGLKListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XSJYGLKListener.java index 16faf40..adc1f54 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XSJYGLKListener.java +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XSJYGLKListener.java @@ -10,10 +10,11 @@ import org.springframework.stereotype.Component; import java.util.List; /** - * 学生教育管理科审核节点的执行监听器。 知无涯 - * 向“学生教育管理科”角色发送企业微信通知。 + * 【线上监听器-升级版】为“学生教育管理科审核”节点做准备。 + * 1. 向该候选组的所有成员发送企业微信通知。 + * 2. 将该组的所有成员ID列表存入 'userList' 流程变量,供下游监听器(GenericMessageListener)使用。 */ -@Component +@Component("xsjyglkListener") @Slf4j public class XSJYGLKListener implements ExecutionListener { @@ -21,39 +22,51 @@ public class XSJYGLKListener implements ExecutionListener { public void notify(DelegateExecution delegateExecution) { DisciplinaryMapper disciplinaryMapper = SpringUtils.getBean(DisciplinaryMapper.class); - // 1. 【核心】定义“学生教育管理科”xsjyglksh + // 1. 【核心】定义“学生教育管理科”角色Key final String TARGET_ROLE_KEY = "xsjyglksh"; - log.info("流程实例 [{}]: 准备向角色 '{}' 发送通知。", delegateExecution.getProcessInstanceId(), TARGET_ROLE_KEY); + log.info("流程实例 [{}]: 触发XSJYGLKListener, 准备向角色 '{}' 发送通知并设置流程变量。", delegateExecution.getProcessInstanceId(), TARGET_ROLE_KEY); - // 2. 根据这个固定的角色Key,查询该角色的所有成员ID(即使只有一个)。 + // 2. 根据角色Key,查询该角色的所有成员ID。 + // 这个查询结果既用于发送企微,也用于设置流程变量。 List userIdList = disciplinaryMapper.getApprovalByRoleKey(TARGET_ROLE_KEY); // 3. 检查是否找到了成员。 if (userIdList == null || userIdList.isEmpty()) { - log.error("根据角色Key '{}' 未找到任何用户,无法发送通知。", TARGET_ROLE_KEY); - return; // 中止执行,不抛出异常以免中断流程 + log.error("根据角色Key '{}' 未找到任何用户,无法发送通知和设置变量。", TARGET_ROLE_KEY); + return; // 中止执行 } - // 4. 发送企业微信通知。 - // (我们的批量发送逻辑即使只有一个用户也能完美处理) + // --- 关键新增逻辑:开始 --- + // 4. 将查询到的用户ID列表存入流程变量 'userList'。 + // 这是为下游的 GenericMessageListener 准备的标准“产品”。 + delegateExecution.setVariable("userList", userIdList); + log.info("已将角色 '{}' 的 {} 位成员ID存入 'userList' 流程变量,供下游监听器使用。", TARGET_ROLE_KEY, userIdList.size()); + // --- 关键新增逻辑:结束 --- + + // 5. (原有逻辑) 发送企业微信通知。 + sendWeChatNotification(userIdList, disciplinaryMapper, TARGET_ROLE_KEY); + } + + /** + * 封装的发送企业微信的私有方法,保持主方法 notify 的整洁。 + */ + private void sendWeChatNotification(List userIdList, DisciplinaryMapper disciplinaryMapper, String roleKey) { try { - // 批量查询userName (即使只有一个ID,这个方法也能正常工作) List userNameList = disciplinaryMapper.getUserNamesByUserIdList(userIdList); - if (!userNameList.isEmpty()) { - // 将列表拼接成 "username1|username2|..." 的格式 + if (userNameList != null && !userNameList.isEmpty()) { String toUser = String.join("|", userNameList); WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class); String content = "您有一条新的学生违纪审批任务待处理,请点击前往处理。"; weChatUtil.sendTextMessage(toUser, content); - log.info("已成功向角色 '{}' 的成员发送通知。接收人: {}", TARGET_ROLE_KEY, toUser); + log.info("已成功向角色 '{}' 的成员发送企业微信通知。接收人: {}", roleKey, toUser); } else { - log.warn("角色 '{}' 中找到了 {} 个用户,但无人配置企业微信账号,未发送任何通知。", TARGET_ROLE_KEY, userIdList.size()); + log.warn("角色 '{}' 中找到了 {} 个用户,但无人配置企业微信账号,未发送任何通知。", roleKey, userIdList.size()); } } catch (Exception e) { - log.error("向角色 '{}' 发送企业微信通知时出现异常。流程将继续。错误详情: {}", TARGET_ROLE_KEY, e.getMessage(), e); + log.error("向角色 '{}' 发送企业微信通知时出现异常。流程将继续。错误详情: {}", roleKey, e.getMessage(), e); } } } \ No newline at end of file diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XYWJCLWYHListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XYWJCLWYHListener.java index a318bed..4de3ea2 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XYWJCLWYHListener.java +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/disciplinary/XYWJCLWYHListener.java @@ -3,24 +3,26 @@ package com.srs.flowable.listener.disciplinary; import com.srs.common.utils.WeChatUtil; import com.srs.flowable.mapper.DisciplinaryMapper; import lombok.extern.slf4j.Slf4j; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.bpmn.model.SequenceFlow; import org.flowable.engine.delegate.DelegateExecution; import org.flowable.engine.delegate.ExecutionListener; -import org.mybatis.spring.annotation.MapperScan; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; import java.util.List; -import java.util.stream.Collectors; /** - * 【线上监听器】为“学院违纪处理委员会”多实例任务准备数据并发送通知 知无涯 + * 【生产者线上监听器】为“学院违纪处理委员会”多实例任务准备数据。 + * 1. 设置 'userList' 变量供多实例节点本身消费。 + * 2. 设置 'nextTaskNameForMessage' 变量供下游消息监听器消费。 + * 3. 发送企业微信通知。 */ -@MapperScan("com.srs.flowable.mapper") @Component("xywjclwyhListener") @Slf4j public class XYWJCLWYHListener implements ExecutionListener { - // 推荐:使用依赖注入,而不是SpringUtils @Autowired private DisciplinaryMapper disciplinaryMapper; @@ -29,46 +31,49 @@ public class XYWJCLWYHListener implements ExecutionListener { @Override public void notify(DelegateExecution execution) { - log.info("流程实例 [{}]: 在进入'学院违纪处理委员会'节点前,开始准备审批数据...", execution.getProcessInstanceId()); - - // 步骤 1: 单一数据库查询,获取所有需要的审批人信息 (ID和企微账号) - // List userInfos = disciplinaryMapper.getUserInfoByRoleKey("xywjclwyh"); - // 注意:如果您不方便修改Mapper,这里可以保持原来的两次查询,逻辑依然正确。 - // 我们先用原始的两次查询来演示,更具普适性。 + log.info("流程实例 [{}]: 触发 XYWJCLWYHListener,为'委员会'节点准备数据...", execution.getProcessInstanceId()); List userIdList = disciplinaryMapper.getApprovalByRoleKey("xywjclwyh"); - - if (userIdList == null || userIdList.isEmpty()) { + if (CollectionUtils.isEmpty(userIdList)) { log.error("角色 'xywjclwyh' 未配置或未找到任何有效用户,流程无法继续。"); throw new RuntimeException("学院违纪处理委员会审批人员未设置"); } - // 步骤 2: 设置多实例任务需要的流程变量 (这是“线上”为“节点”准备的核心数据) + // 步骤 1: 设置 'userList' 变量,供多实例UserTask节点消费 execution.setVariable("userList", userIdList); - log.info("流程实例 [{}]: 已成功设置'userList'变量,包含 {} 个审批人。", execution.getProcessInstanceId(), userIdList.size()); + log.info("已将'委员会'的 {} 位成员ID存入 'userList' 流程变量。", userIdList.size()); + + // 步骤 2: 主动获取并设置下一个节点的名称,供 GenericMessageListener 消费 + FlowElement nextElement = getNextFlowElement(execution); + if (nextElement != null) { + execution.setVariable("nextTaskNameForMessage", nextElement.getName()); + log.info("已将下一个任务的名称 '{}' 存入流程变量 'nextTaskNameForMessage'。", nextElement.getName()); + } // 步骤 3: 发送企业微信通知 sendWeChatNotification(userIdList, execution.getProcessInstanceId()); } - /** - * 封装并执行企业微信通知逻辑 - */ private void sendWeChatNotification(List userIdList, String processInstanceId) { try { List userNameList = disciplinaryMapper.getUserNamesByUserIdList(userIdList); - if (userNameList == null || userNameList.isEmpty()) { - log.warn("流程实例 [{}]: 角色'xywjclwyh'存在审批人ID,但无人配置企微账号,未发送通知。", processInstanceId); + if (CollectionUtils.isEmpty(userNameList)) { + log.warn("流程实例 [{}]: 角色'xywjclwyh'存在审批人ID,但无人配置企微账号。", processInstanceId); return; } - String toUser = String.join("|", userNameList); String content = "您有一条新的学生违纪审批任务待处理,请点击前往处理。"; - weChatUtil.sendTextMessage(toUser, content); - log.info("流程实例 [{}]: 已成功向学院违纪处理委员会发送通知。接收人: {}", processInstanceId, toUser); + log.info("流程实例 [{}]: 已成功向'委员会'发送企微通知。", processInstanceId); } catch (Exception e) { - log.error("流程实例 [{}]: 发送企微通知时出现异常,但不影响主流程。错误: {}", processInstanceId, e.getMessage(), e); + log.error("流程实例 [{}]: 发送企微通知时出现异常: {}", processInstanceId, e.getMessage(), e); } } + + private FlowElement getNextFlowElement(DelegateExecution execution) { + if (execution.getCurrentFlowElement() instanceof SequenceFlow) { + return ((SequenceFlow) execution.getCurrentFlowElement()).getTargetFlowElement(); + } + return null; + } } \ No newline at end of file diff --git a/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuDisciplinaryApplicationServiceImpl.java b/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuDisciplinaryApplicationServiceImpl.java index 95679a7..3a2053e 100644 --- a/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuDisciplinaryApplicationServiceImpl.java +++ b/srs-routine/src/main/java/com/srs/routine/service/impl/RtStuDisciplinaryApplicationServiceImpl.java @@ -265,7 +265,7 @@ public class RtStuDisciplinaryApplicationServiceImpl extends ServiceImpl