一、引言
在大多数企业级软件系统中,"订单"是承载业务闭环的核心实体,而"审核"则是保障业务合规性、风控和质量控制的关键环节。无论是电商下单、SaaS 订阅、金融放款申请,还是企业内部采购审批,**"订单 + 审核"**几乎是所有业务流的标配。
很多团队在项目初期往往简单地在订单表上加几个状态字段和一个"审核人"字段,随着业务复杂度上升,会逐渐出现:
- 审核逻辑散落在各个 Service / Controller 里,难以维护;
- 新增一个审核节点或规则,需要大范围改代码;
- 审核链路不透明,无法追溯每一个决策;
- 多系统间(如 OMS、CRM、财务系统)审核状态不同步。
本文将围绕**"软件系统的订单-审核业务架构"**展开分析,从问题定义、架构设计思路到技术实现,结合示例代码和实际经验,帮助你搭建一个可扩展、易维护、可观测的订单审核体系。
二、问题与背景:订单-审核的典型业务场景
2.1 典型场景
-
电商平台
- 用户下单 → 风控系统审核(是否高风险) → 人工客服审核特殊订单 → 仓储发货。
-
金融/信贷系统
- 贷款申请订单 → 反欺诈引擎初审 → 信审人员复审 → 资金放款。
-
企业采购系统
- 部门提交采购订单 → 部门经理审核 → 财务审核 → 总监/CEO 审批大额订单。
这些场景的共同特点:
- 多步骤审核:通常不是简单的"通过/拒绝",而是一条带条件分支的审核链路;
- 多角色参与:系统审核、人审、多级管理者、外部第三方;
- 高合规要求:过程要可追溯,操作要可审计。
2.2 常见痛点
-
审核流程写死在代码里
比如:
lessif (order.getAmount().compareTo(new BigDecimal("10000")) > 0) { // 需要经理审批 } else { // 主管审批 }当组织架构或规则变化时,要改一堆 if-else,风险极高。
-
状态混乱
- 订单状态与审核状态耦合在一个字段,比如:
CREATED/APPROVED/REJECTED/SHIPPED。 - 导致订单生命周期与审核生命周期混在一起,很难表达"订单已生效但还在某个部分审核"等中间态。
- 订单状态与审核状态耦合在一个字段,比如:
-
缺乏审核轨迹与审计能力
- 只在订单表里记录当前审核状态,没有详细的审核记录(谁在什么时间通过了什么节点)。
- 做审计、风控回溯、纠纷处理时很困难。
-
多系统协同困难
- 订单系统、支付系统、风控系统、CRM 系统都需要审核信息,但没有统一的审核域模型和事件流。
三、总体架构设计思路
3.1 概念分离:订单域与审核域
首先应在领域建模层面明确区分:订单(Order)与审核(Approval/Review)是两个不同的"子域" :
- 订单域(Order Domain) :聚焦于业务本身,如商品信息、金额、数量、用户、支付方式、发货地址等,以及订单生命周期(创建、支付、发货、完成等)。
- 审核域(Approval Domain) :聚焦于"对某个业务对象进行审核"的通用能力,与"订单"只是通过关联关系联系起来。
这种分离带来两个好处:
- 可以复用审核能力到其他业务对象(如合同、退款、客户资质);
- 可以解耦订单的业务变动与审核规则变动。
在数据建模上,通常体现为:
order表:记录订单基础信息;approval_instance(审核实例表):记录一次完整的审核流程;approval_task(审核任务表):记录每个节点、每个参与者的具体审批动作;- 通过
biz_type+biz_id将审核实例与各种业务对象关联,如订单。
3.2 基本架构组件
一个典型的订单-审核业务架构,可以包括以下组件:
-
订单服务(Order Service)
- 负责订单的创建、变更和查询。
- 触发审核流程:在特定业务节点(如订单创建、支付前)创建审核实例。
-
审核服务(Approval Service)
- 对所有可审核对象提供统一的审核能力。
- 负责审核流程定义、审核实例管理、审核任务分发、规则执行、状态维护。
-
规则引擎 / 决策引擎
- 对复杂的业务条件(如金额区间、地区、客户标签等)进行判断,决策下一步的审核路径。
- 可以采用 Drools、规则表达式引擎、自研 DSL 等。
-
通知与协同系统
- 将审核任务分发给对应的角色或人员(如站内信、邮件、IM 消息)。
- 记录订单和审核相关事件(Event Log)。
-
监控与审计
- 提供审核相关的统计报表(通过率、平均审核时间、异常率等)。
- 支持审计查询和合规检查。
总体架构可以采用微服务或模块化单体,各模块之间通过领域事件(Domain Event)或消息总线进行解耦。
四、技术实现:从数据模型到流程控制
下面以一种常见的实现思路为例(以 Java/Spring + MySQL 为例),展示从数据模型到核心流程代码的实现。
4.1 核心数据模型设计
4.1.1 订单表(order)
sql
CREATE TABLE `order` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`order_no` VARCHAR(64) NOT NULL UNIQUE,
`user_id` BIGINT NOT NULL,
`amount` DECIMAL(18,2) NOT NULL,
`currency` VARCHAR(16) NOT NULL DEFAULT 'CNY',
`status` VARCHAR(32) NOT NULL, -- 订单业务状态:CREATED / PAID / SHIPPED / COMPLETED / CANCELED ...
`need_approval` TINYINT(1) NOT NULL DEFAULT 0, -- 是否需要审核
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
`ext` JSON NULL -- 预留扩展字段
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
注意:订单表只记录自身业务状态,不直接混入"审核通过/拒绝"等语义,审核结果通过事件或回调更新订单。
4.1.2 审核实例表(approval_instance)
sql
CREATE TABLE `approval_instance` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`instance_no` VARCHAR(64) NOT NULL UNIQUE,
`biz_type` VARCHAR(32) NOT NULL, -- 业务类型,如 ORDER / CONTRACT / REFUND ...
`biz_id` BIGINT NOT NULL, -- 业务主键ID
`status` VARCHAR(32) NOT NULL, -- 审核实例状态:PENDING / APPROVED / REJECTED / CANCELED
`current_node` VARCHAR(64) NULL, -- 当前所在节点编码
`definition_id` BIGINT NOT NULL, -- 审核流程定义ID
`created_by` BIGINT NOT NULL, -- 创建人(例如下单用户ID或系统ID)
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL,
`ext` JSON NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.1.3 审核任务表(approval_task)
sql
CREATE TABLE `approval_task` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`instance_id` BIGINT NOT NULL, -- 审核实例ID
`node_code` VARCHAR(64) NOT NULL, -- 所在节点编码
`assignee_type` VARCHAR(32) NOT NULL, -- 指派类型:USER / ROLE / GROUP / SYSTEM
`assignee_id` VARCHAR(64) NOT NULL, -- 用户ID或角色编码等
`status` VARCHAR(32) NOT NULL, -- 任务状态:PENDING / APPROVED / REJECTED / SKIPPED
`comment` VARCHAR(512) NULL,
`created_at` DATETIME NOT NULL,
`completed_at` DATETIME NULL,
`ext` JSON NULL,
KEY `idx_instance` (`instance_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4.1.4 审核流程定义表(approval_definition、approval_node)
建议抽象出"流程定义"与"流程实例":
- 流程定义:描述一个业务类型对应到哪些审核节点(比如:初审 → 复审 → 财务)。
- 流程实例:基于流程定义创建的一次具体审核过程。
流程定义可存在 DB,也可以存在配置中心,甚至可以接入 BPMN 引擎(如 Flowable、Camunda)。此处给出一个简化版字段示例:
r
CREATE TABLE `approval_definition` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`biz_type` VARCHAR(32) NOT NULL,
`name` VARCHAR(128) NOT NULL,
`is_enabled` TINYINT(1) NOT NULL DEFAULT 1,
`config_json` JSON NOT NULL, -- 存储节点和路由的配置
`created_at` DATETIME NOT NULL,
`updated_at` DATETIME NOT NULL
);
config_json 中可以配置节点顺序、条件路由、参与人规则等。例如:
css
{
"nodes": [
{
"code": "RISK_CHECK",
"name": "风控审核",
"assigneeType": "ROLE",
"assigneeId": "RISK_OPERATOR"
},
{
"code": "MANAGER_APPROVE",
"name": "经理审批",
"assigneeType": "ROLE",
"assigneeId": "MANAGER",
"condition": "order.amount > 10000"
}
]
}
复杂场景可接入规则引擎评估
condition。
4.2 审核流程的发起与流转
4.2.1 发起审核:订单服务侧
在订单创建逻辑中,根据业务规则判断是否需要审核,并在需要时调用审核服务:
scss
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private ApprovalClient approvalClient;
public Order createOrder(CreateOrderRequest request, Long userId) {
Order order = new Order();
order.setOrderNo(generateOrderNo());
order.setUserId(userId);
order.setAmount(request.getAmount());
order.setStatus(OrderStatus.CREATED.name());
order.setCreatedAt(LocalDateTime.now());
order.setUpdatedAt(LocalDateTime.now());
// 业务规则判断是否需要审核
boolean needApproval = request.getAmount().compareTo(new BigDecimal("5000")) > 0;
order.setNeedApproval(needApproval ? 1 : 0);
orderRepository.save(order);
if (needApproval) {
// 调用审核服务发起审核
approvalClient.startApproval(
"ORDER", // bizType
order.getId(), // bizId
userId, // 发起人
buildOrderContext(order) // 上下文信息供规则评估使用
);
} else {
// 无需审核的场景可直接进入下一步业务逻辑,例如自动确认
autoConfirmOrder(order);
}
return order;
}
private Map<String, Object> buildOrderContext(Order order) {
Map<String, Object> ctx = new HashMap<>();
ctx.put("amount", order.getAmount());
ctx.put("userId", order.getUserId());
// 其他用于规则引擎的字段
return ctx;
}
}
4.2.2 审核服务:创建审核实例与任务
审核服务接收到发起请求后:
- 根据
biz_type找到对应的流程定义; - 针对该订单(或其他业务对象)创建一个
approval_instance; - 根据流程定义的第一个节点,生成相应的
approval_task并分配给对应的角色/人员。
示例(伪代码):
scss
@Service
public class ApprovalService {
@Autowired
private ApprovalDefinitionRepository definitionRepo;
@Autowired
private ApprovalInstanceRepository instanceRepo;
@Autowired
private ApprovalTaskRepository taskRepo;
@Autowired
private RuleEngine ruleEngine;
@Autowired
private DomainEventPublisher eventPublisher;
public ApprovalInstance startApproval(String bizType, Long bizId, Long initiator, Map<String, Object> context) {
ApprovalDefinition def = definitionRepo.findEnabledByBizType(bizType)
.orElseThrow(() -> new IllegalStateException("No approval definition for " + bizType));
// 1. 创建实例
ApprovalInstance instance = new ApprovalInstance();
instance.setInstanceNo(generateInstanceNo());
instance.setBizType(bizType);
instance.setBizId(bizId);
instance.setDefinitionId(def.getId());
instance.setStatus(ApprovalStatus.PENDING.name());
instance.setCreatedBy(initiator);
instance.setCreatedAt(LocalDateTime.now());
instance.setUpdatedAt(LocalDateTime.now());
instanceRepo.save(instance);
// 2. 解析流程定义,定位第一个节点
ApprovalFlowConfig flowConfig = parseConfig(def.getConfigJson());
ApprovalNode firstNode = flowConfig.getFirstNode(context, ruleEngine);
if (firstNode == null) {
// 没有需要审核的节点,直接视为通过
instance.setStatus(ApprovalStatus.APPROVED.name());
instanceRepo.save(instance);
// 发布审核通过事件,通知订单服务等
publishApprovalResultEvent(instance, true, "AUTO_PASS_NO_NODE");
return instance;
}
instance.setCurrentNode(firstNode.getCode());
instanceRepo.save(instance);
// 3. 创建任务
List<ApprovalTask> tasks = createTasksForNode(instance, firstNode, context);
taskRepo.saveAll(tasks);
// 4. 发布事件
eventPublisher.publish(new ApprovalStartedEvent(instance.getId(), bizType, bizId));
return instance;
}
private List<ApprovalTask> createTasksForNode(ApprovalInstance instance,
ApprovalNode node,
Map<String, Object> context) {
// 根据 node.assigneeType 决定分配方式
// 简化处理:只支持 ROLE 类型,后续可以扩展至 GROUP / USER / SYSTEM 等
List<String> assignees = resolveAssignee(node, context);
List<ApprovalTask> tasks = new ArrayList<>();
for (String assignee : assignees) {
ApprovalTask task = new ApprovalTask();
task.setInstanceId(instance.getId());
task.setNodeCode(node.getCode());
task.setAssigneeType(node.getAssigneeType().name());
task.setAssigneeId(assignee);
task.setStatus(TaskStatus.PENDING.name());
task.setCreatedAt(LocalDateTime.now());
tasks.add(task);
}
return tasks;
}
}
4.2.3 审核通过/拒绝:任务完成与实例状态更新
审核人员在前端操作"同意/拒绝"后,系统需要:
- 更新对应的
approval_task状态; - 判断当前节点是否所有任务都完成;
- 决定是否流转到下一个节点,或结束实例;
- 审核结束时,通过事件通知订单服务进行后续业务处理。
简单示例:
scss
public void completeTask(Long taskId, Long operatorId, boolean approved, String comment) {
ApprovalTask task = taskRepo.findById(taskId)
.orElseThrow(() -> new IllegalArgumentException("Task not found"));
if (!TaskStatus.PENDING.name().equals(task.getStatus())) {
throw new IllegalStateException("Task is not pending");
}
// 权限检查:operatorId 是否符合 task.assignee
checkPermission(task, operatorId);
task.setStatus(approved ? TaskStatus.APPROVED.name() : TaskStatus.REJECTED.name());
task.setComment(comment);
task.setCompletedAt(LocalDateTime.now());
taskRepo.save(task);
ApprovalInstance instance = instanceRepo.findById(task.getInstanceId())
.orElseThrow(() -> new IllegalArgumentException("Instance not found"));
// 如果当前节点任何一个任务被拒绝,则直接驳回整个实例
if (!approved) {
instance.setStatus(ApprovalStatus.REJECTED.name());
instance.setUpdatedAt(LocalDateTime.now());
instanceRepo.save(instance);
// 发布"审核拒绝"事件,供订单等业务服务处理
publishApprovalResultEvent(instance, false, "REJECTED_BY_TASK");
return;
}
// 若该节点是会签模式,需要所有任务通过才可流转
if (!allTasksApprovedOfNode(instance.getId(), task.getNodeCode())) {
return; // 等待其他任务完成
}
// 所有任务已通过,流转到下一节点或结束
goNextNodeOrFinish(instance);
}
private void goNextNodeOrFinish(ApprovalInstance instance) {
ApprovalDefinition def = definitionRepo.findById(instance.getDefinitionId())
.orElseThrow();
ApprovalFlowConfig flowConfig = parseConfig(def.getConfigJson());
// 使用上下文 + 规则引擎决定下一节点
Map<String, Object> context = loadBizContext(instance); // 从订单等业务对象加载
ApprovalNode nextNode = flowConfig.getNextNode(instance.getCurrentNode(), context, ruleEngine);
if (nextNode == null) {
// 没有下一节点,视为整条审核流程通过
instance.setStatus(ApprovalStatus.APPROVED.name());
instance.setCurrentNode(null);
instance.setUpdatedAt(LocalDateTime.now());
instanceRepo.save(instance);
publishApprovalResultEvent(instance, true, "ALL_NODES_PASSED");
} else {
// 进入下一节点
instance.setCurrentNode(nextNode.getCode());
instance.setUpdatedAt(LocalDateTime.now());
instanceRepo.save(instance);
List<ApprovalTask> tasks = createTasksForNode(instance, nextNode, context);
taskRepo.saveAll(tasks);
}
}
4.3 订单服务如何消费审核结果
订单服务可以通过以下方式与审核服务解耦:
- 同步回调(适合简单系统)
- 消息队列 / 事件总线(推荐)
示例:监听审核事件并更新订单状态(伪代码):
scss
@Component
public class OrderApprovalListener {
@Autowired
private OrderRepository orderRepository;
@EventListener
public void onApprovalResult(ApprovalResultEvent event) {
if (!"ORDER".equals(event.getBizType())) {
return;
}
Order order = orderRepository.findById(event.getBizId())
.orElseThrow(() -> new IllegalStateException("Order not found"));
if (event.isApproved()) {
// 审核通过,更新订单业务状态
order.setStatus(OrderStatus.APPROVED.name());
// 可以进一步触发支付/发货流程
} else {
// 审核拒绝,订单取消或进入"审核拒绝"状态
order.setStatus(OrderStatus.REJECTED.name());
}
order.setUpdatedAt(LocalDateTime.now());
orderRepository.save(order);
}
}
通过这种事件驱动的方式,可以让审核结果"广播"给多个关心的系统,而不是只面向订单服务。
4.4 规则引擎与可配置化审核
随着业务发展,审核条件会非常复杂且频繁变更,如果全部硬编码在 Java 代码里,维护成本极高。常见做法是引入规则引擎 或策略表驱动:
-
使用 Drools 或类似规则引擎,将规则定义为 DRL 文件,按数据驱动执行;
-
自研简单 DSL/表达式,在配置表中存储规则表达式(例如 SpEL、QLExpress、MVEL 等);
-
通过"配置 + 规则"决定:
- 是否需要某个审核节点;
- 审核节点应该由谁来处理;
- 审核通过/拒绝的条件。
例如在 approval_definition.config_json 中:
css
{
"nodes": [
{
"code": "RISK_CHECK",
"name": "风控审核",
"assigneeType": "ROLE",
"assigneeId": "RISK_OPERATOR",
"enableCondition": "amount >= 1000"
},
{
"code": "MANAGER_CHECK",
"name": "大额订单经理审批",
"assigneeType": "ROLE",
"assigneeId": "MANAGER",
"enableCondition": "amount >= 10000"
}
]
}
在节点选择逻辑中,对 enableCondition 进行评估,决定是否启用该节点。
4.5 审核轨迹与审计能力
为满足审计、合规、风控需求,审核系统应提供:
-
完整的轨迹记录
- 审核实例维度:包括发起时间、结束时间、最终结果;
- 审核任务维度:包括每个节点的处理人、处理时间、意见、操作 IP/终端。
-
变更历史
- 当审核规则、流程定义发生变更时,应记录版本,并能够回溯特定订单在当时执行的规则版本。
-
只读接口和报表
- 提供查询接口:按时间、业务类型、审核结果、处理人等条件查询;
- 提供统计报表:通过率、平均处理时长、按角色/部门的工作量统计等。
可考虑设计一张操作日志表:
sql
CREATE TABLE `approval_operation_log` (
`id` BIGINT PRIMARY KEY AUTO_INCREMENT,
`instance_id` BIGINT NOT NULL,
`task_id` BIGINT NULL,
`operator_id` BIGINT NOT NULL,
`operation` VARCHAR(64) NOT NULL, -- e.g. START / APPROVE / REJECT / TRANSFER / ADD_NODE ...
`detail` JSON NULL,
`created_at` DATETIME NOT NULL
);
五、技术选型与架构模式分析
5.1 优点分析
-
解耦性强
- 订单域与审核域分离,审核能力可复用于各种业务对象;
- 使用事件驱动,可方便接入更多下游系统(如通知、BI)。
-
可扩展性高
- 通过流程定义和规则引擎,可以以"配置 + 规则"方式演进审核流程,而无需频繁修改代码;
- 支持多种审核模式:单人审批、会签、或签、加签、转审等。
-
可观测性与审计能力好
- 审核实例、审核任务、操作日志分表记录,轨迹清晰;
- 易于为风险管理、合规审核提供数据支持。
-
易于多系统协同
- 统一的审核域模型,使 OMS、CRM、财务等各系统采用相同语义和事件,减少系统对齐成本。
5.2 缺点与挑战
-
系统复杂度提高
- 引入额外的审核服务、规则引擎、消息系统,部署和运维复杂度上升;
- 开发人员需要理解审核域的概念和规范,学习成本增加。
-
性能与延迟
- 引入审核流程必然增加订单从创建到生效的链路时长,尤其是多人人工审核时延明显;
- 在高并发场景,要谨慎设计审核服务的性能和扩展性。
-
一致性与幂等性问题
- 订单状态与审核状态通过事件异步同步,需要考虑幂等处理、防止消息丢失或重复;
- 对于强一致性要求非常高的场景,可能需要引入分布式事务或 TCC 模式。
-
规则管理风险
- 若规则可在线配置,必须有严格的发布流程、灰度机制和回滚能力;
- 不当的规则配置可能导致大面积误审或过审,风险较大。
5.3 实际应用中的建议
-
从简单到复杂,循序渐进
- 初期可以只支持固定的 1--2 个审核节点,不引入复杂的规则引擎;
- 随着业务复杂度上升,再逐步引入流程定义版本化、规则引擎、可视化流程编排等。
-
区分"高价值订单"与"普通订单"
- 针对金额低、风险低的订单,可尽量走自动化审核或免审流程,以提升用户体验;
- 将有限的人力集中在高价值、高风险订单审核上。
-
设计好审核接口与事件契约
- 对外只暴露少量、稳定的接口(如:发起审核 / 查询审核状态 / 完成任务);
- 使用事件总线(如 Kafka、RocketMQ)传递
ApprovalResultEvent等领域事件,并制定清晰的事件 schema。
-
加强运营后台和审核工具建设
- 为审核人员提供高效的工作台和任务队列功能;
- 提供灵活的筛选和批量处理能力;
- 尽量将"规则调优"的能力放到产品、风控、运营手中,而不必每次改代码。
-
重视安全与合规
- 审核操作通常涉及重要业务决策(比如是否放款、是否发货),要有完备的权限体系、操作日志、风控防护;
- 对敏感字段(例如身份证、银行卡号)要做脱敏显示和访问控制。
六、结论:订单-审核业务架构的价值与演进方向
订单-审核业务架构,是企业核心业务链路中的"安全阀"和"质量闸门"。合理的架构设计可以在以下方面带来显著价值:
- 提高业务灵活性:通过可配置的流程和规则,快速适应组织架构变化、市场策略和监管要求;
- 提升系统可维护性:解耦订单与审核逻辑,审核作为独立的子域/服务,避免核心代码被大量 if-else 污染;
- 增强合规与风控能力:全面记录审核轨迹,实现责任可追溯,为审计和纠纷处理提供有力依据;
- 支撑多业务、多系统复用:同一套审核能力可以支撑订单、退款、合同、客户资质等不同业务对象。
未来的发展方向主要包括:
-
审核流程"编排化、可视化"
- 更广泛地引入 BPMN 引擎、Low-Code 编排平台,使业务人员可以直接编排审核流程。
-
审核规则智能化
- 借助机器学习、风控模型,对高频场景给出自动审核决策和策略推荐,减少人工参与。
-
跨系统统一审核平台
- 在大型企业中,建立统一的"企业级审核平台",将各系统的审批能力集中管理,实现标准化与集中风控。
通过适当的领域建模、技术选型与工程实践,团队可以构建一套既稳定可靠、又高度灵活的订单-审核业务架构,支撑企业业务的长期演进。
七、延伸阅读与参考资料(可选)
以下资料有助于进一步深入理解订单-审核架构、工作流与规则引擎:
-
领域驱动设计与分层架构
- Eric Evans,《Domain-Driven Design: Tackling Complexity in the Heart of Software》
- Vaughn Vernon,《Implementing Domain-Driven Design》
-
工作流与流程引擎
- Flowable 官方文档:flowable.com/open-source...
- Camunda 官方文档:docs.camunda.org/
- Activiti 官方文档:www.activiti.org/documentati...
-
规则引擎与决策系统
- Drools 官方文档:docs.drools.org/
- 《Drools 6 Developer's Guide》
-
事件驱动架构 / 微服务实践
- Martin Fowler,文章 "Event-Driven Architecture":martinfowler.com/articles/20...
- Chris Richardson,《Microservices Patterns》