feat: 完善 Skill 管理与发布治理
- 实现标准资源存储、能力绑定及双格式导入导出 - 接入分类、可见范围、审批发布与资源权限校验 - 补充并发、租户隔离、安全边界和迁移契约测试
This commit is contained in:
@@ -20,6 +20,9 @@ public class ApprovalInstanceBase implements Serializable {
|
||||
@Id(keyType = KeyType.Generator, value = "snowFlakeId", comment = "主键")
|
||||
private BigInteger id;
|
||||
|
||||
@Column(tenantId = true, comment = "租户ID")
|
||||
private BigInteger tenantId;
|
||||
|
||||
@Column(comment = "流程ID")
|
||||
private BigInteger flowId;
|
||||
|
||||
@@ -82,6 +85,14 @@ public class ApprovalInstanceBase implements Serializable {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public BigInteger getTenantId() {
|
||||
return tenantId;
|
||||
}
|
||||
|
||||
public void setTenantId(BigInteger tenantId) {
|
||||
this.tenantId = tenantId;
|
||||
}
|
||||
|
||||
public BigInteger getFlowId() {
|
||||
return flowId;
|
||||
}
|
||||
|
||||
@@ -31,6 +31,16 @@ public class ApprovalInstancePageVo {
|
||||
|
||||
private BigInteger applicantId;
|
||||
|
||||
/**
|
||||
* 申请人展示名称。
|
||||
*/
|
||||
private String applicantName;
|
||||
|
||||
/**
|
||||
* 申请人登录账号。
|
||||
*/
|
||||
private String applicantAccount;
|
||||
|
||||
private Date submittedAt;
|
||||
|
||||
private Date finishedAt;
|
||||
@@ -131,6 +141,42 @@ public class ApprovalInstancePageVo {
|
||||
this.applicantId = applicantId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取申请人展示名称。
|
||||
*
|
||||
* @return 申请人展示名称
|
||||
*/
|
||||
public String getApplicantName() {
|
||||
return applicantName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置申请人展示名称。
|
||||
*
|
||||
* @param applicantName 申请人展示名称
|
||||
*/
|
||||
public void setApplicantName(String applicantName) {
|
||||
this.applicantName = applicantName;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取申请人登录账号。
|
||||
*
|
||||
* @return 申请人登录账号
|
||||
*/
|
||||
public String getApplicantAccount() {
|
||||
return applicantAccount;
|
||||
}
|
||||
|
||||
/**
|
||||
* 设置申请人登录账号。
|
||||
*
|
||||
* @param applicantAccount 申请人登录账号
|
||||
*/
|
||||
public void setApplicantAccount(String applicantAccount) {
|
||||
this.applicantAccount = applicantAccount;
|
||||
}
|
||||
|
||||
public Date getSubmittedAt() {
|
||||
return submittedAt;
|
||||
}
|
||||
|
||||
@@ -19,6 +19,15 @@ public interface ApprovalActionFacade {
|
||||
*/
|
||||
ApprovalActionResult submit(ApprovalSubmitRequest request);
|
||||
|
||||
/**
|
||||
* 判断当前登录用户是否经资源处理器授权查看审批详情。
|
||||
*
|
||||
* @param resourceType 资源类型
|
||||
* @param identifier 资源标识
|
||||
* @return 允许查看时返回 {@code true}
|
||||
*/
|
||||
boolean canAccessApprovalDetail(String resourceType, Object identifier);
|
||||
|
||||
/**
|
||||
* 处理审批通过后的业务回调。
|
||||
*
|
||||
|
||||
@@ -26,6 +26,17 @@ public interface ApprovalSubjectHandler {
|
||||
*/
|
||||
ApprovalSubmitRequest buildSubmitRequest(BigInteger resourceId, String actionType, BigInteger operatorId);
|
||||
|
||||
/**
|
||||
* 判断当前登录用户是否可通过资源权限查看审批详情。
|
||||
*
|
||||
* <p>审批申请人和任务处理人的访问由审批模块统一判断;该方法只负责补充资源自身的
|
||||
* 授权口径,避免审批详情绕过资源权限系统。</p>
|
||||
*
|
||||
* @param identifier 资源标识
|
||||
* @return 允许查看时返回 {@code true}
|
||||
*/
|
||||
boolean canAccessApprovalDetail(Object identifier);
|
||||
|
||||
/**
|
||||
* 校验资源是否已发布。
|
||||
*
|
||||
|
||||
@@ -49,6 +49,15 @@ public class ApprovalActionFacadeImpl implements ApprovalActionFacade {
|
||||
return ApprovalActionResult.required(instanceId);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
@Override
|
||||
public boolean canAccessApprovalDetail(String resourceType, Object identifier) {
|
||||
ApprovalSubjectHandler handler = getHandler(resourceType);
|
||||
return handler.canAccessApprovalDetail(identifier);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
|
||||
@@ -5,6 +5,8 @@ import com.mybatisflex.core.query.QueryWrapper;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import tech.easyflow.common.entity.LoginAccount;
|
||||
import tech.easyflow.common.satoken.util.SaTokenUtil;
|
||||
import tech.easyflow.common.web.exceptions.BusinessException;
|
||||
import tech.easyflow.approval.entity.ApprovalInstance;
|
||||
import tech.easyflow.approval.entity.ApprovalFlowStep;
|
||||
@@ -25,6 +27,8 @@ import tech.easyflow.approval.mapper.ApprovalLogMapper;
|
||||
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
|
||||
import tech.easyflow.approval.service.ApprovalInstanceService;
|
||||
import tech.easyflow.approval.service.ApprovalMatchService;
|
||||
import tech.easyflow.system.entity.SysAccount;
|
||||
import tech.easyflow.system.service.SysAccountService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.math.BigInteger;
|
||||
@@ -61,6 +65,9 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
|
||||
@Resource
|
||||
private ApprovalAssigneeService approvalAssigneeService;
|
||||
|
||||
@Resource
|
||||
private SysAccountService sysAccountService;
|
||||
|
||||
@Lazy
|
||||
@Resource
|
||||
private ApprovalActionFacade approvalActionFacade;
|
||||
@@ -71,13 +78,24 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public BigInteger submitApproval(ApprovalSubmitRequest request) {
|
||||
ApprovalFlowDetailVo flow = approvalMatchService.matchFlow(request);
|
||||
if (CollectionUtil.isEmpty(flow.getSteps())) {
|
||||
throw new BusinessException("审批流程未配置步骤");
|
||||
if (request == null) {
|
||||
throw new BusinessException("审批请求不能为空");
|
||||
}
|
||||
if (request.getApplicantId() == null) {
|
||||
throw new BusinessException("申请人不能为空");
|
||||
}
|
||||
LoginAccount loginAccount = requireCurrentLoginAccount();
|
||||
if (!loginAccount.getId().equals(request.getApplicantId())) {
|
||||
throw new BusinessException(403, 403, "不允许以其他账号身份提交审批");
|
||||
}
|
||||
SysAccount applicant = requireTenantAccount(request.getApplicantId(), "申请人");
|
||||
if (!loginAccount.getTenantId().equals(applicant.getTenantId())) {
|
||||
throw new BusinessException(403, 403, "申请人租户信息与当前登录态不一致");
|
||||
}
|
||||
ApprovalFlowDetailVo flow = approvalMatchService.matchFlow(request);
|
||||
if (CollectionUtil.isEmpty(flow.getSteps())) {
|
||||
throw new BusinessException("审批流程未配置步骤");
|
||||
}
|
||||
|
||||
List<ApprovalFlowStepVo> steps = new ArrayList<>(flow.getSteps());
|
||||
steps.sort(Comparator.comparing(ApprovalFlowStepVo::getStepNo));
|
||||
@@ -85,6 +103,7 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
|
||||
Date now = new Date();
|
||||
|
||||
ApprovalInstance instance = new ApprovalInstance();
|
||||
instance.setTenantId(applicant.getTenantId());
|
||||
instance.setFlowId(flow.getId());
|
||||
instance.setFlowVersion(flow.getVersion());
|
||||
instance.setResourceType(flow.getResourceType());
|
||||
@@ -124,7 +143,7 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void approve(BigInteger instanceId, String comment, BigInteger operatorId) {
|
||||
ApprovalInstance instance = requireActiveInstance(instanceId);
|
||||
ApprovalInstance instance = requireActiveInstance(instanceId, operatorId);
|
||||
ApprovalTask currentTask = requireCurrentTask(instanceId, instance.getCurrentStepNo());
|
||||
assertTaskOperable(currentTask, operatorId);
|
||||
List<ApprovalFlowStepVo> steps = resolveFrozenSteps(instance);
|
||||
@@ -161,7 +180,7 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void reject(BigInteger instanceId, String comment, BigInteger operatorId) {
|
||||
ApprovalInstance instance = requireActiveInstance(instanceId);
|
||||
ApprovalInstance instance = requireActiveInstance(instanceId, operatorId);
|
||||
ApprovalTask currentTask = requireCurrentTask(instanceId, instance.getCurrentStepNo());
|
||||
assertTaskOperable(currentTask, operatorId);
|
||||
Date now = new Date();
|
||||
@@ -184,7 +203,7 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public void revoke(BigInteger instanceId, String comment, BigInteger operatorId) {
|
||||
ApprovalInstance instance = requireActiveInstance(instanceId);
|
||||
ApprovalInstance instance = requireActiveInstance(instanceId, operatorId);
|
||||
// 撤回属于发起人的自助操作,不能沿用审批任务处理人的授权口径。
|
||||
if (!Objects.equals(instance.getApplicantId(), operatorId)) {
|
||||
throw new BusinessException(403, 403, "仅审批申请人可以撤回该请求");
|
||||
@@ -209,7 +228,9 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
|
||||
*/
|
||||
@Override
|
||||
public boolean existsActiveInstance(String resourceType, BigInteger resourceId) {
|
||||
BigInteger tenantId = requireCurrentTenantId();
|
||||
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||
.eq(ApprovalInstance::getTenantId, tenantId)
|
||||
.eq(ApprovalInstance::getResourceType, resourceType)
|
||||
.eq(ApprovalInstance::getResourceId, resourceId)
|
||||
.notIn(ApprovalInstance::getStatus,
|
||||
@@ -224,7 +245,9 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
|
||||
*/
|
||||
@Override
|
||||
public ApprovalInstance getById(BigInteger instanceId) {
|
||||
return approvalInstanceMapper.selectOneById(instanceId);
|
||||
return approvalInstanceMapper.selectOneByQuery(QueryWrapper.create()
|
||||
.eq(ApprovalInstance::getId, instanceId)
|
||||
.eq(ApprovalInstance::getTenantId, requireCurrentTenantId()));
|
||||
}
|
||||
|
||||
private Map<String, Object> buildInstanceSnapshot(ApprovalSubmitRequest request, ApprovalFlowDetailVo flow,
|
||||
@@ -344,15 +367,17 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
|
||||
approvalLogMapper.insert(log);
|
||||
}
|
||||
|
||||
private ApprovalInstance requireActiveInstance(BigInteger instanceId) {
|
||||
private ApprovalInstance requireActiveInstance(BigInteger instanceId, BigInteger operatorId) {
|
||||
if (instanceId == null) {
|
||||
throw new BusinessException("审批实例ID不能为空");
|
||||
}
|
||||
ApprovalInstance instance = approvalInstanceMapper.selectOneByQuery(
|
||||
QueryWrapper.create().eq(ApprovalInstance::getId, instanceId).forUpdate()
|
||||
);
|
||||
SysAccount operator = requireTenantAccount(operatorId, "操作人");
|
||||
ApprovalInstance instance = approvalInstanceMapper.selectOneByQuery(QueryWrapper.create()
|
||||
.eq(ApprovalInstance::getId, instanceId)
|
||||
.eq(ApprovalInstance::getTenantId, operator.getTenantId())
|
||||
.forUpdate());
|
||||
if (instance == null) {
|
||||
throw new BusinessException("审批实例不存在");
|
||||
throw new BusinessException(404, 404, "审批实例不存在");
|
||||
}
|
||||
if (ApprovalInstanceStatus.from(instance.getStatus()).isFinished()) {
|
||||
throw new BusinessException("审批实例已结束,无法继续处理");
|
||||
@@ -360,6 +385,49 @@ public class ApprovalInstanceServiceImpl implements ApprovalInstanceService {
|
||||
return instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* 读取账号及其稳定租户归属。
|
||||
*
|
||||
* @param accountId 账号 ID
|
||||
* @param accountLabel 账号角色说明
|
||||
* @return 有效账号
|
||||
* @throws BusinessException 账号不存在或缺少租户归属时抛出
|
||||
*/
|
||||
private SysAccount requireTenantAccount(BigInteger accountId, String accountLabel) {
|
||||
if (accountId == null) {
|
||||
throw new BusinessException(accountLabel + "不能为空");
|
||||
}
|
||||
SysAccount account = sysAccountService.getById(accountId);
|
||||
if (account == null || account.getTenantId() == null) {
|
||||
throw new BusinessException(403, 403, accountLabel + "不存在或租户信息无效");
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前登录账号的租户 ID。
|
||||
*
|
||||
* @return 当前租户 ID
|
||||
* @throws BusinessException 登录态缺少账号或租户信息时抛出
|
||||
*/
|
||||
private BigInteger requireCurrentTenantId() {
|
||||
return requireCurrentLoginAccount().getTenantId();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取完整的当前登录账号。
|
||||
*
|
||||
* @return 当前登录账号
|
||||
* @throws BusinessException 登录态缺少账号或租户信息时抛出
|
||||
*/
|
||||
private LoginAccount requireCurrentLoginAccount() {
|
||||
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||
if (account == null || account.getId() == null || account.getTenantId() == null) {
|
||||
throw new BusinessException(401, 401, "未登录或登录态无效");
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
private ApprovalTask requireCurrentTask(BigInteger instanceId, Integer stepNo) {
|
||||
QueryWrapper queryWrapper = QueryWrapper.create()
|
||||
.eq(ApprovalTask::getInstanceId, instanceId)
|
||||
|
||||
@@ -26,9 +26,11 @@ import tech.easyflow.approval.mapper.ApprovalFlowStepMapper;
|
||||
import tech.easyflow.approval.mapper.ApprovalInstanceMapper;
|
||||
import tech.easyflow.approval.mapper.ApprovalLogMapper;
|
||||
import tech.easyflow.approval.mapper.ApprovalTaskMapper;
|
||||
import tech.easyflow.approval.service.ApprovalActionFacade;
|
||||
import tech.easyflow.approval.service.ApprovalAssigneeService;
|
||||
import tech.easyflow.approval.service.ApprovalQueryService;
|
||||
import tech.easyflow.system.entity.SysAccount;
|
||||
import tech.easyflow.system.service.CategoryPermissionService;
|
||||
import tech.easyflow.system.service.SysAccountService;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
@@ -64,6 +66,12 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
|
||||
@Resource
|
||||
private ApprovalAssigneeService approvalAssigneeService;
|
||||
|
||||
@Resource
|
||||
private ApprovalActionFacade approvalActionFacade;
|
||||
|
||||
@Resource
|
||||
private CategoryPermissionService categoryPermissionService;
|
||||
|
||||
@Resource
|
||||
private SysAccountService sysAccountService;
|
||||
|
||||
@@ -80,6 +88,7 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
|
||||
return new Page<>(List.of(), safePageNumber(pageNumber), safePageSize(pageSize), 0L);
|
||||
}
|
||||
QueryWrapper queryWrapper = buildBaseQuery(resourceType, actionType, keyword);
|
||||
queryWrapper.eq(ApprovalInstance::getTenantId, account.getTenantId());
|
||||
queryWrapper.in(ApprovalInstance::getId, instanceIds);
|
||||
queryWrapper.in(ApprovalInstance::getStatus, List.of(
|
||||
ApprovalInstanceStatus.PENDING.getCode(),
|
||||
@@ -109,6 +118,7 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
|
||||
return new Page<>(List.of(), safePageNumber(pageNumber), safePageSize(pageSize), 0L);
|
||||
}
|
||||
QueryWrapper queryWrapper = buildBaseQuery(resourceType, actionType, keyword);
|
||||
queryWrapper.eq(ApprovalInstance::getTenantId, account.getTenantId());
|
||||
queryWrapper.in(ApprovalInstance::getId, instanceIds);
|
||||
queryWrapper.orderBy("finished_at desc, id desc");
|
||||
return mapPage(queryWrapper, safePageNumber(pageNumber), safePageSize(pageSize), false, account, Set.of());
|
||||
@@ -122,6 +132,7 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
|
||||
Long pageNumber, Long pageSize) {
|
||||
LoginAccount account = requireLoginAccount();
|
||||
QueryWrapper queryWrapper = buildBaseQuery(resourceType, actionType, keyword);
|
||||
queryWrapper.eq(ApprovalInstance::getTenantId, account.getTenantId());
|
||||
queryWrapper.eq(ApprovalInstance::getApplicantId, account.getId());
|
||||
queryWrapper.orderBy("submitted_at desc, id desc");
|
||||
return mapPage(queryWrapper, safePageNumber(pageNumber), safePageSize(pageSize), false, account, Set.of());
|
||||
@@ -132,10 +143,18 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
|
||||
*/
|
||||
@Override
|
||||
public ApprovalInstanceDetailVo detail(BigInteger instanceId) {
|
||||
ApprovalInstance instance = approvalInstanceMapper.selectOneById(instanceId);
|
||||
if (instance == null) {
|
||||
throw new BusinessException("审批实例不存在");
|
||||
LoginAccount account = requireLoginAccount();
|
||||
ApprovalInstance instance = approvalInstanceMapper.selectOneByQuery(QueryWrapper.create()
|
||||
.eq(ApprovalInstance::getId, instanceId)
|
||||
.eq(ApprovalInstance::getTenantId, account.getTenantId()));
|
||||
if (instance == null || !Objects.equals(account.getTenantId(), instance.getTenantId())) {
|
||||
throw new BusinessException(404, 404, "审批实例不存在");
|
||||
}
|
||||
List<ApprovalTask> tasks = approvalTaskMapper.selectListByQuery(
|
||||
QueryWrapper.create().eq(ApprovalTask::getInstanceId, instanceId));
|
||||
Set<BigInteger> roleIds = approvalAssigneeService.getAvailableRoleIds(account.getId());
|
||||
assertDetailAccess(instance, tasks, account, roleIds);
|
||||
|
||||
ApprovalInstanceDetailVo detail = new ApprovalInstanceDetailVo();
|
||||
detail.setId(instance.getId());
|
||||
detail.setFlowId(instance.getFlowId());
|
||||
@@ -152,12 +171,10 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
|
||||
detail.setFinishedAt(instance.getFinishedAt());
|
||||
detail.setSnapshotJson(instance.getSnapshotJson());
|
||||
|
||||
List<ApprovalTask> tasks = approvalTaskMapper.selectListByQuery(
|
||||
QueryWrapper.create().eq(ApprovalTask::getInstanceId, instanceId));
|
||||
List<ApprovalLog> logs = approvalLogMapper.selectListByQuery(
|
||||
QueryWrapper.create().eq(ApprovalLog::getInstanceId, instanceId));
|
||||
Map<Integer, ApprovalFlowStepVo> frozenStepMap = resolveFrozenStepMap(instance);
|
||||
Map<BigInteger, SysAccount> accountMap = loadAccountMap(instance, tasks, logs);
|
||||
Map<BigInteger, SysAccount> accountMap = loadAccountMap(instance, tasks, logs, account.getTenantId());
|
||||
detail.setApplicantName(resolveAccountName(accountMap.get(instance.getApplicantId())));
|
||||
detail.setApplicantAccount(resolveAccountLoginName(accountMap.get(instance.getApplicantId())));
|
||||
|
||||
@@ -201,8 +218,6 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
|
||||
})
|
||||
.collect(Collectors.toList()));
|
||||
|
||||
LoginAccount account = requireLoginAccount();
|
||||
Set<BigInteger> roleIds = approvalAssigneeService.getAvailableRoleIds(account.getId());
|
||||
boolean active = !ApprovalInstanceStatus.from(instance.getStatus()).isFinished();
|
||||
boolean canReview = active
|
||||
&& tasks.stream().anyMatch(item -> item.getStepNo().equals(instance.getCurrentStepNo())
|
||||
@@ -220,10 +235,11 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
|
||||
* @param instance 审批实例
|
||||
* @param tasks 审批任务列表
|
||||
* @param logs 审批日志列表
|
||||
* @param tenantId 当前租户 ID
|
||||
* @return 账号 ID 到账号实体的映射
|
||||
*/
|
||||
private Map<BigInteger, SysAccount> loadAccountMap(ApprovalInstance instance, List<ApprovalTask> tasks,
|
||||
List<ApprovalLog> logs) {
|
||||
List<ApprovalLog> logs, BigInteger tenantId) {
|
||||
Set<BigInteger> accountIds = new HashSet<>();
|
||||
if (instance.getApplicantId() != null) {
|
||||
accountIds.add(instance.getApplicantId());
|
||||
@@ -239,7 +255,9 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
|
||||
if (CollectionUtil.isEmpty(accountIds)) {
|
||||
return Map.of();
|
||||
}
|
||||
return sysAccountService.listByIds(accountIds).stream()
|
||||
return sysAccountService.list(QueryWrapper.create()
|
||||
.in(SysAccount::getId, accountIds)
|
||||
.eq(SysAccount::getTenantId, tenantId)).stream()
|
||||
.collect(Collectors.toMap(
|
||||
SysAccount::getId,
|
||||
account -> account,
|
||||
@@ -297,6 +315,7 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
|
||||
boolean pendingMode, LoginAccount account, Set<BigInteger> roleIds) {
|
||||
Page<ApprovalInstance> page = approvalInstanceMapper.paginate(pageNumber, pageSize, queryWrapper);
|
||||
List<ApprovalInstance> records = page.getRecords();
|
||||
Map<BigInteger, SysAccount> applicantAccountMap = loadApplicantAccountMap(records, account.getTenantId());
|
||||
Set<BigInteger> pendingTaskInstanceIds = pendingMode
|
||||
? approvalAssigneeService.listPendingInstanceIds(account.getId(), roleIds,
|
||||
records.stream().map(ApprovalInstance::getId).collect(Collectors.toList()))
|
||||
@@ -315,6 +334,9 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
|
||||
item.setSummary(record.getSummary());
|
||||
item.setApplicationReason(record.getApplicationReason());
|
||||
item.setApplicantId(record.getApplicantId());
|
||||
SysAccount applicantAccount = applicantAccountMap.get(record.getApplicantId());
|
||||
item.setApplicantName(resolveAccountName(applicantAccount));
|
||||
item.setApplicantAccount(resolveAccountLoginName(applicantAccount));
|
||||
item.setSubmittedAt(record.getSubmittedAt());
|
||||
item.setFinishedAt(record.getFinishedAt());
|
||||
boolean active = !ApprovalInstanceStatus.from(record.getStatus()).isFinished();
|
||||
@@ -332,6 +354,31 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
|
||||
return voPage;
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量加载分页记录中的申请人账号,避免列表逐行查询。
|
||||
*
|
||||
* @param records 审批实例分页记录
|
||||
* @param tenantId 当前租户 ID
|
||||
* @return 申请人 ID 到账号实体的映射
|
||||
*/
|
||||
private Map<BigInteger, SysAccount> loadApplicantAccountMap(List<ApprovalInstance> records, BigInteger tenantId) {
|
||||
Set<BigInteger> applicantIds = records.stream()
|
||||
.map(ApprovalInstance::getApplicantId)
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toSet());
|
||||
if (CollectionUtil.isEmpty(applicantIds)) {
|
||||
return Map.of();
|
||||
}
|
||||
return sysAccountService.list(QueryWrapper.create()
|
||||
.in(SysAccount::getId, applicantIds)
|
||||
.eq(SysAccount::getTenantId, tenantId)).stream()
|
||||
.collect(Collectors.toMap(
|
||||
SysAccount::getId,
|
||||
account -> account,
|
||||
(left, right) -> left,
|
||||
LinkedHashMap::new));
|
||||
}
|
||||
|
||||
private long safePageNumber(Long pageNumber) {
|
||||
return pageNumber == null || pageNumber < 1 ? 1L : pageNumber;
|
||||
}
|
||||
@@ -342,12 +389,39 @@ public class ApprovalQueryServiceImpl implements ApprovalQueryService {
|
||||
|
||||
private LoginAccount requireLoginAccount() {
|
||||
LoginAccount account = SaTokenUtil.getLoginAccount();
|
||||
if (account == null) {
|
||||
if (account == null || account.getId() == null || account.getTenantId() == null) {
|
||||
throw new BusinessException("当前未登录");
|
||||
}
|
||||
return account;
|
||||
}
|
||||
|
||||
/**
|
||||
* 校验审批详情的主体权限。
|
||||
*
|
||||
* @param instance 审批实例
|
||||
* @param tasks 审批任务
|
||||
* @param account 当前账号
|
||||
* @param roleIds 当前账号的有效角色 ID
|
||||
* @throws BusinessException 当前用户不是申请人、处理人、同租户超管或资源授权者时抛出
|
||||
*/
|
||||
private void assertDetailAccess(ApprovalInstance instance,
|
||||
List<ApprovalTask> tasks,
|
||||
LoginAccount account,
|
||||
Set<BigInteger> roleIds) {
|
||||
if (account.getId().equals(instance.getApplicantId())
|
||||
|| categoryPermissionService.isSuperAdmin(account)) {
|
||||
return;
|
||||
}
|
||||
boolean taskParticipant = tasks.stream().anyMatch(task -> account.getId().equals(task.getActedBy())
|
||||
|| ApprovalTaskStatus.PENDING.getCode().equals(task.getStatus())
|
||||
&& approvalAssigneeService.canHandleTask(task, account.getId(), roleIds));
|
||||
if (taskParticipant
|
||||
|| approvalActionFacade.canAccessApprovalDetail(instance.getResourceType(), instance.getResourceId())) {
|
||||
return;
|
||||
}
|
||||
throw new BusinessException(403, 403, "无权限查看该审批实例");
|
||||
}
|
||||
|
||||
private String resolveCurrentStepName(ApprovalInstance instance) {
|
||||
Map<Integer, ApprovalFlowStepVo> stepMap = resolveFrozenStepMap(instance);
|
||||
return resolveStepName(stepMap, instance.getCurrentStepNo());
|
||||
|
||||
Reference in New Issue
Block a user