Files
wenzi/src/main/java/com/mosquito/project/permission/ApprovalFlowService.java
Your Name 5342627fde feat(approval): 实现完整的审批流后端服务
- 新增实体类: SysApprovalFlow, SysApprovalRecord, SysApprovalHistory
- 新增Repositories: ApprovalFlowRepository, ApprovalRecordRepository, ApprovalHistoryRepository
- 完整实现ApprovalFlowService: 提交审批、处理审批、取消审批等
- 更新ApprovalController连接实际服务
- 添加单元测试ApprovalFlowServiceTest
- 更新Ralph状态文件 (Phase 3: 90%)
2026-03-05 10:31:21 +08:00

375 lines
14 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package com.mosquito.project.permission;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.*;
/**
* 审批流服务
*/
@Service
public class ApprovalFlowService {
// 审批状态常量
public static final String STATUS_PENDING = "PENDING";
public static final String STATUS_APPROVED = "APPROVED";
public static final String STATUS_REJECTED = "REJECTED";
public static final String STATUS_PROCESSING = "PROCESSING";
public static final String STATUS_CANCELLED = "CANCELLED";
// 审批动作
public static final String ACTION_SUBMIT = "SUBMIT";
public static final String ACTION_APPROVE = "APPROVE";
public static final String ACTION_REJECT = "REJECT";
public static final String ACTION_TRANSFER = "TRANSFER";
private final ApprovalFlowRepository flowRepository;
private final ApprovalRecordRepository recordRepository;
private final ApprovalHistoryRepository historyRepository;
private final ObjectMapper objectMapper;
public ApprovalFlowService(
ApprovalFlowRepository flowRepository,
ApprovalRecordRepository recordRepository,
ApprovalHistoryRepository historyRepository,
ObjectMapper objectMapper) {
this.flowRepository = flowRepository;
this.recordRepository = recordRepository;
this.historyRepository = historyRepository;
this.objectMapper = objectMapper;
}
/**
* 提交审批申请
*/
@Transactional
public Map<String, Object> submitApproval(Long flowId, String bizType, Long bizId,
String title, Long applicantId, String applyReason) {
// 获取审批流程配置
Optional<SysApprovalFlow> flowOpt = flowRepository.findById(flowId);
if (!flowOpt.isPresent()) {
throw new IllegalArgumentException("审批流程不存在: " + flowId);
}
SysApprovalFlow flow = flowOpt.get();
if (!"ENABLED".equals(flow.getStatus())) {
throw new IllegalArgumentException("审批流程已禁用");
}
// 检查是否已有待处理的审批
List<SysApprovalRecord> existing = recordRepository.findByBizTypeAndBizId(bizType, bizId);
for (SysApprovalRecord record : existing) {
if (STATUS_PENDING.equals(record.getStatus()) || STATUS_PROCESSING.equals(record.getStatus())) {
throw new IllegalArgumentException("该业务已存在待处理的审批申请");
}
}
// 解析审批节点,获取第一个审批人
Long firstApproverId = resolveFirstApprover(flow, applicantId);
// 创建审批记录
SysApprovalRecord record = new SysApprovalRecord();
record.setFlowId(flowId);
record.setBizType(bizType);
record.setBizId(bizId);
record.setCurrentNode(0);
record.setApplicantId(applicantId);
record.setCurrentApproverId(firstApproverId);
record.setStatus(STATUS_PROCESSING);
record.setCreatedAt(LocalDateTime.now());
record.setUpdatedAt(LocalDateTime.now());
record = recordRepository.save(record);
// 记录审批历史
SysApprovalHistory history = new SysApprovalHistory();
history.setRecordId(record.getId());
history.setNodeIndex(0);
history.setApproverId(applicantId);
history.setAction(ACTION_SUBMIT);
history.setComment(applyReason);
history.setCreatedAt(LocalDateTime.now());
historyRepository.save(history);
Map<String, Object> result = new HashMap<>();
result.put("recordId", record.getId());
result.put("status", record.getStatus());
result.put("currentNode", record.getCurrentNode());
result.put("currentApproverId", firstApproverId);
return result;
}
/**
* 通过触发事件提交审批
*/
@Transactional
public Map<String, Object> submitApprovalByEvent(String triggerEvent, String bizType, Long bizId,
String title, Long applicantId, String applyReason) {
Optional<SysApprovalFlow> flowOpt = flowRepository.findByTriggerEventAndStatus(triggerEvent, "ENABLED");
if (!flowOpt.isPresent()) {
throw new IllegalArgumentException("未找到触发事件对应的审批流程: " + triggerEvent);
}
return submitApproval(flowOpt.get().getId(), bizType, bizId, title, applicantId, applyReason);
}
/**
* 处理审批
*/
@Transactional
public Map<String, Object> handleApproval(Long recordId, String action, Long operatorId, String comment) {
Optional<SysApprovalRecord> recordOpt = recordRepository.findById(recordId);
if (!recordOpt.isPresent()) {
throw new IllegalArgumentException("审批记录不存在: " + recordId);
}
SysApprovalRecord record = recordOpt.get();
if (!STATUS_PROCESSING.equals(record.getStatus())) {
throw new IllegalArgumentException("审批记录状态不是处理中: " + record.getStatus());
}
// 验证审批人权限
if (!operatorId.equals(record.getCurrentApproverId())) {
throw new IllegalArgumentException("您不是当前审批人,无权处理此审批");
}
// 获取流程配置
Optional<SysApprovalFlow> flowOpt = flowRepository.findById(record.getFlowId());
if (!flowOpt.isPresent()) {
throw new IllegalArgumentException("审批流程配置不存在");
}
SysApprovalFlow flow = flowOpt.get();
// 解析节点配置
List<Map<String, Object>> nodes = parseNodes(flow.getNodes());
int currentNode = record.getCurrentNode();
// 记录审批历史
SysApprovalHistory history = new SysApprovalHistory();
history.setRecordId(recordId);
history.setNodeIndex(currentNode);
history.setApproverId(operatorId);
history.setAction(action);
history.setComment(comment);
history.setCreatedAt(LocalDateTime.now());
historyRepository.save(history);
Map<String, Object> result = new HashMap<>();
if (ACTION_APPROVE.equals(action)) {
// 批准 - 检查是否还有下一个节点
if (currentNode + 1 >= nodes.size()) {
// 审批完成
record.setStatus(STATUS_APPROVED);
record.setUpdatedAt(LocalDateTime.now());
recordRepository.save(record);
result.put("status", STATUS_APPROVED);
result.put("message", "审批已通过");
} else {
// 进入下一个节点
int nextNode = currentNode + 1;
Long nextApproverId = resolveNextApprover(nodes, nextNode, record);
record.setCurrentNode(nextNode);
record.setCurrentApproverId(nextApproverId);
record.setUpdatedAt(LocalDateTime.now());
recordRepository.save(record);
result.put("status", STATUS_PROCESSING);
result.put("currentNode", nextNode);
result.put("currentApproverId", nextApproverId);
result.put("message", "已提交给下一审批人");
}
} else if (ACTION_REJECT.equals(action)) {
// 拒绝
record.setStatus(STATUS_REJECTED);
record.setUpdatedAt(LocalDateTime.now());
recordRepository.save(record);
result.put("status", STATUS_REJECTED);
result.put("message", "审批已拒绝");
} else if (ACTION_TRANSFER.equals(action)) {
// 转交
// 转交目标ID在comment中传递格式: "transfer:userId"
if (comment != null && comment.startsWith("transfer:")) {
Long transferToId = Long.parseLong(comment.substring(9));
record.setCurrentApproverId(transferToId);
record.setUpdatedAt(LocalDateTime.now());
recordRepository.save(record);
result.put("status", STATUS_PROCESSING);
result.put("currentApproverId", transferToId);
result.put("message", "已转交给其他审批人");
} else {
throw new IllegalArgumentException("转交操作需要指定目标审批人,格式: transfer:userId");
}
} else {
throw new IllegalArgumentException("无效的审批动作: " + action);
}
return result;
}
/**
* 获取待审批列表
*/
public List<SysApprovalRecord> getPendingApprovals(Long userId) {
return recordRepository.findPendingByApproverId(userId);
}
/**
* 获取已审批列表(我审批过的)
*/
public List<SysApprovalRecord> getApprovedList(Long userId) {
return recordRepository.findProcessedByApplicantId(userId);
}
/**
* 获取我发起的审批
*/
public List<SysApprovalRecord> getMyApplications(Long userId) {
return recordRepository.findByApplicantId(userId);
}
/**
* 获取审批记录详情
*/
public Map<String, Object> getRecordById(Long recordId) {
Optional<SysApprovalRecord> recordOpt = recordRepository.findById(recordId);
if (!recordOpt.isPresent()) {
return null;
}
SysApprovalRecord record = recordOpt.get();
Map<String, Object> result = new HashMap<>();
result.put("id", record.getId());
result.put("flowId", record.getFlowId());
result.put("bizType", record.getBizType());
result.put("bizId", record.getBizId());
result.put("currentNode", record.getCurrentNode());
result.put("applicantId", record.getApplicantId());
result.put("status", record.getStatus());
result.put("currentApproverId", record.getCurrentApproverId());
result.put("createdAt", record.getCreatedAt());
result.put("updatedAt", record.getUpdatedAt());
// 获取流程信息
Optional<SysApprovalFlow> flowOpt = flowRepository.findById(record.getFlowId());
if (flowOpt.isPresent()) {
SysApprovalFlow flow = flowOpt.get();
result.put("flowName", flow.getFlowName());
result.put("flowCode", flow.getFlowCode());
}
// 获取审批历史
List<SysApprovalHistory> histories = historyRepository.findByRecordIdOrderByNodeIndexDesc(recordId);
result.put("history", histories);
return result;
}
/**
* 获取审批历史
*/
public List<SysApprovalHistory> getApprovalHistory(Long recordId) {
return historyRepository.findByRecordIdOrderByNodeIndexDesc(recordId);
}
/**
* 取消审批
*/
@Transactional
public boolean cancelApproval(Long recordId, Long operatorId) {
Optional<SysApprovalRecord> recordOpt = recordRepository.findById(recordId);
if (!recordOpt.isPresent()) {
throw new IllegalArgumentException("审批记录不存在: " + recordId);
}
SysApprovalRecord record = recordOpt.get();
if (!operatorId.equals(record.getApplicantId())) {
throw new IllegalArgumentException("只有申请人可以取消审批");
}
if (!STATUS_PROCESSING.equals(record.getStatus())) {
throw new IllegalArgumentException("只有处理中的审批可以取消");
}
record.setStatus(STATUS_CANCELLED);
record.setUpdatedAt(LocalDateTime.now());
recordRepository.save(record);
return true;
}
/**
* 获取所有审批流程配置
*/
public List<SysApprovalFlow> getAllFlows() {
return flowRepository.findAll();
}
/**
* 获取启用的审批流程
*/
public List<SysApprovalFlow> getEnabledFlows() {
return flowRepository.findByStatus("ENABLED");
}
/**
* 解析节点配置JSON
*/
@SuppressWarnings("unchecked")
private List<Map<String, Object>> parseNodes(String nodesJson) {
try {
return objectMapper.readValue(nodesJson, List.class);
} catch (JsonProcessingException e) {
throw new IllegalArgumentException("审批节点配置解析失败", e);
}
}
/**
* 解析第一个审批人
*/
private Long resolveFirstApprover(SysApprovalFlow flow, Long applicantId) {
List<Map<String, Object>> nodes = parseNodes(flow.getNodes());
if (nodes.isEmpty()) {
throw new IllegalArgumentException("审批流程节点配置为空");
}
return resolveApproverFromNode(nodes.get(0), applicantId);
}
/**
* 解析下一个审批人
*/
private Long resolveNextApprover(List<Map<String, Object>> nodes, int nodeIndex, SysApprovalRecord record) {
if (nodeIndex >= nodes.size()) {
return null;
}
return resolveApproverFromNode(nodes.get(nodeIndex), record.getApplicantId());
}
/**
* 从节点配置中解析审批人
*/
@SuppressWarnings("unchecked")
private Long resolveApproverFromNode(Map<String, Object> node, Long applicantId) {
String approverType = (String) node.get("approverType");
if ("SELF".equals(approverType)) {
return applicantId;
} else if ("ROLE".equals(approverType)) {
// 根据角色ID查找用户这里需要UserRoleService暂时返回null
return null;
} else if ("DEPARTMENT_HEAD".equals(approverType)) {
// 查找部门负责人需要DepartmentService暂时返回null
return null;
} else if ("SPECIFIC".equals(approverType)) {
// 指定用户
Object approverId = node.get("approverId");
if (approverId != null) {
return ((Number) approverId).longValue();
}
}
// 默认返回null需要手动分配
return null;
}
}