From 095aff71837ddcb4607a362c664861ea4dca97d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E9=99=88=E6=81=92=E6=88=90?= <962704835@qq.com> Date: Mon, 22 Dec 2025 17:39:08 +0800 Subject: [PATCH] =?UTF-8?q?=E5=85=A5=E4=BC=8D=E4=BF=9D=E7=95=99=E5=AD=A6?= =?UTF-8?q?=E7=B1=8D=E7=94=B3=E8=AF=B7=E3=80=81=E5=A4=96=E5=AE=BF=E7=94=B3?= =?UTF-8?q?=E8=AF=B7-=E4=B8=8A=E4=B8=80=E7=BA=A7=E5=AE=A1=E6=89=B9?= =?UTF-8?q?=E7=BB=93=E6=9D=9F=E4=B9=8B=E5=90=8E=EF=BC=8C=E7=BB=99=E4=B8=8B?= =?UTF-8?q?=E4=B8=80=E7=BA=A7=E5=8F=91=E9=80=81=E5=AE=A1=E6=89=B9=E6=8F=90?= =?UTF-8?q?=E9=86=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ApprovalAssigneeListener.java | 87 ++++++++++++++- .../StartApprovalAssigneeListener.java | 105 +++++++++++++++++- .../OutsideAccommodationEndListener.java | 86 +++++++++++++- .../OutsideAccommodationStartListener.java | 105 +++++++++++++++++- 4 files changed, 378 insertions(+), 5 deletions(-) diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/enlistmentReserve/ApprovalAssigneeListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/enlistmentReserve/ApprovalAssigneeListener.java index c2ddf88..a32f56e 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/listener/enlistmentReserve/ApprovalAssigneeListener.java +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/enlistmentReserve/ApprovalAssigneeListener.java @@ -5,12 +5,15 @@ import com.srs.common.core.domain.entity.SysUser; import com.srs.common.doman.vo.TeacherVo; import com.srs.common.utils.SecurityUtils; +import com.srs.common.utils.WeChatUtil; import com.srs.common.utils.spring.SpringUtils; import com.srs.flowable.domain.EnlistmentReserve; import com.srs.flowable.domain.EnlistmentReserveApproval; import com.srs.flowable.mapper.EnlistmentReserveApprovalMapper; import com.srs.flowable.mapper.EnlistmentReserveMapper; +import com.srs.flowable.mapper.RelieveMapper; import com.srs.system.service.ISysUserService; +import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.FlowNode; import org.flowable.engine.HistoryService; @@ -28,9 +31,10 @@ import java.util.List; /** * 审批流程负责人自动流转监听器 - * 用于节点审批通过时,添加审批记录 + * 用于节点审批通过时,添加审批记录 + 给学生发送企业微信通知 */ @Component("approvalAssigneeListener") // Spring Bean名称,与BPMN表达式对应 +@Slf4j public class ApprovalAssigneeListener implements ExecutionListener { @Autowired @@ -103,8 +107,89 @@ public class ApprovalAssigneeListener implements ExecutionListener { // 改变申请表中的审核状态(审核通过) enlistmentReserve.setApplyStatus(status); rtEnlistmentReserveMapper.updateRtEnlistmentReserve(enlistmentReserve); + // ========== 给学生发送入伍保留学籍审批通知 ========== + sendStudentWeChatNotification(execution, enlistmentReserve, currentNodeName, approvalResult, approvalOpinion); } + /** + * 给学生发送入伍保留学籍审批结果的企业微信通知 + */ + private void sendStudentWeChatNotification(DelegateExecution execution, EnlistmentReserve enlistmentReserve, + String currentNodeName, Long approvalResult, String approvalOpinion) { + try { + // 1. 获取需要的Mapper和工具类 + RelieveMapper relieveMapper = (RelieveMapper) SpringUtils.getBean("relieveMapper"); + WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class); + + // 2. 获取学生基础信息 + String studentNo = enlistmentReserve.getStudentNo(); + String studentName = enlistmentReserve.getStudentName(); + + // 3. 获取学生userId(优先从流程变量取) + Long stuUserId = null; + if (execution.hasVariable("stuUserId")) { + stuUserId = Long.valueOf(execution.getVariable("stuUserId").toString()); + } else { + // 兜底:通过申请表获取学生id + stuUserId = enlistmentReserve.getStudentId(); + } + + if (stuUserId == null) { + log.warn("⚠ 未找到学生(学号:{},姓名:{})对应的用户ID,无法发送通知", studentNo, studentName); + return; + } + + // 4. 查询学生的企业微信账号 + String stuUserName = relieveMapper.getUserNameByUserId(stuUserId); + + if (stuUserName != null && !stuUserName.isEmpty()) { + // 5. 拼接审批结果描述 + String approveResultDesc = getApprovalResultDesc(approvalResult, currentNodeName); + + // 6. 构造通知内容 + String content = String.format("您的入伍保留学籍申请有新的审批进展:%s(%s),%s,点此查看详情", + currentNodeName, + approveResultDesc, + (approvalOpinion != null && !approvalOpinion.isEmpty()) ? "审批意见:" + approvalOpinion : ""); + + // 7. 发送企业微信消息 + weChatUtil.sendTextMessage(stuUserName, content); + log.info("✅ 已成功向学生(姓名:{},学号:{},userName:{})发送入伍保留学籍审批通知。审批节点:{},结果:{}", + studentName, studentNo, stuUserName, currentNodeName, approveResultDesc); + + } else { + // 发送日志 + log.warn("⚠ 找到了学生(学号:{},姓名:{},userId:{}),但其对应的企业微信账号为空,无法发送通知。", + studentNo, studentName, stuUserId); + } + } catch (Exception e) { + // 异常处理:记录日志+不影响主流程 + String studentNo = enlistmentReserve.getStudentNo(); + log.error("❌ 向学生(学号:{})发送入伍保留学籍审批通知时出现异常,但流程将继续。错误详情:", studentNo, e); + } + } + + /** + * 转换审批结果为可读描述(适配入伍保留学籍审批状态) + */ + private String getApprovalResultDesc(Long approvalResult, String currentNodeName) { + if (approvalResult == null) { + return "审批已处理"; + } + // 匹配项目中实际的审批状态值(0=驳回/1=通过/2=审批中) + switch (approvalResult.intValue()) { + case 0: + return currentNodeName + "驳回"; + case 1: + return currentNodeName + "通过"; + case 2: + return "审批中"; + default: + return "审批已完成"; + } + } + + /** * 根据节点ID和流程定义ID,查询节点名称(兼容所有版本的核心方法) */ diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/enlistmentReserve/StartApprovalAssigneeListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/enlistmentReserve/StartApprovalAssigneeListener.java index ae55c20..9ed55d4 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/listener/enlistmentReserve/StartApprovalAssigneeListener.java +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/enlistmentReserve/StartApprovalAssigneeListener.java @@ -4,13 +4,18 @@ import com.srs.common.core.domain.entity.SysUser; import com.srs.common.doman.vo.TeacherVo; +import com.srs.common.utils.DateUtils; import com.srs.common.utils.SecurityUtils; +import com.srs.common.utils.WeChatUtil; import com.srs.common.utils.spring.SpringUtils; import com.srs.flowable.domain.EnlistmentReserve; import com.srs.flowable.domain.EnlistmentReserveApproval; +import com.srs.flowable.domain.NotificationManage; import com.srs.flowable.mapper.EnlistmentReserveApprovalMapper; import com.srs.flowable.mapper.EnlistmentReserveMapper; +import com.srs.flowable.mapper.LeaveMapper; import com.srs.system.service.ISysUserService; +import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.FlowNode; import org.flowable.engine.HistoryService; @@ -31,6 +36,7 @@ import java.util.List; * 用于节点审批之前时,更新下一个节点的负责人变量(approval) */ @Component("startApprovalAssigneeListener") // Spring Bean名称,与BPMN表达式对应 +@Slf4j public class StartApprovalAssigneeListener implements ExecutionListener { @Autowired @@ -46,7 +52,7 @@ public class StartApprovalAssigneeListener implements ExecutionListener { String currentActivityId = execution.getCurrentActivityId(); // 当前节点ID(兼容所有版本) String processDefinitionId = execution.getProcessDefinitionId(); // 流程定义ID - // 2. 根据节点ID查询节点名称(核心修正:通过流程定义获取名称) + // 2. 根据节点ID查询节点名称(通过流程定义获取名称) String currentNodeName = getNodeNameByActivityId(processDefinitionId, currentActivityId); if (currentNodeName == null) { throw new RuntimeException("未找到节点ID=" + currentActivityId + "的名称"); @@ -61,6 +67,8 @@ public class StartApprovalAssigneeListener implements ExecutionListener { if (nextAssigneeId != null) { execution.setVariable("approval", nextAssigneeId); execution.setVariable("currentNode", currentNodeName); + // ========== 发送通知逻辑 ========== + sendApprovalNotification(execution, currentNodeName, nextAssigneeId, enlistmentId); } } @@ -174,4 +182,99 @@ public class StartApprovalAssigneeListener implements ExecutionListener { throw new RuntimeException("未配置节点[" + currentNodeName + "]的当前负责人规则"); } } + + /** + * 发送审批通知(系统通知+企业微信通知) + */ + private void sendApprovalNotification(DelegateExecution execution, String currentNodeName, + Long nextAssigneeId, Long enlistmentId) { + try { + // 获取需要的Mapper和工具类 + EnlistmentReserveMapper enlistmentReserveMapper = (EnlistmentReserveMapper) SpringUtils.getBean(EnlistmentReserveMapper.class); + LeaveMapper leaveMapper = (LeaveMapper) SpringUtils.getBean("leaveMapper"); + WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class); + + // 1. 查询入伍保留学籍申请详情 + EnlistmentReserve enlistmentReserve = enlistmentReserveMapper.selectRtEnlistmentReserveById(enlistmentId); + if (enlistmentReserve == null) { + log.warn("未找到入伍保留学籍申请记录,ID:{}", enlistmentId); + return; + } + + // 2. 查询下一个审批人的信息(获取用户名/工号) + SysUser nextApprover = sysUserService.selectUserById(nextAssigneeId); + if (nextApprover == null) { + log.warn("未找到审批人信息,用户ID:{}", nextAssigneeId); + return; + } + String approverUserName = nextApprover.getUserName(); // 审批人工号/用户名 + String approverName = nextApprover.getNickName(); // 审批人姓名 + + log.info("开始发送【入伍保留学籍审批】通知,审批节点:{},审批人:{}({})", + currentNodeName, approverName, approverUserName); + + // 3. 处理系统通知(先删后加,避免重复通知) + NotificationManage notificationManage = new NotificationManage(); + notificationManage.setContent(String.format("您有一条【入伍保留学籍审批】待处理(节点:%s)", currentNodeName)); + notificationManage.setReceiver(nextAssigneeId); // 接收人:下一个审批人 + + // 3.1 查询是否已有相同通知,有则删除 + NotificationManage existNotify = leaveMapper.selectCphMsgListForFlowable(notificationManage); + if (existNotify != null) { + int delRes = leaveMapper.deleteCphMsgById(existNotify.getId()); + log.info("删除重复的系统通知,通知ID:{},删除结果:{}", existNotify.getId(), delRes); + } + + // 3.2 添加新的系统通知 + notificationManage.setSender(SecurityUtils.getUserId()); // 发送人:当前操作人 + notificationManage.setCreateTime(DateUtils.getNowDate()); // 创建时间 + int addRes = leaveMapper.insertCphMsg(notificationManage); + log.info("新增系统通知成功,接收人ID:{},添加结果:{}", nextAssigneeId, addRes); + + // 4. 企业微信推送消息 + if (approverUserName != null && !approverUserName.isEmpty()) { + // 构造带超链接的消息内容 + String weChatContent = String.format( + "您有一条【入伍保留学籍审批】待处理(节点:%s),请点击前往处理", + currentNodeName + ); + + // 发送企业微信文本消息 + weChatUtil.sendTextMessage(approverUserName, weChatContent); + log.info("企业微信通知发送成功,接收人工号:{},节点:{}", approverUserName, currentNodeName); + } else { + log.warn("审批人工号为空,无法发送企业微信通知,用户ID:{}", nextAssigneeId); + } + + // ========== 删除当前审批人的待处理通知 ========== + // 4.1 获取当前审批人ID(当前操作人=已完成审批的人) + Long currentApproverId = SecurityUtils.getUserId(); + if (currentApproverId == null) { + log.warn("当前审批人ID为空,无法删除其待处理通知"); + return; + } + + // 4.2 构造当前审批人的待处理通知查询条件 + NotificationManage currentNotifyQuery = new NotificationManage(); + // 内容匹配:当前节点的待处理通知(关键词+节点名称) + currentNotifyQuery.setContent(String.format("您有一条【入伍保留学籍审批】待处理(节点:%s)", currentNodeName)); + currentNotifyQuery.setReceiver(currentApproverId); // 接收人=当前审批人 + + // 4.3 查询当前审批人的该节点待处理通知 + NotificationManage currentExistNotify = leaveMapper.selectCphMsgListForFlowable(currentNotifyQuery); + if (currentExistNotify != null) { + // 4.4 删除该通知(清理已处理的待办) + int currentDelRes = leaveMapper.deleteCphMsgById(currentExistNotify.getId()); + log.info("删除当前审批人({})的【{}】待处理通知,通知ID:{},删除结果:{}", + currentApproverId, currentNodeName, currentExistNotify.getId(), currentDelRes); + } else { + log.info("当前审批人({})无【{}】节点的待处理通知,无需删除", currentApproverId, currentNodeName); + } + + } catch (Exception e) { + // 捕获所有异常,仅记录日志,不影响主流程 + log.error("发送入伍保留学籍审批通知失败,节点:{},审批人ID:{},错误信息:{}", + currentNodeName, nextAssigneeId, e.getMessage(), e); + } + } } \ No newline at end of file diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/outsideAccommodation/OutsideAccommodationEndListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/outsideAccommodation/OutsideAccommodationEndListener.java index 2613247..7f7dbe3 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/listener/outsideAccommodation/OutsideAccommodationEndListener.java +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/outsideAccommodation/OutsideAccommodationEndListener.java @@ -2,12 +2,15 @@ package com.srs.flowable.listener.outsideAccommodation; import com.srs.common.utils.SecurityUtils; +import com.srs.common.utils.WeChatUtil; import com.srs.common.utils.spring.SpringUtils; import com.srs.flowable.domain.DormitoryStudent; import com.srs.flowable.domain.OutsideAccommodationApply; import com.srs.flowable.domain.OutsideAccommodationApproval; import com.srs.flowable.mapper.OutsideAccommodationApplyMapper; import com.srs.flowable.mapper.OutsideAccommodationApprovalMapper; +import com.srs.flowable.mapper.RelieveMapper; +import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.FlowNode; import org.flowable.engine.RepositoryService; @@ -21,9 +24,10 @@ import java.util.Date; /** * 审批流程负责人自动流转监听器 - * 用于节点审批通过时,添加审批记录 + * 用于节点审批通过时,添加审批记录 + 给学生发送企业微信通知 */ @Component("OutsideAccommodationEndListener") // Spring Bean名称,与BPMN表达式对应 +@Slf4j public class OutsideAccommodationEndListener implements ExecutionListener { // 用于查询流程定义中的节点信息 @@ -36,7 +40,7 @@ public class OutsideAccommodationEndListener implements ExecutionListener { String currentActivityId = execution.getCurrentActivityId(); // 当前节点ID(兼容所有版本) String processDefinitionId = execution.getProcessDefinitionId(); // 流程定义ID - // 2. 根据节点ID查询节点名称(核心修正:通过流程定义获取名称) + // 2. 根据节点ID查询节点名称(通过流程定义获取名称) String currentNodeName = getNodeNameByActivityId(processDefinitionId, currentActivityId); if (currentNodeName == null) { throw new RuntimeException("未找到节点ID=" + currentActivityId + "的名称"); @@ -88,6 +92,84 @@ public class OutsideAccommodationEndListener implements ExecutionListener { } outsideAccommodationApply.setStatus(status); outsideAccommodationApplyMapper.updateDmsOutsideAccommodationApply(outsideAccommodationApply); + // ========== 给学生发送企业微信通知 ========== + sendStudentWeChatNotification(execution, outsideAccommodationApply, currentNodeName, approvalResult, approvalOpinion); + } + + /** + * 给学生发送外宿申请审批结果的企业微信通知 + */ + private void sendStudentWeChatNotification(DelegateExecution execution, OutsideAccommodationApply outsideAccommodationApply, + String currentNodeName, Long approvalResult, String approvalOpinion) { + try { + // 1. 获取需要的Mapper和工具类 + RelieveMapper relieveMapper = (RelieveMapper) SpringUtils.getBean("relieveMapper"); + WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class); + + // 2. 获取学生学号和姓名(从外宿申请对象中获取) + String studentNo = outsideAccommodationApply.getStudentNo(); + String studentName = outsideAccommodationApply.getStudentName(); + + // 3. 先尝试从流程变量获取学生userId + Long stuUserId = null; + if (execution.hasVariable("stuUserId")) { + stuUserId = Long.valueOf(execution.getVariable("stuUserId").toString()); + } else { + // 补充逻辑:如果流程变量中没有stuUserId,通过申请表获取 + stuUserId = outsideAccommodationApply.getStudentId(); + } + + if (stuUserId == null) { + log.warn("⚠ 未找到学生(学号:{},姓名:{})对应的用户ID,无法发送通知", studentNo, studentName); + return; + } + + // 4. 使用userId查询对应的企业微信账号 + String stuUserName = relieveMapper.getUserNameByUserId(stuUserId); + + if (stuUserName != null && !stuUserName.isEmpty()) { + // 5. 拼接审批结果提示信息(适配外宿申请场景) + String approveResultDesc = getApprovalResultDesc(approvalResult, currentNodeName); + + // 6. 拼接最终通知内容 + String content = String.format("您的外宿申请有新的审批进展:%s(%s),%s,点此查看详情", + currentNodeName, + approveResultDesc, + (approvalOpinion != null && !approvalOpinion.isEmpty()) ? "审批意见:" + approvalOpinion : ""); + + // 7. 发送企业微信消息 + weChatUtil.sendTextMessage(stuUserName, content); + log.info("✅ 已成功向学生(姓名:{},学号:{},userName:{})发送外宿申请审批通知。审批节点:{},结果:{}", + studentName, studentNo, stuUserName, currentNodeName, approveResultDesc); + + } else { + log.warn("⚠ 找到了学生(学号:{},姓名:{},userId:{}),但其对应的企业微信账号为空,无法发送通知。", + studentNo, studentName, stuUserId); + } + } catch (Exception e) { + // 捕获所有异常,仅记录日志,不影响主流程 + String studentNo = outsideAccommodationApply.getStudentNo(); + log.error("❌ 向学生(学号:{})发送外宿申请审批通知时出现异常,但流程将继续。错误详情:", studentNo, e); + } + } + + /** + * 转换审批结果为可读描述(适配外宿申请的审批状态) + */ + private String getApprovalResultDesc(Long approvalResult, String currentNodeName) { + if (approvalResult == null) { + return "审批已处理"; + } + switch (approvalResult.intValue()) { + case 0: + return currentNodeName + "驳回"; + case 1: + return currentNodeName + "通过"; + case 2: + return "审批中"; + default: + return "审批已完成"; + } } /** diff --git a/srs-flowable/src/main/java/com/srs/flowable/listener/outsideAccommodation/OutsideAccommodationStartListener.java b/srs-flowable/src/main/java/com/srs/flowable/listener/outsideAccommodation/OutsideAccommodationStartListener.java index 557c172..2aebd5b 100644 --- a/srs-flowable/src/main/java/com/srs/flowable/listener/outsideAccommodation/OutsideAccommodationStartListener.java +++ b/srs-flowable/src/main/java/com/srs/flowable/listener/outsideAccommodation/OutsideAccommodationStartListener.java @@ -2,13 +2,19 @@ package com.srs.flowable.listener.outsideAccommodation; import com.srs.common.core.domain.entity.SysUser; import com.srs.common.doman.vo.TeacherVo; +import com.srs.common.utils.DateUtils; +import com.srs.common.utils.SecurityUtils; +import com.srs.common.utils.WeChatUtil; import com.srs.common.utils.spring.SpringUtils; import com.srs.dormitory.domain.DmsOutsideAccommodationApply; import com.srs.flowable.domain.EnlistmentReserve; +import com.srs.flowable.domain.NotificationManage; import com.srs.flowable.domain.OutsideAccommodationApply; import com.srs.flowable.mapper.EnlistmentReserveMapper; +import com.srs.flowable.mapper.LeaveMapper; import com.srs.flowable.mapper.OutsideAccommodationApplyMapper; import com.srs.system.service.ISysUserService; +import lombok.extern.slf4j.Slf4j; import org.flowable.bpmn.model.BpmnModel; import org.flowable.bpmn.model.FlowNode; import org.flowable.engine.RepositoryService; @@ -25,6 +31,7 @@ import java.util.List; * 用于节点审批之前时,更新下一个节点的负责人变量(approval) */ @Component("OutsideAccommodationStartListener") // Spring Bean名称,与BPMN表达式对应 +@Slf4j public class OutsideAccommodationStartListener implements ExecutionListener { @Autowired @@ -40,7 +47,7 @@ public class OutsideAccommodationStartListener implements ExecutionListener { String currentActivityId = execution.getCurrentActivityId(); // 当前节点ID(兼容所有版本) String processDefinitionId = execution.getProcessDefinitionId(); // 流程定义ID - // 2. 根据节点ID查询节点名称(核心修正:通过流程定义获取名称) + // 2. 根据节点ID查询节点名称(通过流程定义获取名称) String currentNodeName = getNodeNameByActivityId(processDefinitionId, currentActivityId); if (currentNodeName == null) { throw new RuntimeException("未找到节点ID=" + currentActivityId + "的名称"); @@ -55,6 +62,8 @@ public class OutsideAccommodationStartListener implements ExecutionListener { if (nextAssigneeId != null) { execution.setVariable("approval", nextAssigneeId); execution.setVariable("currentNode", currentNodeName); + // ========== 外宿申请审批通知逻辑 ========== + sendOutsideAccommodationNotification(execution, currentNodeName, nextAssigneeId, accommodationId); } } @@ -168,4 +177,98 @@ public class OutsideAccommodationStartListener implements ExecutionListener { throw new RuntimeException("未配置节点[" + currentNodeName + "]的当前负责人规则"); } } + /** + * 发送外宿申请审批通知(系统通知+企业微信通知) + */ + private void sendOutsideAccommodationNotification(DelegateExecution execution, String currentNodeName, + Long nextAssigneeId, Long accommodationId) { + try { + // 获取需要的Mapper和工具类 + OutsideAccommodationApplyMapper outsideAccommodationApplyMapper = SpringUtils.getBean(OutsideAccommodationApplyMapper.class); + LeaveMapper leaveMapper = (LeaveMapper) SpringUtils.getBean("leaveMapper"); + WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class); + + // 1. 查询外宿申请详情 + com.srs.flowable.domain.OutsideAccommodationApply outsideAccommodationApply = outsideAccommodationApplyMapper.selectDmsOutsideAccommodationApplyById(accommodationId); + if (outsideAccommodationApply == null) { + log.warn("未找到外宿申请记录,ID:{}", accommodationId); + return; + } + + // 2. 查询下一个审批人的信息(获取用户名/工号) + SysUser nextApprover = sysUserService.selectUserById(nextAssigneeId); + if (nextApprover == null) { + log.warn("未找到审批人信息,用户ID:{}", nextAssigneeId); + return; + } + String approverUserName = nextApprover.getUserName(); // 审批人工号/用户名 + String approverName = nextApprover.getNickName(); // 审批人姓名 + + log.info("开始发送【外宿申请审批】通知,审批节点:{},审批人:{}({})", + currentNodeName, approverName, approverUserName); + + // 3. 处理系统通知(先删后加,避免重复通知) + NotificationManage notificationManage = new NotificationManage(); + notificationManage.setContent(String.format("您有一条【外宿申请审批】待处理(节点:%s)", currentNodeName)); + notificationManage.setReceiver(nextAssigneeId); // 接收人:下一个审批人 + + // 3.1 查询是否已有相同通知,有则删除 + NotificationManage existNotify = leaveMapper.selectCphMsgListForFlowable(notificationManage); + if (existNotify != null) { + int delRes = leaveMapper.deleteCphMsgById(existNotify.getId()); + log.info("删除重复的系统通知,通知ID:{},删除结果:{}", existNotify.getId(), delRes); + } + + // 3.2 添加新的系统通知 + notificationManage.setSender(SecurityUtils.getUserId() != null ? SecurityUtils.getUserId() : 0L); // 发送人:当前操作人(兜底处理) + notificationManage.setCreateTime(DateUtils.getNowDate()); // 创建时间 + int addRes = leaveMapper.insertCphMsg(notificationManage); + log.info("新增系统通知成功,接收人ID:{},添加结果:{}", nextAssigneeId, addRes); + + // 4. 企业微信推送消息 + if (approverUserName != null && !approverUserName.isEmpty()) { + // 构造带超链接的消息内容 + String weChatContent = String.format( + "您有一条【外宿申请审批】待处理(节点:%s),请点击前往处理", + currentNodeName + ); + + // 发送企业微信文本消息 + weChatUtil.sendTextMessage(approverUserName, weChatContent); + log.info("企业微信通知发送成功,接收人工号:{},节点:{}", approverUserName, currentNodeName); + } else { + log.warn("审批人工号为空,无法发送企业微信通知,用户ID:{}", nextAssigneeId); + } + + // ========== 删除当前审批人的待处理通知 ========== + // 4.1 获取当前审批人ID(当前操作人=已完成审批的人) + Long currentApproverId = SecurityUtils.getUserId(); + if (currentApproverId == null) { + log.warn("当前审批人ID为空,无法删除其待处理通知"); + return; + } + + // 4.2 构造当前审批人的待处理通知查询条件 + NotificationManage currentNotifyQuery = new NotificationManage(); + // 内容匹配:当前节点的待处理通知(关键词+节点名称) + currentNotifyQuery.setContent(String.format("您有一条【外宿申请审批】待处理(节点:%s)", currentNodeName)); + currentNotifyQuery.setReceiver(currentApproverId); // 接收人=当前审批人 + + // 4.3 查询当前审批人的该节点待处理通知 + NotificationManage currentExistNotify = leaveMapper.selectCphMsgListForFlowable(currentNotifyQuery); + if (currentExistNotify != null) { + // 4.4 删除该通知(清理已处理的待办) + int currentDelRes = leaveMapper.deleteCphMsgById(currentExistNotify.getId()); + log.info("删除当前审批人({})的【{}】待处理通知,通知ID:{},删除结果:{}", + currentApproverId, currentNodeName, currentExistNotify.getId(), currentDelRes); + } else { + log.info("当前审批人({})无【{}】节点的待处理通知,无需删除", currentApproverId, currentNodeName); + } + + } catch (Exception e) { + // 捕获所有异常,仅记录日志,不影响主流程 + log.error("发送外宿申请审批通知失败,节点:{},审批人ID:{},错误信息:{}", + currentNodeName, nextAssigneeId, e.getMessage(), e); + } + } } \ No newline at end of file