This commit is contained in:
@@ -4,6 +4,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.yunhai.flowcenter.callback.CallbackAppSigner;
|
||||
import com.yunhai.flowcenter.entity.FlwCallbackReg;
|
||||
import com.yunhai.flowcenter.entity.FlwDef;
|
||||
import com.yunhai.flowcenter.entity.FlwTask;
|
||||
import com.yunhai.flowcenter.entity.FlwInstance;
|
||||
import com.yunhai.flowcenter.engine.FlowEngine;
|
||||
import com.yunhai.flowcenter.enums.FlwEnums;
|
||||
@@ -29,6 +30,7 @@ import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
@@ -62,6 +64,7 @@ public class OapiFlowService {
|
||||
private final FlowEngine flowEngine;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final OpenApiUserClient userClient;
|
||||
private final DiagramTokenService tokenService;
|
||||
|
||||
/** 本应用身份(回调探活补应用签名头;未装配时裸发,接收方 /oapi/* 会 401) */
|
||||
private final org.springframework.beans.factory.ObjectProvider<com.yunhai.platform.appauth.LocalAppIdentityProvider>
|
||||
@@ -116,6 +119,193 @@ public class OapiFlowService {
|
||||
return instance == null ? null : toInstanceVo(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回(托管接入件,2026-09-16 补:原先只有 CAS 用户面 {@code /api/flow/instance/{id}/withdraw},
|
||||
* 业务应用只能让浏览器直调,票据与 CORS 一并外溢)。治理口径:
|
||||
* <ul>
|
||||
* <li>身份:{@code submitterId} 必传——应用间签名只证明"哪个应用在调",不证明"谁在撤",
|
||||
* 对端按 submitterId 与办件提交人**逐字复核**,杜绝业务应用越权代撤他人办件;</li>
|
||||
* <li>租户:schId 收敛 + requireInstance 校验办件归属;</li>
|
||||
* <li>状态:仅审批中(10) 且尚无任务被办理(引擎既有纪律,见 FlowInstanceService.withdraw);</li>
|
||||
* <li>不校验 submitterId 的本校成员资格——提交人身份在发起时已按 §5.5 校验过,撤回不改变归属。</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Transactional
|
||||
public void withdraw(Long schId, Long instanceId, Integer submitterId) {
|
||||
if (schId == null || instanceId == null || submitterId == null) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "缺少必要参数:schId/instanceId/submitterId");
|
||||
}
|
||||
FlwInstance instance = instanceService.requireInstance(schId, instanceId);
|
||||
if (!submitterId.equals(instance.getSubmitterId())) {
|
||||
throw new BizException(ErrorCode.FORBIDDEN, "仅提交人本人可撤回");
|
||||
}
|
||||
instanceService.withdraw(schId, instanceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 办件进度(结构化轨迹):steps=发起申请 + 节点步骤(按任务 id 序;同节点同人多任务行合并为多人步骤)。
|
||||
* 状态标签口径与前端流程轨迹一致(0 待审核 / 1 已同意 / 2 已拒绝 / 3 失效 / 4 已退回 / 5 已移交);
|
||||
* 失效行保留(消费方自行决定是否隐藏——重启重提后的旧节点轨迹即 3/失效)。
|
||||
*/
|
||||
public OapiModels.ProgressVo progress(Long schId, Long instanceId, String bizType, String bizId) {
|
||||
FlwInstance instance;
|
||||
if (instanceId != null) {
|
||||
instance = requireInstance(schId, instanceId);
|
||||
} else {
|
||||
if (bizType == null || bizId == null) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "缺少必要参数:instanceId 或 bizType/bizId");
|
||||
}
|
||||
instance = instanceRepository.findBySchIdAndBizTypeAndBizId(schId, bizType, bizId)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办件不存在或不可见"));
|
||||
}
|
||||
return toProgressVo(instance);
|
||||
}
|
||||
|
||||
/**
|
||||
* 流程图换图(token 模式):app-sign 校验在本次请求完成(/oapi 强制验签),token 只向浏览器
|
||||
* 传递"出图资格"。二选一:instanceId(办件口径,带运行时状态+可见性收紧)/ flowGuid(定义
|
||||
* 口径,最高已发布版)。userId 无需传——appKey 调用方已视为可信(两级模型,可见性由发起方
|
||||
* 业务边界保证),浏览器侧只认 token。
|
||||
*/
|
||||
public OapiModels.DiagramUrlVo diagramUrl(Long schId, Long instanceId, String flowGuid, String mode) {
|
||||
if (flowGuid != null && !flowGuid.trim().isEmpty()) {
|
||||
FlwDef def = defRepository.findFirstBySchIdAndFlowGuidAndStatusOrderByVersionDesc(
|
||||
schId, flowGuid.trim(), FlwEnums.DefStatus.PUBLISHED.getCode())
|
||||
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR,
|
||||
"流程不存在或未发布:" + flowGuid));
|
||||
return issueUrl(schId, null, def.getId(), mode);
|
||||
}
|
||||
if (instanceId != null) {
|
||||
requireInstance(schId, instanceId);
|
||||
return issueUrl(schId, instanceId, null, mode);
|
||||
}
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "缺少必要参数:instanceId 或 flowGuid");
|
||||
}
|
||||
|
||||
private OapiModels.DiagramUrlVo issueUrl(Long schId, Long instanceId, Long defId, String mode) {
|
||||
String token = tokenService.issue(schId, instanceId, defId, mode);
|
||||
OapiModels.DiagramUrlVo vo = new OapiModels.DiagramUrlVo();
|
||||
vo.setImgUrl("/open/flow/diagram/img?token=" + token);
|
||||
vo.setExpiresIn(DiagramTokenService.TTL_MS / 1000);
|
||||
return vo;
|
||||
}
|
||||
|
||||
/** 进度/换图共用的办件定位(schId 收敛在次) */
|
||||
private FlwInstance requireInstance(Long schId, Long instanceId) {
|
||||
FlwInstance instance = instanceRepository.findById(instanceId)
|
||||
.orElseThrow(() -> new BizException(ErrorCode.PARAM_ERROR, "办件不存在:" + instanceId));
|
||||
if (!schId.equals(instance.getSchId())) {
|
||||
throw new BizException(ErrorCode.FORBIDDEN, "办件不属于该学校");
|
||||
}
|
||||
return instance;
|
||||
}
|
||||
|
||||
private OapiModels.ProgressVo toProgressVo(FlwInstance instance) {
|
||||
OapiModels.ProgressVo vo = new OapiModels.ProgressVo();
|
||||
vo.setInstanceId(instance.getId());
|
||||
vo.setBizType(instance.getBizType());
|
||||
vo.setBizId(instance.getBizId());
|
||||
vo.setBizTitle(instance.getBizTitle());
|
||||
vo.setStatus(instance.getStatus());
|
||||
vo.setCurrentNodes(instance.getCurrentNodes());
|
||||
vo.setSubmitterId(instance.getSubmitterId());
|
||||
vo.setSubmitterName(instance.getSubmitterName());
|
||||
vo.setSubmitTime(instance.getSubmitTime() == null ? null : instance.getSubmitTime().toString());
|
||||
vo.setFinishTime(instance.getFinishTime() == null ? null : instance.getFinishTime().toString());
|
||||
|
||||
Set<String> activeNodes = new LinkedHashSet<>();
|
||||
if (instance.getCurrentNodes() != null && !instance.getCurrentNodes().isEmpty()) {
|
||||
activeNodes.addAll(Arrays.asList(instance.getCurrentNodes().split(",")));
|
||||
}
|
||||
// 第 0 步固定"发起申请"(对外时间线图3/图4口径)
|
||||
OapiModels.ProgressStepVo submit = new OapiModels.ProgressStepVo();
|
||||
submit.setNodeId("submit");
|
||||
submit.setNodeName("发起申请");
|
||||
submit.setActive(false);
|
||||
submit.setLabel("发起申请");
|
||||
OapiModels.ProgressTaskVo submitter = new OapiModels.ProgressTaskVo();
|
||||
submitter.setUserId(instance.getSubmitterId());
|
||||
submitter.setUserName(instance.getSubmitterName());
|
||||
submitter.setStatus(1);
|
||||
submitter.setStatusLabel("已提交");
|
||||
submitter.setArriveTime(vo.getSubmitTime());
|
||||
submit.setAssignees(new ArrayList<>(java.util.Collections.singletonList(submitter)));
|
||||
List<OapiModels.ProgressStepVo> steps = new ArrayList<>();
|
||||
steps.add(submit);
|
||||
|
||||
// 步骤聚合:nodeId 保序;同节点多任务行(会签/退回重到/失效清理)并入同一多人步骤
|
||||
Map<String, OapiModels.ProgressStepVo> stepsByNode = new HashMap<>();
|
||||
for (FlwTask t : taskRepository.findByInstanceIdOrderByIdAsc(instance.getId())) {
|
||||
OapiModels.ProgressStepVo step = stepsByNode.computeIfAbsent(t.getNodeId(), k -> {
|
||||
OapiModels.ProgressStepVo s = new OapiModels.ProgressStepVo();
|
||||
s.setNodeId(t.getNodeId());
|
||||
s.setNodeName(t.getNodeName());
|
||||
s.setAssignees(new ArrayList<>());
|
||||
steps.add(s);
|
||||
return s;
|
||||
});
|
||||
OapiModels.ProgressTaskVo row = new OapiModels.ProgressTaskVo();
|
||||
row.setTaskId(t.getId());
|
||||
row.setUserId(t.getAssigneeId());
|
||||
row.setUserName(t.getAssigneeName());
|
||||
row.setStatus(t.getStatus());
|
||||
row.setStatusLabel(taskLabel(t.getStatus()));
|
||||
row.setOpinion(t.getOpinion());
|
||||
row.setArriveTime(t.getArriveTime() == null ? null : t.getArriveTime().toString());
|
||||
row.setHandleTime(t.getHandleTime() == null ? null : t.getHandleTime().toString());
|
||||
row.setEdgeName(t.getEdgeName());
|
||||
step.getAssignees().add(row);
|
||||
}
|
||||
// 步骤 label:在办节点=待审核,其余取办理人行的汇总(任一拒绝=已拒绝 > 任一同意=已同意 > 全失效=失效)
|
||||
for (OapiModels.ProgressStepVo s : steps) {
|
||||
if (s == submit) {
|
||||
continue;
|
||||
}
|
||||
s.setActive(activeNodes.contains(s.getNodeId()));
|
||||
s.setLabel(s.isActive() ? "待审核" : stepLabel(s.getAssignees()));
|
||||
}
|
||||
vo.setSteps(steps);
|
||||
return vo;
|
||||
}
|
||||
|
||||
private String taskLabel(Integer status) {
|
||||
if (status == null) {
|
||||
return "";
|
||||
}
|
||||
switch (status) {
|
||||
case 0: return "待审核";
|
||||
case 1: return "已同意";
|
||||
case 2: return "已拒绝";
|
||||
case 3: return "失效";
|
||||
case 4: return "已退回";
|
||||
case 5: return "已移交";
|
||||
default: return String.valueOf(status);
|
||||
}
|
||||
}
|
||||
|
||||
private String stepLabel(List<OapiModels.ProgressTaskVo> rows) {
|
||||
boolean denied = false, approved = false, invalid = true;
|
||||
for (OapiModels.ProgressTaskVo r : rows) {
|
||||
int s = r.getStatus() == null ? -1 : r.getStatus();
|
||||
if (s == 2) {
|
||||
denied = true;
|
||||
}
|
||||
if (s == 1) {
|
||||
approved = true;
|
||||
}
|
||||
if (s == 0 || s == 1 || s == 2 || s == 4 || s == 5) {
|
||||
invalid = false;
|
||||
}
|
||||
}
|
||||
if (denied) {
|
||||
return "已拒绝";
|
||||
}
|
||||
if (approved) {
|
||||
return "已同意";
|
||||
}
|
||||
return invalid ? "失效" : "待审核";
|
||||
}
|
||||
|
||||
/** 回调注册(uk: sch+appKey+flowGuid;重复注册=更新配置并重置 secret;注册时探活) */
|
||||
@Transactional
|
||||
public OapiModels.CallbackRegResultVo registerCallback(Long schId, String appKey,
|
||||
|
||||
Reference in New Issue
Block a user