feat: 收敛AI资源发布审批生命周期

- 统一工作流、知识库、聊天助手的发布、重新发布、下线与删除链路

- 收敛审批编排、生命周期状态机与展示态,补齐审批管理和快照预览

- 调整审批管理权限模型为单入口页面加内部按钮权限
This commit is contained in:
2026-04-09 17:13:54 +08:00
parent 81125ce55c
commit 4e565aef99
68 changed files with 3859 additions and 817 deletions

View File

@@ -11,11 +11,15 @@ import org.springframework.expression.ExpressionParser;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.stereotype.Component;
import tech.easyflow.approval.annotation.ApprovalAction;
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
import tech.easyflow.approval.entity.vo.ApprovalActionResult;
import tech.easyflow.approval.service.ApprovalSubjectHandler;
import tech.easyflow.approval.service.ApprovalActionFacade;
import tech.easyflow.common.satoken.util.SaTokenUtil;
import java.lang.reflect.Method;
import java.math.BigInteger;
import java.util.List;
/**
* 提审动作切面。
@@ -25,11 +29,14 @@ import java.math.BigInteger;
public class ApprovalActionAspect {
private final ApprovalActionFacade approvalActionFacade;
private final List<ApprovalSubjectHandler> handlers;
private final ExpressionParser expressionParser = new SpelExpressionParser();
private final ParameterNameDiscoverer parameterNameDiscoverer = new DefaultParameterNameDiscoverer();
public ApprovalActionAspect(ApprovalActionFacade approvalActionFacade) {
public ApprovalActionAspect(ApprovalActionFacade approvalActionFacade,
List<ApprovalSubjectHandler> handlers) {
this.approvalActionFacade = approvalActionFacade;
this.handlers = handlers;
}
/**
@@ -37,7 +44,7 @@ public class ApprovalActionAspect {
*
* @param joinPoint 切点
* @param approvalAction 注解
* @return 审批实例 ID
* @return 动作执行结果
* @throws Throwable 执行异常
*/
@Around("@annotation(approvalAction)")
@@ -45,12 +52,14 @@ public class ApprovalActionAspect {
Object identifier = resolveIdentifier(joinPoint, approvalAction.idExpr());
BigInteger resourceId = identifier == null ? null : new BigInteger(String.valueOf(identifier));
joinPoint.proceed();
return approvalActionFacade.submit(
approvalAction.resourceType(),
ApprovalSubjectHandler handler = getHandler(approvalAction.resourceType());
ApprovalSubmitRequest request = handler.buildSubmitRequest(
resourceId,
approvalAction.actionType(),
SaTokenUtil.getLoginAccount().getId()
);
ApprovalActionResult result = approvalActionFacade.submit(request);
return result;
}
private Object resolveIdentifier(ProceedingJoinPoint joinPoint, String idExpr) {
@@ -63,4 +72,11 @@ public class ApprovalActionAspect {
);
return expressionParser.parseExpression(idExpr).getValue(context);
}
private ApprovalSubjectHandler getHandler(String resourceType) {
return handlers.stream()
.filter(item -> item.resourceType().equals(resourceType))
.findFirst()
.orElseThrow(() -> new IllegalStateException("未找到审批处理器: " + resourceType));
}
}

View File

@@ -0,0 +1,53 @@
package tech.easyflow.approval.entity.vo;
import java.math.BigInteger;
/**
* 审批动作执行结果。
*/
public class ApprovalActionResult {
private boolean approvalRequired;
private BigInteger instanceId;
/**
* 构造需要审批的结果。
*
* @param instanceId 审批实例 ID
* @return 审批结果
*/
public static ApprovalActionResult required(BigInteger instanceId) {
ApprovalActionResult result = new ApprovalActionResult();
result.setApprovalRequired(true);
result.setInstanceId(instanceId);
return result;
}
/**
* 构造直接执行完成的结果。
*
* @return 审批结果
*/
public static ApprovalActionResult direct() {
ApprovalActionResult result = new ApprovalActionResult();
result.setApprovalRequired(false);
return result;
}
public boolean isApprovalRequired() {
return approvalRequired;
}
public void setApprovalRequired(boolean approvalRequired) {
this.approvalRequired = approvalRequired;
}
public BigInteger getInstanceId() {
return instanceId;
}
public void setInstanceId(BigInteger instanceId) {
this.instanceId = instanceId;
}
}

View File

@@ -33,6 +33,8 @@ public class ApprovalInstanceDetailVo {
private String applicantName;
private String applicantAccount;
private Date submittedAt;
private Date finishedAt;
@@ -137,6 +139,14 @@ public class ApprovalInstanceDetailVo {
this.applicantName = applicantName;
}
public String getApplicantAccount() {
return applicantAccount;
}
public void setApplicantAccount(String applicantAccount) {
this.applicantAccount = applicantAccount;
}
public Date getSubmittedAt() {
return submittedAt;
}

View File

@@ -15,6 +15,8 @@ public class ApprovalLogVo {
private BigInteger operatorId;
private String operatorAccount;
private String operatorName;
private Date created;
@@ -45,6 +47,14 @@ public class ApprovalLogVo {
this.operatorId = operatorId;
}
public String getOperatorAccount() {
return operatorAccount;
}
public void setOperatorAccount(String operatorAccount) {
this.operatorAccount = operatorAccount;
}
public String getOperatorName() {
return operatorName;
}

View File

@@ -9,6 +9,7 @@ import java.util.Locale;
public enum ApprovalActionType {
PUBLISH("PUBLISH"),
OFFLINE("OFFLINE"),
DELETE("DELETE");
private final String code;

View File

@@ -1,6 +1,8 @@
package tech.easyflow.approval.service;
import tech.easyflow.approval.entity.ApprovalInstance;
import tech.easyflow.approval.entity.vo.ApprovalActionResult;
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
import java.math.BigInteger;
@@ -12,13 +14,10 @@ public interface ApprovalActionFacade {
/**
* 提交审批。
*
* @param resourceType 资源类型
* @param resourceId 资源 ID
* @param actionType 动作类型
* @param operatorId 操作人 ID
* @return 审批实例 ID
* @param request 审批提交请求
* @return 动作执行结果
*/
BigInteger submit(String resourceType, BigInteger resourceId, String actionType, BigInteger operatorId);
ApprovalActionResult submit(ApprovalSubmitRequest request);
/**
* 处理审批通过后的业务回调。

View File

@@ -15,4 +15,12 @@ public interface ApprovalMatchService {
* @return 命中的流程详情
*/
ApprovalFlowDetailVo matchFlow(ApprovalSubmitRequest request);
/**
* 根据资源上下文匹配审批流程,未命中时返回 {@code null}。
*
* @param request 审批提交请求
* @return 命中的流程详情,未命中时返回 {@code null}
*/
ApprovalFlowDetailVo matchFlowOrNull(ApprovalSubmitRequest request);
}

View File

@@ -0,0 +1,38 @@
package tech.easyflow.approval.service;
import tech.easyflow.approval.entity.ApprovalInstance;
import java.math.BigInteger;
/**
* 审批结果业务处理器。
*/
public interface ApprovalResultHandler {
/**
* 处理审批通过后的业务回调。
*
* @param instance 审批实例
* @param operatorId 操作人 ID
* @param comment 审批意见
*/
void handleApproved(ApprovalInstance instance, BigInteger operatorId, String comment);
/**
* 处理审批驳回后的业务回调。
*
* @param instance 审批实例
* @param operatorId 操作人 ID
* @param comment 审批意见
*/
void handleRejected(ApprovalInstance instance, BigInteger operatorId, String comment);
/**
* 处理审批撤回后的业务回调。
*
* @param instance 审批实例
* @param operatorId 操作人 ID
* @param comment 审批意见
*/
void handleRevoked(ApprovalInstance instance, BigInteger operatorId, String comment);
}

View File

@@ -1,7 +1,5 @@
package tech.easyflow.approval.service;
import tech.easyflow.approval.entity.vo.ApprovalCallbackContext;
import tech.easyflow.approval.entity.vo.ApprovalSubmitCallbackContext;
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
import java.math.BigInteger;
@@ -28,34 +26,6 @@ public interface ApprovalSubjectHandler {
*/
ApprovalSubmitRequest buildSubmitRequest(BigInteger resourceId, String actionType, BigInteger operatorId);
/**
* 审批提交完成后的回调。
*
* @param context 回调上下文
*/
void onSubmitted(ApprovalSubmitCallbackContext context);
/**
* 审批通过后的回调。
*
* @param context 回调上下文
*/
void onApproved(ApprovalCallbackContext context);
/**
* 审批驳回后的回调。
*
* @param context 回调上下文
*/
void onRejected(ApprovalCallbackContext context);
/**
* 审批撤回后的回调。
*
* @param context 回调上下文
*/
void onRevoked(ApprovalCallbackContext context);
/**
* 校验资源是否已发布。
*

View File

@@ -1,14 +1,15 @@
package tech.easyflow.approval.service.impl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import tech.easyflow.approval.entity.ApprovalInstance;
import tech.easyflow.approval.entity.vo.ApprovalCallbackContext;
import tech.easyflow.approval.entity.vo.ApprovalSubmitCallbackContext;
import tech.easyflow.approval.entity.vo.ApprovalActionResult;
import tech.easyflow.approval.entity.vo.ApprovalFlowDetailVo;
import tech.easyflow.approval.entity.vo.ApprovalSubmitRequest;
import tech.easyflow.approval.enums.ApprovalResourceType;
import tech.easyflow.approval.service.ApprovalActionFacade;
import tech.easyflow.approval.service.ApprovalInstanceService;
import tech.easyflow.approval.service.ApprovalMatchService;
import tech.easyflow.approval.service.ApprovalResultHandler;
import tech.easyflow.approval.service.ApprovalSubjectHandler;
import java.math.BigInteger;
@@ -22,31 +23,30 @@ public class ApprovalActionFacadeImpl implements ApprovalActionFacade {
private final List<ApprovalSubjectHandler> handlers;
private final ApprovalInstanceService approvalInstanceService;
private final ApprovalMatchService approvalMatchService;
private final ApprovalResultHandler approvalResultHandler;
public ApprovalActionFacadeImpl(List<ApprovalSubjectHandler> handlers,
ApprovalInstanceService approvalInstanceService) {
ApprovalInstanceService approvalInstanceService,
ApprovalMatchService approvalMatchService,
ApprovalResultHandler approvalResultHandler) {
this.handlers = handlers;
this.approvalInstanceService = approvalInstanceService;
this.approvalMatchService = approvalMatchService;
this.approvalResultHandler = approvalResultHandler;
}
/**
* {@inheritDoc}
*/
@Override
@Transactional(rollbackFor = Exception.class)
public BigInteger submit(String resourceType, BigInteger resourceId, String actionType, BigInteger operatorId) {
ApprovalSubjectHandler handler = getHandler(resourceType);
ApprovalSubmitRequest request = handler.buildSubmitRequest(resourceId, actionType, operatorId);
public ApprovalActionResult submit(ApprovalSubmitRequest request) {
ApprovalFlowDetailVo flow = approvalMatchService.matchFlowOrNull(request);
if (flow == null) {
return ApprovalActionResult.direct();
}
BigInteger instanceId = approvalInstanceService.submitApproval(request);
ApprovalSubmitCallbackContext context = new ApprovalSubmitCallbackContext();
context.setInstanceId(instanceId);
context.setResourceType(request.getResourceType());
context.setResourceId(request.getResourceId());
context.setActionType(request.getActionType());
context.setOperatorId(operatorId);
handler.onSubmitted(context);
return instanceId;
return ApprovalActionResult.required(instanceId);
}
/**
@@ -54,8 +54,7 @@ public class ApprovalActionFacadeImpl implements ApprovalActionFacade {
*/
@Override
public void handleApproved(ApprovalInstance instance, BigInteger operatorId, String comment) {
ApprovalSubjectHandler handler = getHandler(instance.getResourceType());
handler.onApproved(buildCallbackContext(instance, operatorId, comment));
approvalResultHandler.handleApproved(instance, operatorId, comment);
}
/**
@@ -63,8 +62,7 @@ public class ApprovalActionFacadeImpl implements ApprovalActionFacade {
*/
@Override
public void handleRejected(ApprovalInstance instance, BigInteger operatorId, String comment) {
ApprovalSubjectHandler handler = getHandler(instance.getResourceType());
handler.onRejected(buildCallbackContext(instance, operatorId, comment));
approvalResultHandler.handleRejected(instance, operatorId, comment);
}
/**
@@ -72,8 +70,7 @@ public class ApprovalActionFacadeImpl implements ApprovalActionFacade {
*/
@Override
public void handleRevoked(ApprovalInstance instance, BigInteger operatorId, String comment) {
ApprovalSubjectHandler handler = getHandler(instance.getResourceType());
handler.onRevoked(buildCallbackContext(instance, operatorId, comment));
approvalResultHandler.handleRevoked(instance, operatorId, comment);
}
/**
@@ -85,14 +82,6 @@ public class ApprovalActionFacadeImpl implements ApprovalActionFacade {
handler.assertPublishedAccess(identifier, denyMessage);
}
private ApprovalCallbackContext buildCallbackContext(ApprovalInstance instance, BigInteger operatorId, String comment) {
ApprovalCallbackContext context = new ApprovalCallbackContext();
context.setInstance(instance);
context.setOperatorId(operatorId);
context.setComment(comment);
return context;
}
private ApprovalSubjectHandler getHandler(String resourceType) {
String normalized = ApprovalResourceType.from(resourceType).getCode();
return handlers.stream()

View File

@@ -53,6 +53,18 @@ public class ApprovalMatchServiceImpl implements ApprovalMatchService {
*/
@Override
public ApprovalFlowDetailVo matchFlow(ApprovalSubmitRequest request) {
ApprovalFlowDetailVo matchedFlow = matchFlowOrNull(request);
if (matchedFlow == null) {
throw new BusinessException("当前资源上下文未命中审批流程");
}
return matchedFlow;
}
/**
* {@inheritDoc}
*/
@Override
public ApprovalFlowDetailVo matchFlowOrNull(ApprovalSubmitRequest request) {
ApprovalSubmitRequest normalized = normalizeRequest(request);
QueryWrapper flowWrapper = QueryWrapper.create()
.eq(ApprovalFlow::getResourceType, normalized.getResourceType())
@@ -60,7 +72,7 @@ public class ApprovalMatchServiceImpl implements ApprovalMatchService {
.eq(ApprovalFlow::getStatus, ApprovalFlowStatus.ENABLED.getCode());
List<ApprovalFlow> flows = approvalFlowMapper.selectListByQuery(flowWrapper);
if (CollectionUtil.isEmpty(flows)) {
throw new BusinessException("未找到可用的审批流程");
return null;
}
List<BigInteger> flowIds = flows.stream().map(ApprovalFlow::getId).collect(Collectors.toList());
Map<BigInteger, List<ApprovalFlowScope>> scopeMap = approvalFlowScopeMapper.selectListByQuery(
@@ -76,7 +88,7 @@ public class ApprovalMatchServiceImpl implements ApprovalMatchService {
}
}
if (matchedFlows.isEmpty()) {
throw new BusinessException("当前资源上下文未命中审批流程");
return null;
}
matchedFlows.sort(Comparator

View File

@@ -154,8 +154,9 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
List<ApprovalLog> logs = approvalLogMapper.selectListByQuery(
QueryWrapper.create().eq(ApprovalLog::getInstanceId, instanceId));
Map<Integer, ApprovalFlowStepVo> frozenStepMap = resolveFrozenStepMap(instance);
Map<BigInteger, String> accountNameMap = loadAccountNameMap(instance, tasks, logs);
detail.setApplicantName(accountNameMap.get(instance.getApplicantId()));
Map<BigInteger, SysAccount> accountMap = loadAccountMap(instance, tasks, logs);
detail.setApplicantName(resolveAccountName(accountMap.get(instance.getApplicantId())));
detail.setApplicantAccount(resolveAccountLoginName(accountMap.get(instance.getApplicantId())));
detail.setTasks(tasks.stream()
.sorted(Comparator.comparing(ApprovalTask::getStepNo))
@@ -171,7 +172,7 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
taskVo.setAssigneeTargetCode(item.getAssigneeTargetCode());
taskVo.setAssigneeTargetName(item.getAssigneeTargetName());
taskVo.setActedBy(item.getActedBy());
taskVo.setActedByName(accountNameMap.get(item.getActedBy()));
taskVo.setActedByName(resolveAccountName(accountMap.get(item.getActedBy())));
taskVo.setActedAt(item.getActedAt());
taskVo.setComment(item.getComment());
return taskVo;
@@ -185,7 +186,8 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
logVo.setId(item.getId());
logVo.setEventType(item.getEventType());
logVo.setOperatorId(item.getOperatorId());
logVo.setOperatorName(accountNameMap.get(item.getOperatorId()));
logVo.setOperatorAccount(resolveAccountLoginName(accountMap.get(item.getOperatorId())));
logVo.setOperatorName(resolveAccountName(accountMap.get(item.getOperatorId())));
logVo.setCreated(item.getCreated());
logVo.setPayloadJson(item.getPayloadJson());
return logVo;
@@ -205,14 +207,14 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
}
/**
* 批量加载审批详情里涉及到的账号显示名称
* 批量加载审批详情里涉及到的账号信息
*
* @param instance 审批实例
* @param tasks 审批任务列表
* @param logs 审批日志列表
* @return 账号 ID 到展示名称的映射
* @return 账号 ID 到账号实体的映射
*/
private Map<BigInteger, String> loadAccountNameMap(ApprovalInstance instance, List<ApprovalTask> tasks,
private Map<BigInteger, SysAccount> loadAccountMap(ApprovalInstance instance, List<ApprovalTask> tasks,
List<ApprovalLog> logs) {
Set<BigInteger> accountIds = new HashSet<>();
if (instance.getApplicantId() != null) {
@@ -232,7 +234,7 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
return sysAccountService.listByIds(accountIds).stream()
.collect(Collectors.toMap(
SysAccount::getId,
this::resolveAccountName,
account -> account,
(left, right) -> left,
LinkedHashMap::new));
}
@@ -256,6 +258,19 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
return null;
}
/**
* 解析账号登录名。
*
* @param account 账号实体
* @return 登录账号
*/
private String resolveAccountLoginName(SysAccount account) {
if (account == null || !StringUtils.hasText(account.getLoginName())) {
return null;
}
return account.getLoginName().trim();
}
private QueryWrapper buildBaseQuery(String resourceType, String actionType, String keyword) {
QueryWrapper queryWrapper = QueryWrapper.create();
if (StringUtils.hasText(resourceType)) {