package com.yunhai.flowcenter.notify; import com.yunhai.flowcenter.engine.FlowEvent; import com.yunhai.flowcenter.entity.FlwInstance; import com.yunhai.flowcenter.entity.FlwTask; import com.yunhai.flowcenter.repository.FlwInstanceRepository; import com.yunhai.flowcenter.repository.FlwTaskRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.springframework.transaction.event.TransactionPhase; import org.springframework.transaction.event.TransactionalEventListener; import java.util.Collections; /** * 站内信/待办角标通知(概览 §七 契约,回调通知"业务系统"、本类通知"人",两通道互不替代): * - 任务到达:msgType=2、bizType=flow.task、msgNo=task:{taskId}、jumpPath=/task/{taskId}、todo +1 * - 任务被办理:todo -1(办理人) * - 完成/拒绝/撤回:msgType=1、bizType=flow.result、content 必填(结果+意见)、msgNo 带 version * (重提重启多轮终态互不幂等吞掉) * - 催办:bizType=flow.urge、msgNo=urge:{taskId}:{第N次} * 全部在 AFTER_COMMIT 监听器里发送,失败不阻断(UserMsgClient 内部容错)。 */ @Slf4j @Component @RequiredArgsConstructor public class UserMsgNotifier { private final UserMsgClient userMsgClient; private final FlwTaskRepository taskRepository; private final FlwInstanceRepository instanceRepository; @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT, fallbackExecution = true) public void onFlowEvent(FlowEvent event) { switch (event.getType()) { case TASK_CREATED: notifyTaskCreated(event); break; case TASK_HANDLED: notifyTaskHandled(event); break; case RETURNED: notifyReturned(event); break; case TRANSFERRED: notifyTaskCreated(event); break; case COMPLETED: case REJECTED: case WITHDRAWN: notifyResult(event); break; case URGE: notifyUrge(event); break; default: break; } } private void notifyTaskCreated(FlowEvent event) { FlwTask task = taskRepository.findById(event.getTaskId()).orElse(null); FlwInstance instance = instanceOf(event.getInstanceId()); if (task == null || instance == null) { return; } String title = "您有新的待办:" + safe(instance.getBizTitle()); String content = "「" + safe(instance.getBizTitle()) + "」等待您审批" + (task.getNodeName() == null ? "" : "(" + task.getNodeName() + ")"); userMsgClient.sendUserMsg(instance.getSchId().intValue(), Collections.singletonList(task.getAssigneeId()), 2, "flow.task", String.valueOf(instance.getId()), title, content, "/task/" + task.getId(), "task:" + task.getId(), 30); userMsgClient.todoInc(instance.getSchId().intValue(), Collections.singletonList(task.getAssigneeId()), 1); } private void notifyTaskHandled(FlowEvent event) { FlwTask task = taskRepository.findById(event.getTaskId()).orElse(null); FlwInstance instance = instanceOf(event.getInstanceId()); if (task == null || instance == null) { return; } userMsgClient.todoInc(instance.getSchId().intValue(), Collections.singletonList(task.getAssigneeId()), -1); } /** * 退回:通知被退回节点的全部新待办人(RETURNED 事件 data.targetNodeIds 由服务层带上—— * 退回重建任务在事件发布前已提交,这里按节点查待办即可);todo 角标由 notifyTaskCreated * 通用链路在引擎重建任务时已发过,这里只补一条带退回意见的消息。 */ private void notifyReturned(FlowEvent event) { FlwInstance instance = instanceOf(event.getInstanceId()); if (instance == null || event.getData() == null) { return; } Object fromNode = event.getData().get("fromNodeName"); Object opinion = event.getData().get("opinion"); StringBuilder content = new StringBuilder("「").append(safe(instance.getBizTitle())).append("」") .append("被退回(").append(fromNode == null ? "" : fromNode).append(")"); if (opinion != null && !String.valueOf(opinion).isEmpty()) { content.append(",意见:").append(opinion); } java.util.List targets = event.getData().get("targetUserIds") == null ? Collections.emptyList() : (java.util.List) event.getData().get("targetUserIds"); if (targets.isEmpty()) { return; } userMsgClient.sendUserMsg(instance.getSchId().intValue(), targets, 1, "flow.returned", String.valueOf(instance.getId()), "办件被退回:" + safe(instance.getBizTitle()), content.toString(), "/instance/" + instance.getId(), "returned:" + instance.getId() + ":" + instance.getVersion(), 30); } private void notifyResult(FlowEvent event) { FlwInstance instance = instanceOf(event.getInstanceId()); if (instance == null) { return; } Object result = event.getData() == null ? null : event.getData().get("result"); String outcome; String bizTitle = safe(instance.getBizTitle()); switch (event.getType()) { case COMPLETED: outcome = "已通过审批"; break; case REJECTED: outcome = "审批被拒绝"; break; default: outcome = "已撤回"; break; } StringBuilder content = new StringBuilder("您提交的「" + bizTitle + "」" + outcome); if (result != null) { content.append("(结果:").append(result).append(")"); } Object opinion = event.getData() == null ? null : event.getData().get("opinion"); if (opinion != null && !String.valueOf(opinion).isEmpty()) { content.append(",意见:").append(opinion); } userMsgClient.sendUserMsg(instance.getSchId().intValue(), Collections.singletonList(instance.getSubmitterId()), 1, "flow.result", String.valueOf(instance.getId()), "办件状态更新:" + bizTitle, content.toString(), "/instance/" + instance.getId(), "result:" + instance.getId() + ":" + instance.getVersion(), 30); } private void notifyUrge(FlowEvent event) { FlwTask task = taskRepository.findById(event.getTaskId()).orElse(null); FlwInstance instance = instanceOf(event.getInstanceId()); if (task == null || instance == null) { return; } int count = task.getUrgeCount() == null ? 1 : task.getUrgeCount(); String content = "「" + safe(instance.getBizTitle()) + "」任务(" + (task.getNodeName() == null ? task.getNodeId() : task.getNodeName()) + ")被催办(第 " + count + " 次),请尽快办理"; userMsgClient.sendUserMsg(instance.getSchId().intValue(), Collections.singletonList(task.getAssigneeId()), 1, "flow.urge", String.valueOf(instance.getId()), "催办提醒:" + safe(instance.getBizTitle()), content, "/task/" + task.getId(), "urge:" + task.getId() + ":" + count, 7); } private FlwInstance instanceOf(Long instanceId) { return instanceId == null ? null : instanceRepository.findById(instanceId).orElse(null); } private static String safe(String s) { return s == null ? "" : s; } }