简介:《OA系统中流程审批数据库的设计》是一份面向企业信息化从业者、OA系统设计与开发人员的数据库设计文档,重点解决流程审批模块中数据建模与表结构规划问题。资源以单个PDF文件呈现,压缩包大小仅35KB,内容精炼而系统。文档围绕流程实例表、活动实例表、审批任务表、规则配置表、历史版本表及用户权限表六大核心模块展开,逐一说明字段设计、状态追踪、审批决策与规则映射的实现思路,并兼顾历史追溯、权限安全与后期扩展性,适合需要在现有OA框架中优化审批数据模型的读者参考。清晰的结构和贴近实际业务的讲解方式,可以帮助读者快速建立流程审批数据库的整体设计框架。目前已有744人学习使用,显示出较高的参考价值。
1. 流程审批数据库设计:OA系统最容易被低估的模块
做过OA系统的人都知道,流程审批模块是真正的黑匣子——表面上就是“提交、审批、归档”三个动作,实际上牵涉到流程实例、活动节点、审批任务、规则路由、历史版本、权限校验一大堆表结构的设计。我拆过不少OA项目,发现一个规律:凡是审批流跑得稳的系统,数据库设计一定在早期就把流程实例表、活动实例表、审批任务表这三者的关系理清楚了;凡是后期改得痛不欲生的,基本都是把审批状态直接塞在业务表里,或者把规则写死在代码里,不敢碰也碰不得。这份《OA系统中流程审批数据库的设计》讲的就是这件事,适合正在做OA系统、或者准备重构审批模块的开发者。它把一套完整的审批流数据库模型拆开给你看,包含流程实例、活动实例、审批任务、规则配置、历史版本、用户权限六张核心表的设计思路,从表结构到关系映射都有覆盖。看完你能直接照着搭一套能跑的审批库,而不是停留在概念层面。
2. 核心表结构设计:流程实例、活动实例与审批任务的职责拆分
2.1 流程实例表:审批流的“主档”该放什么字段
流程实例表是整个审批数据库的锚点。很多人设计这张表的时候容易犯一个错误——什么都往里塞,流程业务数据、当前审批人、审批意见全堆在一起,结果就是表越来越胖,查询越来越慢,业务逻辑还拆不开。按照资料里的思路,流程实例表只做一件事:记录“一次审批从发起到结束”的完整生命周期。它的核心字段包括流程ID、发起人、开始时间、结束时间、状态字段,以及关联业务数据的外键字段。
CREATE TABLE process_instance ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '流程实例ID', process_def_id VARCHAR(64) NOT NULL COMMENT '流程定义ID,对应具体流程模板', business_no VARCHAR(64) NOT NULL COMMENT '业务单号,如订单号、合同编号', business_type VARCHAR(32) NOT NULL COMMENT '业务类型,用于区分不同审批流程', initiator_id BIGINT NOT NULL COMMENT '发起人用户ID', status TINYINT NOT NULL DEFAULT 0 COMMENT '状态:0新建 1进行中 2已完成 3已撤销', start_time DATETIME NOT NULL COMMENT '发起时间', end_time DATETIME NULL COMMENT '结束时间', current_node_id BIGINT NULL COMMENT '当前所在节点ID,方便快速定位', deleted TINYINT NOT NULL DEFAULT 0 COMMENT '逻辑删除标记', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_business (business_type, business_no), KEY idx_initiator (initiator_id, status), KEY idx_status (status, start_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流程实例表';这段设计的核心在于business_no和business_type两个字段的组合。它们的作用是建立审批流程与具体业务数据的桥梁——比如采购审批对应采购单号,合同审批对应合同编号。我在实际项目中习惯再加一个current_node_id字段,用来记录当前进行到的节点,这样查询“我发起的流程现在走到哪了”的时候不需要去活动实例表里子查询,直接拎这个字段就能展示。代价是每次节点流转时需要多一次 UPDATE,但这个代价在绝大多数OA场景下是值得的。
状态字段status建议用 TINYINT 存数字而不是字符串,理由很简单:数字在 InnoDB 里只占 1 字节,而字符串最少也要 4 字节(UTF-8 下的 'done' 是 4 字节)。而且数字状态可以直接放在索引里做范围查询,比如查“所有已完成且在某个时间段内的流程”,走联合索引效率会高不少。
2.2 活动实例表:按步骤记录执行轨迹
活动实例表是容易被忽略的一张表。很多初级设计者在做审批流的时候只建了任务表,觉得有了任务表就能追踪进度,实际上任务表只能告诉你“哪个节点谁审了”,不能告诉你“当前流程到底执行到第几步”。活动实例表解决的是“流程步骤”的追踪问题,它记录的是流程实例在执行过程中的每一个活动节点,相当于流程的“过程日志”。
CREATE TABLE activity_instance ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '活动实例ID', process_instance_id BIGINT NOT NULL COMMENT '所属流程实例ID', activity_def_id VARCHAR(64) NOT NULL COMMENT '活动定义ID,对应流程模板中的节点', activity_name VARCHAR(128) NOT NULL COMMENT '活动名称,如"部门经理审批"', executor_id BIGINT NULL COMMENT '执行人ID,执行人是空的说明是系统自动节点', start_time DATETIME NOT NULL COMMENT '活动开始时间', finish_time DATETIME NULL COMMENT '活动完成时间', status TINYINT NOT NULL DEFAULT 0 COMMENT '活动状态:0待执行 1执行中 2已完成 3已跳过', remark VARCHAR(255) NULL COMMENT '备注,记录跳转原因等', KEY idx_process (process_instance_id, start_time), KEY idx_executor (executor_id, status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='活动实例表';这张表的核心价值在于两点。第一,它能做效率分析——通过计算finish_time和start_time的差值,可以统计出每个节点的平均审批耗时,找出流程中的瓶颈环节。第二,它能支撑流程的复杂度控制——比如我们可以在流程发起时不立即生成所有活动实例,而是只生成当前节点的实例,每完成一步再创建下一个。这种“按需实例化”的方式更适合复杂的OA流程,因为很多审批分支要等前面的条件判断完才能确定要不要走。
状态字段除了常见的待执行、已完成,我建议保留一个已跳过状态。这个状态在处理“或签”“会签”这类复杂节点特别好用。比如会签节点有三个人要审,其中两个人通过就放行,那么第三个人的活动状态就可以标记为“已跳过”,这样既保留了完整轨迹,又不会让统计口径出错。
2.3 审批任务表:把“谁该干什么”落到实处的关键
三张核心表里,审批任务表是跟业务逻辑耦合最深的一张。它的职责是存储每个审批节点的具体任务信息——谁审、审什么、结论是什么、意见是什么。换句话说,活动实例表回答的是“流程走到哪了”,审批任务表回答的是“这一步具体是谁做的、怎么做的”。
CREATE TABLE approval_task ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '任务ID', activity_instance_id BIGINT NOT NULL COMMENT '所属活动实例ID', task_def_id VARCHAR(64) NOT NULL COMMENT '任务定义ID', approver_id BIGINT NOT NULL COMMENT '审批人用户ID', approver_name VARCHAR(64) NOT NULL COMMENT '审批人姓名冗余字段', approve_result TINYINT NULL COMMENT '审批结果:0拒绝 1同意 2驳回 3转交', approve_comment VARCHAR(500) NULL COMMENT '审批意见', task_status TINYINT NOT NULL DEFAULT 0 COMMENT '任务状态:0待处理 1已处理 2已取消', receive_time DATETIME NOT NULL COMMENT '接收时间', handle_time DATETIME NULL COMMENT '处理时间', KEY idx_activity (activity_instance_id), KEY idx_approver (approver_id, task_status), KEY idx_status (task_status, receive_time) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审批任务表';这里有两个设计细节值得展开。第一个是approver_name这个冗余字段。规范化设计理论上不应该存这个名字,查用户表 JOIN 就能拿到,但实际生产中我是建议存上的——审批意见是需要长期留痕的数据,万一员工离职、用户记录被清理,审批记录里的名字不能跟着消失。第二个是approve_result的取值,很多人只设计“同意、拒绝”两个值,我建议加上“驳回”和“转交”。转交是OA里特别常见的操作,比如审批人觉得这事该财务审,把任务转给财务,如果只存“拒绝”,流程逻辑上就得走“不同意”分支,但从业务语义上讲根本不是一回事。
从规范化的角度来看,这套设计的核心是:流程实例表为根,活动实例表为中间层,审批任务表为叶子。这种分层结构最大的好处是支持“并行审批”——多个审批任务挂在同一个活动实例下,系统判断活动是否完成就看挂载的任务状态是否全部满足条件,互不干扰。
3. 规则配置与历史版本:流程灵活性和可追溯性的底层支撑
3.1 规则配置表:用数据库驱动流程走向
设计审批数据库的时候如果只做三张主表,系统只能跑固定流程——A审完给B,B审完给C,结束了。但真实企业环境里的审批流没这么简单:金额超过五万要总监审批,超过二十万要总经理审批,还要经过财务会签;某些特定项目的合同要跳过部门审批直接走法务。这些规则如果写死在代码里,每调整一次都要发版,很不现实。规则配置表的价值,就是把“什么时候走哪个分支、谁能审”这些决策逻辑搬进数据库,让管理员通过后台界面就能调整。
一个可落地的做法是采用“表达式配置 + 解析执行”的方式,而不是设计一堆复杂的条件字段。表达式配置让规则的表达能力更强,而且和具体业务解耦。
CREATE TABLE approval_rule ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '规则ID', process_def_id VARCHAR(64) NOT NULL COMMENT '所属流程定义ID', rule_name VARCHAR(128) NOT NULL COMMENT '规则名称', rule_type TINYINT NOT NULL COMMENT '规则类型:1条件路由 2审批权限控制', condition_expr VARCHAR(500) NOT NULL COMMENT '条件表达式,如 "amount > 50000 && level == 3"', action_expr VARCHAR(500) NOT NULL COMMENT '动作表达式,如 "route_to:node_004" 或 "allow:role_finance"', priority INT NOT NULL DEFAULT 100 COMMENT '优先级,数字小的先执行', enabled TINYINT NOT NULL DEFAULT 1 COMMENT '是否启用', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_process_rule (process_def_id, rule_type, priority) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='审批规则配置表';condition_expr字段是整个设计的重点。我见过两种主流方案,一种是存 JSON 结构,把条件拆成field > value && field2 == value2这样的数组;另一种是直接存可解析的表达式,由规则引擎在运行时解析执行。从灵活性角度,直接存表达式显然是更强的方案——新增条件类型不需要改表结构,只需要让规则引�擎支持新的比较操作符。从安全角度看,存表达式必须警惕注入风险,推荐用安全的表达式解析库,而不要自己用 eval 之类的函数去执行。
rule_type字段把规则分成两类也是为了可维护性。条件路由规则负责决定流程下一步去哪个节点,审批权限规则负责决定当前节点谁能审批。实际项目里,这两类规则的调整频率都挺高,合在一张表里没问题,但通过类型区分能让后台配置界面更清晰。
3.2 历史版本表:为审批留一条后悔药
审批流程最忌讳的就是“数据被覆盖”。举个例子:一份采购合同发起了流程,第一次审批被部门经理驳回,发起人修改后又重新提交,这时候流程实例表里只有一条记录,指向的是最新一次提交。如果运营想知道第一次提交的合同金额是多少、驳回意见是什么,光看主表是看不到的。历史版本表就是为此存在的——每次流程状态发生重大变化,比如驳回修改、节点跳转,都把变化前后的数据快照下来。
CREATE TABLE process_history_version ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '版本ID', process_instance_id BIGINT NOT NULL COMMENT '流程实例ID', version_no INT NOT NULL COMMENT '版本号,从1开始递增', snapshot_data JSON NOT NULL COMMENT '该版本下的完整业务数据快照', change_log VARCHAR(500) NOT NULL COMMENT '变更说明,如"被部门经理驳回后修改合同金额"', operator_id BIGINT NOT NULL COMMENT '操作人ID', created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, KEY idx_process_version (process_instance_id, version_no), KEY idx_operator (operator_id, created_at) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='流程历史版本表';snapshot_data字段用 JSON 类型存储,这是这套设计里比较现代化的一点。流程实例表里的business_no只存了业务单号,具体的业务数据长什么样在主表里是看不到的。用 JSON 快照把每次提交时业务数据的关键字段存下来,后面做审计、做回溯、甚至做个历史版本对比功能都特别方便。在 MySQL 5.7 之后 JSON 类型支持字段提取函数,可以直接在 SQL 里做 JSON 条件过滤,比用 TEXT 存拼接字符串靠谱得多。
版本记录时机也要想清楚。我的做法是在每个审批动作完成后触发一次版本记录——无论是同意、驳回还是转交,这就保证任何时间点的流程状态都能被还原。追加𠕇个细节:版本表不建议存全量大字段,比如合同正文的 PDF 内容不要塞进 JSON 快照,快照只存结构化的关键字段(金额、数量、有效期这些),大文件走对象存储,表里存文件路径就够了。这样做既保证了追溯能力,又控制住了表体积。
3.3 用户表与角色权限表:审批链路上的安全护栏
审批数据库如果只关注流程本身而忽略权限设计,很容�易出现越权审批这类生产事故。用户表和角色权限表的职责就是确保“谁有资格审、谁能看这个单子”。这里的设计要点,是把“用户”和“角色”拆成两张大表,中间用关联表连接,而不是在用户表里塞一个role字段。
CREATE TABLE sys_user ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '用户ID', username VARCHAR(64) NOT NULL UNIQUE COMMENT '登录账号', real_name VARCHAR(64) NOT NULL COMMENT '真实姓名', dept_id BIGINT NOT NULL COMMENT '所属部门ID', enabled TINYINT NOT NULL DEFAULT 1 COMMENT '账号状态', KEY idx_dept (dept_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表'; CREATE TABLE sys_role ( id BIGINT AUTO_INCREMENT PRIMARY KEY COMMENT '角色ID', role_code VARCHAR(64) NOT NULL UNIQUE COMMENT '角色编码,如 ROLE_MANAGER', role_name VARCHAR(64) NOT NULL COMMENT '角色名称', permission_json JSON NOT NULL COMMENT '权限定义,如 {"view": true, "approve": true}' ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表'; CREATE TABLE sys_user_role ( user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, PRIMARY KEY (user_id, role_id), KEY idx_role (role_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户角色关联表';权限模型有 RBAC 和 ABAC 两大流派。资料里的设计思路更接近 RBAC——角色绑定用户,角色拥有固定的操作权限(查看、编辑、审批)。对于大多数OA场景,RBAC 已经够用,而且模型简单,团队容易理解和维护。如果遇到特别复杂的场景,比如“金额超过十万且本人在场时,经理才能审批”,这种就要上 ABAC 策略引擎了——把规则配置表里的condition_expr和permission_json结合来做动态判定。
权限字段用 JSON 存储有一个好处:扩展系统新增操作类型时,不需要做 DDL 迁移。比如原来只有view、approve两种操作,后来要加一个forward(转交)操作,用 JSON 就是加个键值对的事,如果用独立的权限明细表,就要插一批新记录。当然 JSON 的劣势是无法在 SQL 级别做字段约束校验,这个需要在应用层补齐。
4. 避坑指南:审批流数据库设计的五个常见坑
4.1 坑一:把所有业务数据塞进流程实例表
现象:流程实例表越来越胖,光字段就有四十多个,业务扩展一点就要加列。流程和业务的耦合越来越重,通用流程引擎完全做不起来。原因:设计时想着“一次查完,不用关联”,把合同编号、金额、供应商、付款方式等业务字段全部建到了流程实例表里。不同业务类型的字段差异很大,导致表里大量字段对某个业务是 NULL,白占存储空间还把索引搞得很臃肿。解决:流程实例表只保留business_no和business_type,业务细节建模到独立的业务表,通过业务单号关联。泛型设计的好处是新增一个审批类型不用动表结构,只需要新加一张业务表,流程引擎完全不用改。
4.2 坑二:用 DELETE 物理删除审批记录
现象:后台误操作删了一条审批记录,审计查不到记录,财务对账也對不上了,没有任何办法恢复。原因:审批数据本质上属于“电子凭证”,有法律效力和审计要求。当初直接 DELETE 是为了省事,想让表“干净”,但审批表最不需要的就是物理删除。解决:全部换成逻辑删除,加deleted字段默认填 0,删除操作变成 UPDATE 置 1。查询时统一带where deleted = 0过滤。如果需要恢复,一条 UPDATE 就能还原,不用从 binlog 里翻数据。
4.3 坑三:状态字段直接存中文
现象:status字段的值是“新建”“进行中”“已完成”,排序查不出顺序,范围统计也没法做。原因:图省事,觉得开发的时候写中文一眼能看懂。但数据库里用 TINYINT 或 VARCHAR 存数字状态,再用枚举类和字典表映射,才是可维护的设计。解决:状态字段全部改用数字枚举,比如 0-新建、1-进行中、2-已完成、3-已撤销、4-已终止。代码层用枚举常量定义好在查询语句里对照。应用显示层做字典翻译,页面展示还是中文,数据层保持纯净。
4.4 坑四:不建联合索引,按状态查流程巨慢
现象:流程量大之后,“查某个用户待审批的任务”这个高频接口响应超过三秒,数据库 CPU 被打满。原因:只在主键上建了索引,按approver_id+task_status查的时候全表扫描。虽然单看数据量不大,但OA系统里“待办查询”可能是被轮询的接口,频率极高,每次都全表扫,性能就崩了。解决:按高频查询路径建联合索引。待办查询是where approver_id = ? and task_status in ('待处理'),那就在审批任务表上建idx_approver_status (approver_id, task_status)。如果还要按时间排序看“最近待办”,改成idx_approver_status_time (approver_id, task_status, receive_time)。
4.5 坑五:规则配置表用递归自关联
现象:规则配置变成了无限级树形结构,parent_id 指来指去,调用链超长,改一个规则要debug很久。原因:最初想把流程分支做得足够灵活,设计成每层节点都能配多条子规则,表结构变成自关联嵌套。结果是开发和后续维护都崩溃了,因为没人能一眼看出完整的规则链是什么样子。解决:用显式节点编号替代树形递归。流程模板定义好node_001 -> node_002 -> node_003的有向结构,规则表通过condition_expr的“路由目标”字段指定下一个节点,路由逻辑平铺展开,完全不要自关联。表达能力确实比递归设计略弱,但可维护性大幅提升——然后你会发现,绝大多数业务根本用不到那点少出来的灵活性。
5. 索引设计与验证技巧:让你设计的审批库真的能跑
5.1 从索引到分区:把查询性能逼到极限
前面避坑部分提了联合索引,这里再系统过一遍审批场景的索引设计策略。审批流数据库的查询模式非常固定,高频查询就三类:查待办、查已办、查我发起的流程。每类查询的筛选条件不同,联合索引的字段顺序要跟查询条件逐一匹配。
| 查询场景 | 筛选条件 | 建议索引 |
|---|---|---|
| 我的待办 | approver_id + task_status + receive_time | idx_approver_status_time (approver_id, task_status, receive_time) |
| 我已办理 | approver_id + handle_time | idx_approver_handle (approver_id, handle_time) |
| 我发起的流程 | initiator_id + start_time | idx_initiator_time (initiator_id, start_time) |
| 按业务单号查流程 | business_type + business_no | idx_business (business_type, business_no) |
建索引的时候要记住最左前缀原则:idx_approver_status_time这个索引对approver_id + task_status的查询也有效,但如果你直接只用receive_time查,就完全走不上这个索引。所以设计索引时先确认查询语句里 WHERE 条件的顺序,把等值条件的字段放最前面,范围条件的字段放最后面。
数据量超过千万级时,可以考虑对流程实例表按时间做范围分区。分区策略按start_time做 RANGE 分区比较合适,因为审批数据天然有时间属性,归档也方便。举例:按年分区,当月数据落在当年分区,老数据落历史分区,DELETE 清理历史数据时直接 DROP PARTITION 比 DELETE FROM WHERE 快几个数量级,而且不影响在线数据。
ALTER TABLE process_instance PARTITION BY RANGE (YEAR(start_time)) ( PARTITION p2022 VALUES LESS THAN (2023), PARTITION p2023 VALUES LESS THAN (2024), PARTITION p2024 VALUES LESS THAN (2025), PARTITION p_future VALUES LESS THAN MAXVALUE );分区不是银弹,主键必须包含分区键是它的一个限制——上面的例子意味着PRIMARY KEY必须改成PRIMARY KEY (id, start_time)。还有跨分区查询如果没带分区的等值或范围条件,会扫描所有分区,反而更慢。所以分区策略建议在数据量真的到了千万级再用,初期做好联合索引已经能应对绝大多数场景。
5.2 验证设计:用一个SQL跑通端到端审批流
表结构设计完,怎么确认这套库真的能支撑完整审批流?我自己的习惯是写三条 SQL 做端到端验证,能跑通就说明表结构没大问题。第一条验证流程实例创建是否正确:
-- 验证1:创建流程实例后,能正确查到发起人的“我发起的流程” INSERT INTO process_instance (process_def_id, business_no, business_type, initiator_id, status, start_time, current_node_id) VALUES ('PURCHASE_ APPROVAL_001', 'PO-2024-001', 'purchase', 1001, 0, NOW(), NULL); SELECT id, process_def_id, business_no, status FROM process_instance WHERE initiator_id = 1001 AND status = 0; -- 期望结果:返回刚插入的记录,状态为“新建”第二条验证活动实例推进是否连通流程和任务:
-- 验证2:流程推进到下一步,插入新的活动实例和审批任务 INSERT INTO activity_instance (process_instance_id, activity_def_id, activity_name, executor_id, start_time, status) VALUES (1, 'node_002', '部门经理审批', 2001, NOW(), 1); INSERT INTO approval_task (activity_instance_id, task_def_id, approver_id, approver_name, task_status, receive_time) VALUES (1, 'task_002', 2001, '张三', 0, NOW()); -- 关联查询:能看到当前流程节点和待办人 SELECT pi.business_no, ai.activity_name, at.approver_name FROM process_instance pi JOIN activity_instance ai ON pi.id = ai.process_instance_id JOIN approval_task at ON ai.id = at.activity_instance_id WHERE pi.id = 1 AND at.task_status = 0;第三条验证审批完成后流程状态正确关闭:
-- 验证3:任务处理完,活动完成,流程实例标记为“已完成” UPDATE approval_task SET task_status = 1, approve_result = 1, handle_time = NOW() WHERE id = 1 AND approver_id = 2001; UPDATE activity_instance SET status = 2, finish_time = NOW() WHERE id = 1; UPDATE process_instance SET status = 2, end_time = NOW(), current_node_id = NULL WHERE id = 1; -- 确认:流程实例彻底关闭 SELECT id, status, end_time FROM process_instance WHERE id = 1; -- 期望结果:status = 2(已完成),end_time 非空这三条验证能把流程主链路跑通——发起、推进、审批、完成。如果再加上一条驳回分支的验证:任务审批结果为“驳回”,流程实例状态回到某个前置节点,然后循环第二步,基本就能覆盖百分之八十的日常审批场景。
5.3 从设计到落地的几个实操习惯
直接照着资料建库容易忽略两件事——字段注释和事务边界。表结构建好后每张表、每个字段都要写 COMMENT,这不仅是给后来人看的,也是给三个月后的自己看的——审批逻辑牵涉到状态流转,字段含义一旦模糊,后面分析问题就要翻代码。
事务边界更要提前理清楚。审批动作不是一个 UPDATE 就能搞定的,它至少是“插入审批任务记录 → 更新活动状态 → 更新流程实例状态”三步。这三步必须在一个数据库事务里完成,任何一个失败都要回滚。我自己在做这样的功能时会强制要求 DAO 层方法名带上操作标识,比如approveWithTransaction,团队 review 代码的时候一眼就能看出哪个方法是需要事务的。
搭配上面的事务要求,还要在设计阶段就确认一个偏好——审批流表做主从分离还是单库部署。如果公司的OA和业务系统共用同一个库,需要确认表前缀(比如都加oa_前缀)避开命名冲突;如果单独部署,就按独立库来设计,不用考虑业务库的兼容性,表名字段可以更自由一些。这个决定要在建表前拍板,因为后面改起来很痛苦。
做了这么多年 OA 系统项目,我的一个铁律是:任何审批流的表结构变更都必须先出迁移脚本、再改代码逻辑,绝不直接在线改表结构。尤其是遇到像审批任务表这种每日写入量很大的表,用在线 DDL 工具做结构变更比直接 ALTER TABLE 安全得多。从那以后我每次做完表设计,都会强制自己先写一遍端到端的验证脚本,把三张主表的关联查询跑通了再交付——这个习惯帮我挡掉了不少上线后的低级错误,希望也能帮到你。
本文还有配套的精品资源,点击获取