feat: 提交流程中心后端代码
build / build-push (push) Successful in 28s

Spring Boot 流程引擎服务:流程定义/实例/任务、审批人解析、
回调分发、oapi 接口及单元测试。
保留远程 .gitea/workflows/build.yml 与 docker/Dockerfile,
新增 .gitignore 排除 target/ 构建产物。
This commit is contained in:
chenxing
2026-09-15 10:42:56 +08:00
parent 0296b2b5dd
commit c1103aeb29
65 changed files with 7119 additions and 0 deletions
@@ -0,0 +1,142 @@
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 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);
}
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;
}
}