审批规则设计
以下是针对审批人规则表和处理人计算规则的设计方案,支持灵活的多维度审批人分配策略:
一、审批规则表设计(approval_rule)
1. 表结构设计
CREATE TABLE approval_rule (
id BIGINT AUTO_INCREMENT PRIMARY KEY,
process_key VARCHAR(100) NOT NULL COMMENT '流程定义Key',
node_id VARCHAR(50) NOT NULL COMMENT '流程节点ID(如ActivityId)',
assign_type VARCHAR(20) NOT NULL COMMENT '分配类型',
assign_expression VARCHAR(500) NOT NULL COMMENT '分配规则表达式',
fallback_assignee VARCHAR(100) COMMENT '备选处理人(当表达式结果为空时使用)',
tenant_id VARCHAR(20) NOT NULL COMMENT '租户隔离',
version INT DEFAULT 1 COMMENT '规则版本',
is_active BOOLEAN DEFAULT 1 COMMENT '是否启用',
INDEX idx_process_node (process_key, node_id)
);
2. 字段说明
字段 | 说明 |
assign_type | 分配策略类型,枚举值: |
assign_expression | 根据类型存储不同内容(用户ID、角色代码、部门路径等) |
fallback_assignee | 当规则计算失败时的备用处理人(可指定具体用户或管理员角色) |
二、处理人计算规则示例
场景1:直接指定用户
INSERT INTO approval_rule
(process_key, node_id, assign_type, assign_expression, tenant_id)
VALUES
('purchase_high_value', 'approval_step1', 'FIXED_USER', 'user_001', 'T001');
运行时逻辑:直接使用用户ID user_001
作为审批人
场景2:按角色分配
INSERT INTO approval_rule
(process_key, node_id, assign_type, assign_expression, tenant_id)
VALUES
('sales_contract', 'finance_approve', 'ROLE', 'FINANCE_DIRECTOR', 'T001');
运行时逻辑:查询拥有 FINANCE_DIRECTOR
角色的所有用户
场景3:按部门负责人分配
INSERT INTO approval_rule
(process_key, node_id, assign_type, assign_expression, tenant_id)
VALUES
('production_plan', 'dept_approve', 'DEPT_LEADER', 'order.deptCode', 'T001');
运行时逻辑:根据业务单据中的 deptCode
字段,查找该部门的负责人
场景4:逐级上报
INSERT INTO approval_rule
(process_key, node_id, assign_type, assign_expression, tenant_id)
VALUES
('purchase_medium', 'upward_approve', 'UPWARD', '3', 'T001');
运行时逻辑:从申请人开始向上找3级领导(需集成组织架构服务)
场景5:动态表达式
INSERT INTO approval_rule
(process_key, node_id, assign_type, assign_expression, tenant_id)
VALUES
('sales_vip', 'special_approve', 'EXPRESSION',
'userService.findByRoleAndDept("VIP_APPROVER", order.salesDept)', 'T001');
运行时逻辑:执行SpEL表达式,调用userService
查询符合条件的用户
三、处理人计算服务实现
1. 核心计算逻辑(Java示例)
public class AssigneeCalculator {
// 策略模式处理不同分配类型
public List<String> calculate(ApprovalRule rule, Order order) {
switch (rule.getAssignType()) {
case "FIXED_USER":
return Collections.singletonList(rule.getAssignExpression());
case "ROLE":
return userService.findUsersByRole(rule.getAssignExpression());
case "DEPT_LEADER":
String deptCode = order.getDeptCode();
return deptService.getDeptLeaders(deptCode);
case "UPWARD":
int level = Integer.parseInt(rule.getAssignExpression());
return orgStructureService.findUpwardUsers(order.getApplicant(), level);
case "EXPRESSION":
return evalExpression(rule.getAssignExpression(), order);
default:
return handleFallback(rule);
}
}
// 使用Spring EL解析动态表达式
private List<String> evalExpression(String expr, Order order) {
ExpressionParser parser = new SpelExpressionParser();
StandardEvaluationContext context = new StandardEvaluationContext();
context.setVariable("order", order);
context.setVariable("userService", userService);
return parser.parseExpression(expr).getValue(context, List.class);
}
}
2. 计算过程时序图
sequenceDiagram
participant Engine as 流程引擎
participant Service as 审批服务
participant RuleDB as 规则表
participant Org as 组织服务
Engine->>Service: 获取节点审批人(processKey, nodeId, order)
Service->>RuleDB: 查询审批规则
RuleDB-->>Service: 返回rule数据
alt 需要动态计算
Service->>Org: 获取部门负责人/角色用户
Org-->>Service: 返回用户列表
else 表达式计算
Service->>Service: 执行SpEL表达式
end
Service-->>Engine: 返回审批人列表
四、高级设计:可扩展规则引擎
1. 策略接口定义
public interface AssignmentStrategy {
List<String> execute(String expression, Order order);
}
// 注册所有策略
@Component
public class StrategyRegistry {
@Autowired
private Map<String, AssignmentStrategy> strategies;
public AssignmentStrategy getStrategy(String type) {
return strategies.get(type + "Strategy");
}
}
// 示例:角色策略实现
@Component
public class RoleStrategy implements AssignmentStrategy {
@Override
public List<String> execute(String roleCode, Order order) {
return userService.findByRole(roleCode);
}
}
2. 规则表达式语法示例
// 复杂场景:多条件组合
{
"type": "COMPOSITE",
"rules": [
{
"type": "ROLE",
"value": "FINANCE_APPROVER"
},
{
"type": "DEPT_LEADER",
"field": "order.applyDept"
}
],
"logic": "AND" // 或 OR
}
五、安全与性能优化
1. 表达式白名单
// 限制可访问的类和方法
SpelParserConfiguration config = new SpelParserConfiguration(
null,
new SafeExpressionCompiler()
);
public class SafeExpressionCompiler extends SpelCompilerMode {
@Override
public boolean isAllowed(String expression) {
return expression.matches("^[a-zA-Z0-9._()]+$"); // 过滤危险字符
}
}
2. 缓存设计
@Cacheable(value = "approvalRules",
key = "#processKey + ':' + #nodeId + ':' + #tenantId")
public ApprovalRule getActiveRule(String processKey, String nodeId, String tenantId) {
return ruleDao.findActiveRule(processKey, nodeId, tenantId);
}
@Cacheable(value = "roleUsers", key = "#roleCode + ':' + #tenantId")
public List<String> getUsersByRole(String roleCode, String tenantId) {
return userDao.findByRole(roleCode, tenantId);
}
六、最佳实践建议
- 审批链设计
对于需要多人审批的场景,可采用:
INSERT INTO approval_rule
(process_key, node_id, assign_type, assign_expression)
VALUES
('contract_approve', 'multi_step', 'SEQUENTIAL',
'["ROLE:LEGAL_REVIEWER", "DEPT_LEADER:order.applyDept"]');
表示需要依次经过法务审核和部门负责人审批
- 会签/或签支持
添加approval_mode
字段:
ALTER TABLE approval_rule ADD approval_mode VARCHAR(10) DEFAULT 'SINGLE'
COMMENT '审批模式:SINGLE/AND/OR';
AND
:所有审批人同意OR
:任意一人同意即可
- 动态代理审批人
当审批人不在岗时,可配置自动转发规则:
CREATE TABLE approval_delegation (
user_id VARCHAR(20) NOT NULL,
delegate_user_id VARCHAR(20) NOT NULL,
start_time DATETIME,
end_time DATETIME
);
通过这种设计,系统可以实现:
- 灵活配置:支持从简单指定到复杂动态计算的各类场景
- 安全可控:表达式执行沙箱化+白名单机制
- 高性能:关键数据缓存+预编译策略
- 易扩展:策略模式支持新增分配类型