Compare commits

...

2 Commits

Author SHA1 Message Date
f7d5d45372 Merge remote-tracking branch 'origin/main' 2025-08-27 17:35:37 +08:00
59749c51d8 结束 2025-08-27 17:34:29 +08:00
10 changed files with 254 additions and 76 deletions

View File

@@ -1,55 +1,106 @@
package com.srs.flowable.listener.disciplinary;
import com.srs.common.utils.SecurityUtils;
import com.srs.common.utils.WeChatUtil;
import com.srs.common.utils.spring.SpringUtils;
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.stereotype.Component;
import java.util.List;
/**
* 执行监听器:在“学生教育管理科审批归档”节点开始时, 知无涯
* 向负责归档的角色发送企业微信通知。
* “学生教育管理科审批归档”节点做准备 知无涯
* <p>
* 它的职责包括:
* 1. 主动清理上一个节点残留的变量。
* 2. 查询“学生教育管理科归档”组的成员。
* 3. 向所有成员发送企业微信通知。
* 4. 为所有成员创建内部消息。
* </p>
*/
@Component
@Component("archivingNotifyListener") // 明确指定Bean名称
@Slf4j
public class ArchivingNotifyListener implements ExecutionListener {
@Override
public void notify(DelegateExecution delegateExecution) {
log.info("流程实例 [{}]: 已进入学生教育管理科审批归档节点,准备发送通知。", delegateExecution.getProcessInstanceId());
public void notify(DelegateExecution execution) {
log.info("流程实例 [{}]: 触发 ArchivingNotifyListener (一体化),为'学生教育管理科审批归档'节点发送所有通知...", execution.getProcessInstanceId());
// --- 步骤 1: 主动清理上一个节点残留的流程变量 ---
if (execution.hasVariable("approval")) {
execution.removeVariable("approval");
log.info("已清理上一个节点残留的 'approval' 变量。");
}
if (execution.hasVariable("userList")) {
execution.removeVariable("userList");
log.info("已清理上一个节点残留的 'userList' 变量。");
}
try {
DisciplinaryMapper disciplinaryMapper = SpringUtils.getBean(DisciplinaryMapper.class);
ICphMsgService cphMsgService = SpringUtils.getBean(ICphMsgService.class);
WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class);
// 1. 【核心】定义负责归档的角色的 role_key。
// --- 步骤 2: 查询“学生教育管理科归档”组的成员 ---
final String ARCHIVING_ROLE_KEY = "xsjyglkspgd";
// 2. 根据角色Key查询用户ID列表
List<Long> userIdList = disciplinaryMapper.getApprovalByRoleKey(ARCHIVING_ROLE_KEY);
if (userIdList == null || userIdList.isEmpty()) {
log.warn("根据角色Key '{}' 未找到任何归档人员,无法发送通知。", ARCHIVING_ROLE_KEY);
log.warn("根据角色Key '{}' 未找到任何归档人员,无法发送任何通知。", ARCHIVING_ROLE_KEY);
return;
}
// 3. 批量查询 userName 并发送通知
// --- 步骤 3: 发送企业微信通知 (批量) ---
List<String> userNameList = disciplinaryMapper.getUserNamesByUserIdList(userIdList);
if (!userNameList.isEmpty()) {
if (userNameList != null && !userNameList.isEmpty()) {
String toUser = String.join("|", userNameList);
WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class);
String content = "您有一条新的学生违纪归档任务待处理,<a href='http://zhxg.gxsdxy.cn/web/#/pages/Approval/index'>请点击前往处理</a>。";
weChatUtil.sendTextMessage(toUser, content);
log.info("已成功向归档角色 ({}) 发送通知。接收人: {}", ARCHIVING_ROLE_KEY, toUser);
String weChatMessage = "您有一条新的学生违纪归档任务待处理,<a href='http://zhxg.gxsdxy.cn/web/#/pages/Approval/index'>请点击前往处理</a>。";
weChatUtil.sendTextMessage(toUser, weChatMessage);
log.info("已成功向归档角色 ({}) 发送企业微信通知。", ARCHIVING_ROLE_KEY);
} else {
log.warn("角色 '{}' 中找到了 {} 个用户,但无人配置企业微信账号,未发送任何通知。", ARCHIVING_ROLE_KEY, userIdList.size());
}
// --- 步骤 4: 创建内部消息 (逐条) ---
FlowElement nextElement = getNextFlowElement(execution);
String taskName = (nextElement != null && nextElement.getName() != null) ? nextElement.getName() : "学生教育管理科审批归档";
String activityId = (nextElement != null) ? nextElement.getId() : "Activity_1agoizv"; // 节点的ID作为备用
String messageText = "您有一条新的【" + taskName + "】待办任务需要处理。";
String link = "<a href='http://zhxg.gxsdxy.cn/web/#/pages/Approval/index'>请点击前往处理</a>。";
String hiddenIdentifier = String.format("<span id='flow-identifier' data-proc-inst-id='%s' data-activity-id='%s' style='display:none;'></span>",
execution.getProcessInstanceId(), activityId);
String internalMessageContent = messageText + link + hiddenIdentifier;
Long senderId = SecurityUtils.getUserId() != null ? SecurityUtils.getUserId() : 1L;
for (Long receiverId : userIdList) {
CphMsg cphMsg = new CphMsg();
cphMsg.setReceiver(receiverId);
cphMsg.setSender(senderId);
cphMsg.setContent(internalMessageContent);
cphMsgService.insertCphMsg(cphMsg);
log.info("已成功为'学生教育管理科归档'成员 (userId:{}) 创建内部消息。", receiverId);
}
} catch (Exception e) {
log.error("向归档角色发送企业微信通知时出现异常。流程将继续。错误详情: {}", e.getMessage(), e);
log.error("在 ArchivingNotifyListener 中发送通知时出现异常。错误详情: {}", e.getMessage(), e);
}
}
/**
* 安全地获取线上监听器的下一个目标节点
*/
private FlowElement getNextFlowElement(DelegateExecution execution) {
if (execution.getCurrentFlowElement() instanceof SequenceFlow) {
return ((SequenceFlow) execution.getCurrentFlowElement()).getTargetFlowElement();
}
return null;
}
}

View File

@@ -13,7 +13,7 @@ import java.util.Collections;
import java.util.List;
/**
* 【通用线上监听器】为即将到来的任务创建内部“我的消息”记录。 知无涯
* 为即将到来的任务创建内部“我的消息”记录。 知无涯
* <p>
* 该监听器被设计为在SequenceFlow的'take'事件上触发。
* 它会智能地从流程变量中读取单个接收人(变量名: approval)或多个接收人(变量名: userList)

View File

@@ -13,7 +13,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 【线上专用监听器】在流程结果返回给发起人时,
* 在流程结果返回给发起人时, 知无涯
* 负责发送企业微信和内部消息两种通知。
*/
@Component("initiatorNotificationListener") // 使用一个全新的、明确的Bean名称

View File

@@ -13,7 +13,7 @@ import javax.annotation.PostConstruct;
import java.util.List;
/**
* 【任务监听器】在任务完成后,删除内部消息。
* 【任务监听器】在任务完成后,删除内部消息。 知无涯
* <p>
* 该监听器被设计为在UserTask的'complete'事件上触发。
* 它通过解析消息内容中的隐藏HTML标识来找到与当前已完成任务关联的消息

View File

@@ -14,7 +14,7 @@ import org.springframework.stereotype.Component;
import java.util.List;
/**
* 【多实例专用线上监听器】为即将到来的多实例任务创建内部消息。
* 为即将到来的多实例任务创建内部消息。 知无涯
* <p>
* 它只从流程变量 `userList` 中读取接收人列表。
* </p>

View File

@@ -1,55 +1,104 @@
package com.srs.flowable.listener.disciplinary;
import com.srs.common.utils.SecurityUtils;
import com.srs.common.utils.WeChatUtil;
import com.srs.common.utils.spring.SpringUtils;
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.stereotype.Component;
import java.util.List;
/**
* 执行监听器:在“校领导审批”节点开始时,向校领导发送企业微信通知。 知无涯
* (简化版:因为流程能到达此节点,即表示前序节点已全票通过)
* “校领导审批”节点做准备 知无涯
* <p>
* 它的职责包括:
* 1. 主动清理上一个节点残留的变量。
* 2. 查询校领导组成员。
* 3. 向所有成员发送企业微信通知。
* 4. 为所有成员创建内部消息。
* </p>
*/
@Component
@Component("schoolLeaderApprovalListener")
@Slf4j
public class SchoolLeaderApprovalListener implements ExecutionListener {
@Override
public void notify(DelegateExecution delegateExecution) {
log.info("流程实例 [{}]: 已进入校领导审批节点,准备发送通知。", delegateExecution.getProcessInstanceId());
public void notify(DelegateExecution execution) {
log.info("流程实例 [{}]: 触发 SchoolLeaderApprovalListener (一体化),为'校领导审批'节点发送所有通知...", execution.getProcessInstanceId());
// --- 步骤 1: 主动清理上一个节点残留的流程变量 ---
if (execution.hasVariable("approval")) {
execution.removeVariable("approval");
log.info("已清理上一个节点残留的 'approval' 变量。");
}
if (execution.hasVariable("userList")) {
execution.removeVariable("userList");
log.info("已清理上一个节点残留的 'userList' 变量。");
}
try {
DisciplinaryMapper disciplinaryMapper = SpringUtils.getBean(DisciplinaryMapper.class);
ICphMsgService cphMsgService = SpringUtils.getBean(ICphMsgService.class);
WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class);
// 1. 【核心】定义“校领导”这个角色的 role_key
// --- 步骤 2: 查询校领导组成员 ---
final String LEADER_ROLE_KEY = "xldsp";
// 2. 根据角色Key查询用户ID列表
List<Long> userIdList = disciplinaryMapper.getApprovalByRoleKey(LEADER_ROLE_KEY);
if (userIdList == null || userIdList.isEmpty()) {
log.warn("根据角色Key '{}' 未找到任何校领导用户,无法发送通知。", LEADER_ROLE_KEY);
log.warn("根据角色Key '{}' 未找到任何校领导用户,无法发送任何通知。", LEADER_ROLE_KEY);
return;
}
// 3. 批量查询 userName 并发送通知
// --- 步骤 3: 发送企业微信通知 (批量) ---
List<String> userNameList = disciplinaryMapper.getUserNamesByUserIdList(userIdList);
if (!userNameList.isEmpty()) {
if (userNameList != null && !userNameList.isEmpty()) {
String toUser = String.join("|", userNameList);
WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class);
String content = "校领导您有一条新的学生违纪审批任务待处理,<a href='http://zhxg.gxsdxy.cn/web/#/pages/Approval/index'>请点击前往处理</a>。";
String weChatMessage = "校领导您有一条新的学生违纪审批任务待处理,<a href='http://zhxg.gxsdxy.cn/web/#/pages/Approval/index'>请点击前往处理</a>。";
weChatUtil.sendTextMessage(toUser, weChatMessage);
log.info("已成功向校领导 (角色: {}) 发送企业微信通知。", LEADER_ROLE_KEY);
}
weChatUtil.sendTextMessage(toUser, content);
log.info("已成功向校领导 (角色: {}) 发送通知。接收人: {}", LEADER_ROLE_KEY, toUser);
} else {
log.warn("角色 '{}' 中找到了 {} 个用户,但无人配置企业微信账号,未发送任何通知。", LEADER_ROLE_KEY, userIdList.size());
// --- 步骤 4: (关键) 创建内部消息 (逐条) ---
FlowElement nextElement = getNextFlowElement(execution);
String taskName = (nextElement != null && nextElement.getName() != null) ? nextElement.getName() : "校领导审批";
String activityId = (nextElement != null) ? nextElement.getId() : "Activity_0ftj9eo";
String messageText = "您有一条新的【" + taskName + "】待办任务需要处理。";
String link = "<a href='http://zhxg.gxsdxy.cn/web/#/pages/Approval/index'>请点击前往处理</a>。";
String hiddenIdentifier = String.format("<span id='flow-identifier' data-proc-inst-id='%s' data-activity-id='%s' style='display:none;'></span>",
execution.getProcessInstanceId(), activityId);
String internalMessageContent = messageText + link + hiddenIdentifier;
Long senderId = SecurityUtils.getUserId() != null ? SecurityUtils.getUserId() : 1L;
for (Long receiverId : userIdList) {
CphMsg cphMsg = new CphMsg();
cphMsg.setReceiver(receiverId);
cphMsg.setSender(senderId);
cphMsg.setContent(internalMessageContent);
cphMsgService.insertCphMsg(cphMsg);
log.info("已成功为校领导 (userId:{}) 创建内部消息。", receiverId);
}
} catch (Exception e) {
log.error("向校领导发送企业微信通知时出现异常。流程将继续。错误详情: {}", e.getMessage(), e);
log.error("在 SchoolLeaderApprovalListener 中发送通知时出现异常。错误详情: {}", e.getMessage(), e);
}
}
/**
* 安全地获取线上监听器的下一个目标节点
*/
private FlowElement getNextFlowElement(DelegateExecution execution) {
if (execution.getCurrentFlowElement() instanceof SequenceFlow) {
return ((SequenceFlow) execution.getCurrentFlowElement()).getTargetFlowElement();
}
return null;
}
}

View File

@@ -1,59 +1,137 @@
package com.srs.flowable.listener.disciplinary;
import com.srs.common.utils.SecurityUtils;
import com.srs.common.utils.WeChatUtil;
import com.srs.common.utils.spring.SpringUtils;
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;
import org.springframework.util.StringUtils;
import org.springframework.util.Assert;
import javax.annotation.PostConstruct;
/**
* 流程结束监听器。
* 在流程的最终节点,向当事学生发送处理结果的通知。 知无涯
* 为“学生接收”节点做准备并发送所有通知。 知无涯
* <p>
* 它的核心职责是:
* 1. 从流程变量 'stuId' 中获取学生ID。
* 2. 将学生ID设置到 'approval' 流程变量中供下一个UserTask分配办理人。
* 3. 向该学生发送企业微信通知。
* 4. 为该学生创建内部消息,并确保任务名正确。
* </p>
*/
@Component
@Component("stuInfoListener") // 保持Bean名称不变
@Slf4j
public class StuInfoListener implements ExecutionListener {
@Autowired
private DisciplinaryMapper disciplinaryMapper;
@Autowired
private ICphMsgService cphMsgService;
@Autowired(required = false)
private WeChatUtil weChatUtil;
@PostConstruct
public void checkDependencies() {
Assert.notNull(disciplinaryMapper, "DisciplinaryMapper in StuInfoListener is not injected!");
Assert.notNull(cphMsgService, "ICphMsgService in StuInfoListener is not injected!");
}
@Override
public void notify(DelegateExecution delegateExecution) {
// 1. 从流程变量中获取学生ID
Object stuIdObj = delegateExecution.getVariable("stuId");
public void notify(DelegateExecution execution) {
log.info("流程实例 [{}]: 触发 StuInfoListener (一体化版),为'学生接收'节点准备数据并发送通知...", execution.getProcessInstanceId());
// 安全检查确保stuId存在且类型正确
if (!(stuIdObj instanceof Long)) {
log.error("流程实例 [{}] 在结束时未能获取到有效的 'stuId' 变量,无法发送最终通知。", delegateExecution.getProcessInstanceId());
return;
// --- 步骤 1: 安全地获取学生ID ---
Object stuIdObj = execution.getVariable("stuId");
if (stuIdObj == null) {
throw new IllegalStateException("核心流程变量 'stuId' 未找到。");
}
Long stuId = (Long) stuIdObj;
log.info("流程实例 [{}] 即将结束准备向学生ID: {} 发送最终通知。", delegateExecution.getProcessInstanceId(), stuId);
// 2. 向该学生发送企业微信通知
Long stuId;
try {
DisciplinaryMapper disciplinaryMapper = SpringUtils.getBean(DisciplinaryMapper.class);
stuId = Long.parseLong(stuIdObj.toString());
} catch (NumberFormatException e) {
throw new IllegalStateException("流程变量 'stuId' 类型错误。");
}
// 根据学生ID查询其对应的企业微信账号(userName)
String userName = disciplinaryMapper.getUserNameByUserId(stuId);
// --- 步骤 2: (核心) 设置下一个任务的办理人 ---
execution.setVariable("approval", stuId);
log.info("已成功将下一个任务的办理人('approval')设置为学生ID: {}", stuId);
// 检查是否找到了对应的企业微信账号
if (!StringUtils.hasText(userName)) {
log.warn("流程实例 [{}]: 根据学生ID '{}' 未找到对应的企业微信账号,无法发送最终通知。", delegateExecution.getProcessInstanceId(), stuId);
return; // 中止发送逻辑
// --- 步骤 3: (可选) 发送企业微信通知 ---
sendWeChatNotification(stuId);
// --- 步骤 4: (关键新增) 创建内部消息,并确保任务名正确 ---
createInternalMessage(stuId, execution);
}
/**
* 发送企业微信通知的私有方法
*/
private void sendWeChatNotification(Long stuId) {
if (weChatUtil != null) {
try {
String studentUserName = disciplinaryMapper.getUserNameByUserId(stuId);
if (studentUserName != null && !studentUserName.isEmpty()) {
String content = "同学你好,关于你的一项违纪处分申请已有最终结果,请登录系统在'我的待办'中查看并确认。";
weChatUtil.sendTextMessage(studentUserName, content);
log.info("已成功向学生 (ID:{}) 发送企业微信通知。", stuId);
} else {
log.warn("未找到学生 (ID:{}) 对应的企业微信账号,未发送通知。", stuId);
}
} catch (Exception e) {
log.error("向学生 (ID:{}) 发送企业微信时发生异常: {}", stuId, e.getMessage(), e);
}
// 3. 构建并发送通知内容
WeChatUtil weChatUtil = SpringUtils.getBean(WeChatUtil.class);
String content = "您好,关于您的违纪处理流程已办结,最终结果已生成。<a href='http://zhxg.gxsdxy.cn/web/#/pages/Disciplinary/MyDisciplinary'>请点击登录系统查看详情</a>。";
weChatUtil.sendTextMessage(userName, content);
log.info("流程实例 [{}]: 已成功向学生ID '{}' (企业微信账号: {}) 发送了流程办结通知。", delegateExecution.getProcessInstanceId(), stuId, userName);
} catch (Exception e) {
// 即使通知失败,也不应影响流程的正常结束
log.error("流程实例 [{}]: 在向学生ID '{}' 发送最终通知时出现异常。错误详情: {}",
delegateExecution.getProcessInstanceId(), stuId, e.getMessage(), e);
}
}
/**
* 创建内部消息的私有方法
*/
private void createInternalMessage(Long receiverId, DelegateExecution execution) {
try {
FlowElement nextElement = getNextFlowElement(execution);
// String taskName = (nextElement != null && nextElement.getName() != null) ? nextElement.getName() : "结果确认";
// String activityId = (nextElement != null) ? nextElement.getId() : "Activity_0iy3ij6"; // 备用ID
String taskName = "学生接收";
String activityId = "Activity_0iy3ij6"; // 备用ID
String messageText = "您有一条新的【" + taskName + "】待办任务需要处理。";
String link = "<a href='http://zhxg.gxsdxy.cn/web/#/pages/Approval/index'>请点击前往处理</a>。";
String hiddenIdentifier = String.format("<span id='flow-identifier' data-proc-inst-id='%s' data-activity-id='%s' style='display:none;'></span>",
execution.getProcessInstanceId(), activityId);
String internalMessageContent = messageText + link + hiddenIdentifier;
Long senderId = SecurityUtils.getUserId() != null ? SecurityUtils.getUserId() : 1L;
CphMsg cphMsg = new CphMsg();
cphMsg.setReceiver(receiverId);
cphMsg.setSender(senderId);
cphMsg.setContent(internalMessageContent);
cphMsgService.insertCphMsg(cphMsg);
log.info("已成功为学生 (ID:{}) 创建内部消息。", receiverId);
} catch (Exception e) {
log.error("为学生 (ID:{}) 创建内部消息时发生异常: {}", receiverId, e.getMessage(), e);
}
}
/**
* 安全地获取线上监听器的下一个目标节点
*/
private FlowElement getNextFlowElement(DelegateExecution execution) {
if (execution.getCurrentFlowElement() instanceof SequenceFlow) {
return ((SequenceFlow) execution.getCurrentFlowElement()).getTargetFlowElement();
}
return null;
}
}

View File

@@ -10,7 +10,7 @@ import org.springframework.stereotype.Component;
import java.util.List;
/**
* 【线上监听器-升级版】为“学生教育管理科审核”节点做准备。
* 为“学生教育管理科审核”节点做准备。 知无涯
* 1. 向该候选组的所有成员发送企业微信通知。
* 2. 将该组的所有成员ID列表存入 'userList' 流程变量,供下游监听器(GenericMessageListener)使用。
*/

View File

@@ -14,7 +14,7 @@ import org.springframework.util.CollectionUtils;
import java.util.List;
/**
* 【生产者线上监听器】为“学院违纪处理委员会”多实例任务准备数据。
* 为“学院违纪处理委员会”多实例任务准备数据。 知无涯
* 1. 设置 'userList' 变量供多实例节点本身消费。
* 2. 设置 'nextTaskNameForMessage' 变量供下游消息监听器消费。
* 3. 发送企业微信通知。

View File

@@ -265,7 +265,7 @@ public class RtStuDisciplinaryApplicationServiceImpl extends ServiceImpl<RtStuDi
// todo 企业微信推送消息
AjaxResult ajaxResult = flowDefinitionService.startProcessInstanceById("flow_n27gxm4k:27:795072", variables);
AjaxResult ajaxResult = flowDefinitionService.startProcessInstanceById("flow_n27gxm4k:30:857592", variables);
String code = ajaxResult.get("code").toString();
if (code.equals("200")) {