build / build-push (push) Successful in 5m11s
- 多出口:FlwTask 增 edge_name 列(sql/20260916),FlwTaskRepository.claimAndFinish
同步落列;FlowTaskService.handle 出度>1 时 edgeName 必填;FlowEngine.continueAdvance
按 edge name 选路(单出口原路径逐字节兼容,未命中不推进);FlowAdvanceService/
FlowNodeAdvanceListener 透传;BranchScope 从 FlowEngine 提取
- DefValidator:多出口出口命名必填且同节点唯一;跨分支连线发布期明确拒绝
- 退回 POST /task/{id}/returnBack:默认退主管线最近已批节点,精确失效当前节点待办,
split 幂等 upsert 使退回重走并行段不撞 flw_branch uk
- 移交 POST /task/{id}/transfer:TRANSFERRED(5)+新建待办,AssigneeResolver.requireUserName
- 或签 ANY 补齐:首个同意失效同节点兄弟待办
- 已办口径/撤回守卫:status in (1,2,4,5);新事件 flow.returned/flow.transferred
- FlowDiagramService:EdgeVo.name/NodeVo.state/DiagramVo.layout,办件图状态计算
- 新 IT:多出口×3、ANY 或签、退回重走并行段、退回/移交/撤回
mvn test 84/84 绿
179 lines
7.9 KiB
Java
179 lines
7.9 KiB
Java
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<Integer> targets = event.getData().get("targetUserIds") == null
|
||
? Collections.emptyList()
|
||
: (java.util.List<Integer>) 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;
|
||
}
|
||
}
|