feat(oapi): 应用内审批 oapi——待办/办件查询 + 办理(operator 复核)+ 待办跳转 bizUrl 优先
build / build-push (push) Successful in 1m29s
build / build-push (push) Successful in 1m29s
- GET /oapi/flow/tasks/todo?schId&assigneeId&bizType&page&size(出参含 biz 定位/快照/版本)
- GET /oapi/flow/instances?schId&bizType&status&page&size(查询不限办理人)
- POST /oapi/flow/task/{taskId}/approve|reject?schId&operatorId&operatorName:
operatorId 按任务办理人逐字复核(非办理人→业务码拒绝不 500),拒绝必填意见,
opinion 经事件链透传到回调 payload;审批后回调/待办减一自动复用
- 待办通知 jumpPath:办件 bizUrl 优先(空回落 /task/{taskId})
契约 IT +3(todo 过滤分页/instances 状态过滤/办理三态+payload 断言),
单测 UserMsgNotifierBizUrlTest 两态;全量 99 单测 + 11 契约 IT 绿。
This commit is contained in:
@@ -15,11 +15,14 @@ import com.yunhai.flowcenter.repository.FlwInstanceRepository;
|
||||
import com.yunhai.flowcenter.repository.FlwTaskRepository;
|
||||
import com.yunhai.platform.apistd.BizException;
|
||||
import com.yunhai.platform.utils.exception.ErrorCode;
|
||||
import com.yunhai.platform.utils.model.BasePageResult;
|
||||
import com.yunhai.platform.utils.model.BoaUser;
|
||||
import com.yunhai.platform.openapi.base.OpenApiUserClient;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.data.domain.Page;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
import org.springframework.http.HttpEntity;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpMethod;
|
||||
@@ -61,6 +64,7 @@ public class OapiFlowService {
|
||||
private final FlwTaskRepository taskRepository;
|
||||
private final FlwCallbackRegRepository regRepository;
|
||||
private final FlowInstanceService instanceService;
|
||||
private final FlowTaskService taskService;
|
||||
private final FlowEngine flowEngine;
|
||||
private final ObjectMapper objectMapper;
|
||||
private final OpenApiUserClient userClient;
|
||||
@@ -119,6 +123,133 @@ public class OapiFlowService {
|
||||
return instance == null ? null : toInstanceVo(instance);
|
||||
}
|
||||
|
||||
// ===== 应用内审批(2026-09 第三步):查询 + 办理 =====
|
||||
|
||||
/**
|
||||
* 我的待办(oapi,应用内审批列表页数据源):assigneeId 维度分页,bizType 可选过滤。
|
||||
* 办理人判定以 taskId 为准——每条待办任务行恰好一个 assignee,不再做集合判断。
|
||||
*/
|
||||
public BasePageResult<OapiModels.TodoTaskVo> pageTodoTasks(Long schId, Integer assigneeId,
|
||||
String bizType, Integer page, Integer size) {
|
||||
if (schId == null || assigneeId == null) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "缺少必要参数:schId/assigneeId");
|
||||
}
|
||||
Page<FlwTask> p = taskRepository.pageTodoBiz(schId, assigneeId, emptyToNull(bizType),
|
||||
PageRequest.of(normPage(page), normSize(size)));
|
||||
List<Long> instanceIds = p.getContent().stream()
|
||||
.map(FlwTask::getInstanceId).distinct().collect(java.util.stream.Collectors.toList());
|
||||
Map<Long, FlwInstance> instances = instanceIds.isEmpty()
|
||||
? java.util.Collections.emptyMap()
|
||||
: instanceRepository.findAllById(instanceIds).stream()
|
||||
.collect(java.util.stream.Collectors.toMap(FlwInstance::getId, i -> i));
|
||||
List<OapiModels.TodoTaskVo> items = p.getContent().stream()
|
||||
.map(t -> toTodoTaskVo(t, instances.get(t.getInstanceId())))
|
||||
.collect(java.util.stream.Collectors.toList());
|
||||
return toPageResult(p.getTotalElements(), items, page, size);
|
||||
}
|
||||
|
||||
/** 办件分页(oapi,应用侧列表/对账):bizType/status 可选过滤 */
|
||||
public BasePageResult<OapiModels.InstanceItemVo> pageInstances(Long schId, String bizType, Integer status,
|
||||
Integer page, Integer size) {
|
||||
if (schId == null) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "缺少必要参数:schId");
|
||||
}
|
||||
Page<FlwInstance> p = instanceRepository.pageByBiz(schId, emptyToNull(bizType), status,
|
||||
PageRequest.of(normPage(page), normSize(size)));
|
||||
List<OapiModels.InstanceItemVo> items = p.getContent().stream()
|
||||
.map(this::toInstanceItemVo).collect(java.util.stream.Collectors.toList());
|
||||
return toPageResult(p.getTotalElements(), items, page, size);
|
||||
}
|
||||
|
||||
/**
|
||||
* 同意(oapi,应用内审批):operatorId 由调用方应用按其登录态传入,本端按任务办理人
|
||||
* 复核({@code task.assigneeId == operatorId},不满足→业务码拒绝,应用签名不替代"谁在办")。
|
||||
* operatorName 供应用侧审计透传,事件流仍以任务行 assigneeName 为准。多出口节点必带
|
||||
* edgeName({@code FlowTaskService.handle} 内校验)。审批后的回调/待办减一等事件链自动复用。
|
||||
*/
|
||||
@Transactional
|
||||
public FlwTask approveTask(Long schId, Long taskId, Integer operatorId, String operatorName,
|
||||
String opinion, String edgeName) {
|
||||
requireOperator(schId, taskId, operatorId, operatorName);
|
||||
return taskService.handle(schId, taskId, true, opinion, operatorId, edgeName);
|
||||
}
|
||||
|
||||
/** 拒绝(oapi):opinion 必填由 {@code FlowTaskService.handle} 强制;其余口径同 {@link #approveTask} */
|
||||
@Transactional
|
||||
public FlwTask rejectTask(Long schId, Long taskId, Integer operatorId, String operatorName,
|
||||
String opinion) {
|
||||
requireOperator(schId, taskId, operatorId, operatorName);
|
||||
return taskService.handle(schId, taskId, false, opinion, operatorId);
|
||||
}
|
||||
|
||||
private void requireOperator(Long schId, Long taskId, Integer operatorId, String operatorName) {
|
||||
if (schId == null || taskId == null || operatorId == null) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "缺少必要参数:schId/taskId/operatorId");
|
||||
}
|
||||
if (operatorName == null || operatorName.trim().isEmpty()) {
|
||||
throw new BizException(ErrorCode.PARAM_ERROR, "缺少必要参数:operatorName(操作人姓名,审计用)");
|
||||
}
|
||||
}
|
||||
|
||||
private OapiModels.TodoTaskVo toTodoTaskVo(FlwTask t, FlwInstance instance) {
|
||||
OapiModels.TodoTaskVo vo = new OapiModels.TodoTaskVo();
|
||||
vo.setTaskId(t.getId());
|
||||
vo.setInstanceId(t.getInstanceId());
|
||||
vo.setInstanceVersion(instance == null ? null : instance.getVersion());
|
||||
vo.setBizType(instance == null ? null : instance.getBizType());
|
||||
vo.setBizId(instance == null ? null : instance.getBizId());
|
||||
vo.setBizTitle(instance == null ? null : instance.getBizTitle());
|
||||
vo.setBizSnapshot(instance == null ? null : instance.getBizSnapshot());
|
||||
vo.setNodeId(t.getNodeId());
|
||||
vo.setNodeName(t.getNodeName());
|
||||
vo.setAssigneeId(t.getAssigneeId());
|
||||
vo.setAssigneeName(t.getAssigneeName());
|
||||
vo.setArriveTime(t.getArriveTime() == null ? null : t.getArriveTime().toString());
|
||||
return vo;
|
||||
}
|
||||
|
||||
private OapiModels.InstanceItemVo toInstanceItemVo(FlwInstance instance) {
|
||||
OapiModels.InstanceItemVo vo = new OapiModels.InstanceItemVo();
|
||||
vo.setInstanceId(instance.getId());
|
||||
vo.setStatus(instance.getStatus());
|
||||
vo.setVersion(instance.getVersion());
|
||||
vo.setBizType(instance.getBizType());
|
||||
vo.setBizId(instance.getBizId());
|
||||
vo.setBizTitle(instance.getBizTitle());
|
||||
vo.setBizUrl(instance.getBizUrl());
|
||||
vo.setSubmitterName(instance.getSubmitterName());
|
||||
vo.setSubmitTime(instance.getSubmitTime() == null ? null : instance.getSubmitTime().toString());
|
||||
vo.setFinishTime(instance.getFinishTime() == null ? null : instance.getFinishTime().toString());
|
||||
vo.setCurrentNodes(instance.getCurrentNodes());
|
||||
return vo;
|
||||
}
|
||||
|
||||
/** 与 FlowQueryService 同口径(page<1→0;size 空/越界→10) */
|
||||
private static int normPage(Integer page) {
|
||||
return page == null || page < 1 ? 0 : page - 1;
|
||||
}
|
||||
|
||||
private static int normSize(Integer size) {
|
||||
return size == null || size < 1 || size > 100 ? 10 : size;
|
||||
}
|
||||
|
||||
private static <T> BasePageResult<T> toPageResult(long total, List<T> content, Integer page, Integer size) {
|
||||
BasePageResult<T> result = new BasePageResult<>();
|
||||
result.setTotal((int) total);
|
||||
result.setContent(content);
|
||||
int pageIndex = page == null || page < 1 ? 1 : page;
|
||||
int pageSize = size == null || size < 1 ? content.size() : size;
|
||||
result.setPageIndex(pageIndex);
|
||||
result.setPageSize(pageSize);
|
||||
result.setTotalPages(pageSize == 0 ? 0 : (int) Math.ceil((double) total / pageSize));
|
||||
result.setHasNext((long) pageIndex * pageSize < total);
|
||||
return result;
|
||||
}
|
||||
|
||||
private static String emptyToNull(String s) {
|
||||
return s == null || s.trim().isEmpty() ? null : s.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤回(托管接入件,2026-09-16 补:原先只有 CAS 用户面 {@code /api/flow/instance/{id}/withdraw},
|
||||
* 业务应用只能让浏览器直调,票据与 CORS 一并外溢)。治理口径:
|
||||
|
||||
Reference in New Issue
Block a user