简介:《OA办公系统大数据库设计.doc》是一份针对OA办公自动化管理系统的数据库设计说明书,面向系统设计、开发、验收、评审与测试人员。文档采用MSSQL SERVER 2008 R2,数据库名为OASYSDB/OA系统数据库,旨在将数据分析结果整理成计算机模型,便于开发人员建立物理数据库。内容涵盖数据字典设计和数据库设计两大部分,数据字典以个人信息、报销信息等数据项为例,详细列出人员编号、姓名、性别、部门、岗位、工资等字段的类型、位置与业务含义;数据库设计则覆盖物理结构、表设计、表间关联、存储过程、触发器及Job设计。包体为单个doc文档,约211KB,结构完整、目录清晰,适合作为OA系统数据库设计初学者的参考资料。目前已有144人学习/下载,对理解传统OA系统数据建模与数据字典规范有实用参考价值。
1. 一套OA能不能交付,先看数据库设计扛不扛得住
一套OA办公系统能不能顺利交差,往往不取决于前端页面有多华丽,而取决于数据库设计扛不扛得住业务流程。这份OA办公系统大数据库设计文档,解的就是这个问题——把组织架构、RBAC权限、审批流和公文会议拆成一张接一张可落地的表,从实体关系到字段级约束都列清楚。适合两类人:一类是答辩前需要把“系统”讲明白的学生,另一类是准备从零搭轻量办公平台、不想在表结构上反复返工的开发者。它不是原理讲义,是一份可以直接照着建表、初始化数据、接后端的施工图纸。我拿到后第一时间核对了三处:部门与用户的层级关系、用户与角色的关联方式、流程实例的挂接字段,这三处定了,后面写业务代码基本不用大改。
2. 先拆整体:组织机构、用户与RBAC权限三张核心表怎么设计
2.1 文档的整体分层:先分清哪些表属于“地基”
一份完整的OA数据库设计,通常拆成四个域:组织机构域、权限域、流程域、业务域。组织机构域对应部门和用户,是整棵表的根;权限域管角色和菜单;流程域管审批流定义和流转记录;业务域则是公文、会议、公告这类真正产生业务数据的表。文档一般会先给一张ER总图,让读者一眼看清实体关系。这张图里,最先落地的三张基础表是部门表sys_dept、用户表sys_user、角色表sys_role。
| 表前缀 | 所属域 | 典型表 | 职责 |
|---|---|---|---|
| sys_ | 组织机构与权限 | sys_dept、sys_user、sys_role、sys_menu | 管理组织架构与访问控制 |
| wf_ | 工作流 | wf_process、wf_instance、wf_task、wf_comment | 管理审批流定义、实例与任务 |
| oa_ | 办公业务 | oa_document、oa_meeting、oa_notice | 承载公文、会议、公告等业务数据 |
拿到文档时我的阅读顺序是:先看ER图里哪几张表没有指向别人的外键、只被别人引用,那就是地基。sys_dept是典型顶层表,带一个自关联的pid字段表达层级;sys_user挂在部门之下,存部门ID;sys_role独立存在,不依赖组织架构。这三张表不依赖任何业务表,先建它们,后面的中间表和业务表才有地方挂。
为什么要关注这个顺序?因为实际执行建表脚本时,如果没按依赖来,第一次建表大概率会报“表或视图不存在”——不是设计错了,是执行顺序错了。文档里的表清单通常按依赖排好,但很多人拿到手就全选跑一遍,反倒忽略这个顺序。我一般会把表清单拆成“主表、中间表、业务表”三批,分批执行,出错时定位会快很多。主表是那些没有外键依赖的基础表,中间表是纯外键关联表,业务表依赖前两者。
这一段还值得留意文档给的是逻辑设计还是物理设计。逻辑设计只画实体和关系,物理设计才带具体字段类型、长度和注释。这份文档在“大数据库设计”的定位下,物理设计比重更高,表名、字段名、类型、主外键都有明确约定,直接照着实现就行。看文档时先翻到表清单那一页,如果每张表都给出字段级说明,基本可以判断这份文档能直接指导开发。
2.2 用户、角色、菜单的关联关系,重点看主外键与中间表
RBAC(基于角色的访问控制)在这份文档里的落地是五张表:sys_user、sys_role、sys_menu、sys_user_role、sys_role_menu。业务数据都存储在实体表里,sys_user_role和sys_role_menu两张中间表不存业务数据,只存关联关系。这套模型非常常见,但落地细节容易出错。
| 关系表 | 关键字段 | 作用 |
|---|---|---|
| sys_user_role | user_id、role_id | 建立用户与角色的多对多关系 |
| sys_role_menu | role_id、menu_id | 建立角色与菜单的多对多关系 |
| 约束约定 | 两字段组成联合主键 | 保证同一对关系只出现一次 |
重点说说联合主键。sys_user_role表只有user_id和role_id两个字段,理论上就应该用这两个字段做联合主键。为什么不用单独的自增ID?因为联合主键能在数据库层面防止重复关系——同一个用户被重复分配给同一个角色时直接报错而不是静默插入。有些同学习惯顺手给中间表加一个自增id,结果重复数据越积越多,RBAC语义被破坏。中间表尽量保持“瘦表”,字段越少误用的面越小。
sys_role_menu同理。这里有个细节:菜单表sys_menu上有parent_id和menu_type字段,menu_type区分目录、菜单、按钮三类。文档把权限控制粒度放到了按钮级,意味着某个角色能不能看到“审批通过”这个按钮,也由sys_role_menu决定。前端做动态菜单和按钮显隐时,按角色关联的菜单列表渲染即可,不需要后端再塞一堆判断。
拿到文档后别急着改字段名。有人习惯用自己的命名体系,把user_id改成u_id,结果后续所有关联表、查询SQL、代码生成器都要跟着改动。文档里已给出的命名是整套体系,改一个等于改一串。这是新手容易犯的错,尤其是建表后发现前端接口拼不上的时候,第一反应别是改表结构,先看是不是查询语句写错了。
2.3 字段级的命名规范与约束约定:默认值、软删除、时间字段
文档在字段设计上有几个约定值得直接搬到自己项目里。主键统一用bigint自增,表名统一带模块前缀(sys_、wf_、oa_),时间字段统一叫create_time和update_time。这些约定看着简单,但能明显降低沟通成本。做过多套系统的人会明白,命名规范最大的价值不是好看,而是让两拨人接手同一套库时不用互相猜。
值得特别关注的是软删除字段del_flag。OA系统里部门、用户这类基础数据不做物理删除,而是用del_flag=0或1标记。这个字段带来一个习惯性要求:几乎所有业务查询都要带del_flag=0条件,漏一条就会查出已删数据。文档里每张表都配了这个字段,说明它的设计者把“可恢复”当作基础能力来对待。
字段注释也值得说。文档为每个字段写了中文注释,这是刚开始做设计的人最容易偷懒的地方。注释的作用不只是阅读方便,后期生成接口文档、给前端联调时,字段含义直接从注释里来,省去反复翻设计的麻烦。我检查一份数据库设计文档好不好,先看注释率,低于六成的后面写代码时大概率要反复问“这个字段是干嘛的”。
时间字段的默认值同样要看。文档里create_time通常默认取当前时间,update_time在更新时自动刷新。如果下一步用代码生成器生成实体,这两个字段就不用做代码层赋值,完全交给数据库。这样能少写不少样板代码,也避免服务器时钟不一致导致的问题。关于时间字段在实际建表时的写法,后面避坑章会专门展开。
3. 再啃流程:审批流定义、流程实例与待办任务的表关联
OA区别于普通信息管理系统的核心是工作流。表单录入了、数据存了,要让人去审批、去流转,这部分的表设计直接决定系统好不好扩展。文档里流程相关的表集中在wf_前缀下,概念上最容易绕,值得单独讲透。记住一句话:流程定义是模板,流程实例是每一次实际运行。后面所有表都是围绕这一对关系展开的。
3.1 流程定义表与流程实例表:版本号与状态位的作用
流程定义表wf_process记录“流程长什么样”:流程编号、流程名称、流程key、版本号version、BPMN文件路径或表单JSON配置。流程实例表wf_instance记录“某一条业务数据正在走哪个流程”,字段包含流程实例ID、关联的流程定义ID、发起人、开始时间、当前节点、实例状态。
为什么一定要拆成两张表?因为同一个流程定义会被多次发起。比如“请假审批”这个流程定义只存一份,但每次有人提交请假单,都会生成一个新的流程实例。这就是一对多:一条流程定义对应多条流程实例。如果不拆,每次发起都要复制一份流程定义,改版时历史数据全部作废,系统根本没法维护。
| 表名 | 关键字段 | 含义 |
|---|---|---|
| wf_process | process_key、version | 流程唯一标识与版本号,一个流程可存在多个版本 |
| wf_instance | process_id、status | 关联流程定义,记录当前实例的处理状态 |
版本号version是容易被忽略的设计点。实际开发中流程改版常有发生——原来审批链是两级,现在要加一级。有了版本号,已经发起的实例继续走旧版本,新发起的走新版本;没有版本号,流程一改版所有进行中的单子全部乱套。文档在流程定义表上留了version字段,这是成熟设计。做迁移时还要留意,旧版本的流程实例是可以继续跑完的,不要一改版就清空。
状态位也一样。wf_instance的status一般整数编码:0审批中、1已通过、2已驳回、3已撤销。有了状态位,业务列表页可以直接按status过滤“我的申请”,不用每次去任务表里拼接查询。文档里的状态编码约定如果能在代码里做成枚举,流程逻辑会清爽很多。这里有个细节:status和当前节点不要混淆,status是整体状态,当前节点是流程走到了哪一步,两者通常同时维护。
3.2 待办任务与已办任务:状态流转与多级审批的实现
wf_task表记录当前待办任务,字段有任务ID、流程实例ID、节点名称、处理人、任务状态、到达时间、完成时间。多级审批的实质是:每完成一个节点,更新当前任务状态,把这条任务标记为“已完成”,同时根据流程定义里的下一节点生成新待办任务,指定给下一节点的处理人。
这套流转可以用三步描述:
- 提交人发起流程,系统向wf_instance插入一条实例记录,同时在wf_task插入一条待办任务,处理人设为第一个审批节点的人。
- 审批人处理完当前节点,业务代码把当前任务状态改成已完成,再查出下一节点的处理人,插入一条新待办任务。
- 最后一个节点处理完,把wf_instance的status改成已通过,流程结束。
文档里任务状态通常用整数编码:0待处理、1已处理、2驳回、3撤销。这个编码约定如果能在代码里做成枚举,待办列表和已办列表的分流就是一次status比较,不用写复杂的条件判断。驳回和撤销是两个不同的动作,驳回是审批人打回去重填,撤销是发起人主动撤回,别合并成一个状态。
这里还要注意一个设计点:每个任务节点能不能加审批意见。能的话,需要在wf_task旁挂一张审批意见表wf_comment,字段包括任务ID、审批人、意见内容、审批时间、是否通过。如果文档缺了这张表,下游做“审批历史”时就得从任务表里拼,非常痛苦。我拿到这份文档时专门核对了有没有wf_comment,因为这直接关系“详情页能不能展示完整审批轨迹”。
3.3 公文、会议、公告等业务表如何挂接到审批流
公文、会议、公告这类业务表和流程设计之间需要一座“桥”。常见做法有两种:一种在业务表里直接加process_instance_id外键,另一种单独建一张业务关联表。前者实现简单、查询直观,后者更灵活但多一次关联查询。这份文档走的是前一种:业务表直接带process_instance_id。
举个例子,发文表oa_document里有一个字段记录对应流程实例ID,“查看这份公文的审批进度”这条SQL非常清晰——先通过公文的process_instance_id查到wf_instance,再顺着查wf_task和wf_comment,审批轨迹一次能拼出来。这个查询链路是OA系统最典型的读操作,如果桥接字段缺失,这一条链路就断了。
业务表和流程实例的对应关系基本是1对1:一条业务数据对应一个流程实例。所以process_instance_id字段上建议加唯一索引,防止同一条公文被意外挂到两条流程上。检查文档时注意这个字段有没有唯一索引,没加的话,后续出现重复关联时很难一眼发现,数据多了才会在查询时发现不对劲。
桥接之后,业务表的状态和流程实例状态要保持同步。提交表单时先插业务数据、再创建流程实例、更新业务表状态为“审批中”;流程跑完要通过回调反写业务表为“已通过”。这两个写操作不在同一个事务里,容易出现业务表状态和流程状态不一致。文档解决这个问题一般通过事务控制或流程结束时的回调反写,做代码实现时要特别留意这一层。常见错误是把状态更新放在前端回调里,前端一刷新就漏掉反写。
3.4 流程引擎表设计的关键取舍:冗余字段还是多表关联
流程设计里有一个常见取舍:把发起人姓名、当前处理人姓名直接冗余到wf_instance上,还是全靠user_id关联去查。文档采用的做法是“ID加名称双写”——存user_id的同时也存user_name。好处是列表页显示发起人时不用二次关联用户表,坏处是用户改名时得同步更新,否则历史数据上的名字是旧的。
这个取舍建议按文档的原始设计走。OA系统的列表页多,每页都去关联用户表,SQL会越来越重。冗余名称虽然牺牲一点一致性,但换来的是查询简单。如果实在担心用户改名,可以在改名的接口里同步刷新一遍流程实例的冗余字段,或者保留ID关联作为最终依据。这不是非黑即白的选择,文档里怎么设计就先把它的逻辑吃透,不要中途自作主张改成纯关联查询,否则列表页性能问题会回头找你。
到这里,流程域的核心表就清楚了:wf_process定义流程模板,wf_instance承载每一次具体流转,wf_task队列化待办任务,wf_comment沉淀审批意见,业务表通过process_instance_id完成挂接。有了这套结构,后面写审批接口时,思路会非常顺。
4. 复现与增量开发中常见的五个坑:现象、原因与排查
这份文档在结构上很完整,但按文档落地时仍然有不少坑。下面五条是我在复现这类OA设计时实际踩过的,按“现象—原因—解决”整理,照着排查可以省不少时间。
4.1 建表脚本执行报“字段不存在”,但ER图里明明有
现象:按文档顺序执行建表脚本,到用户角色关联表时报“字段不存在”或“表不存在”,报错位置刚好卡在插入中间表数据的语句上。
原因:两种常见情况。第一种是执行顺序问题,关联表引用了还没创建的主表,关系尚未建立,字段自然找不到。第二种是字段名大小写不一致,数据库在Linux环境下对表名大小写敏感,文档里写的是dept_id,脚本里写成DEPT_ID就会报错。
解决:严格按依赖顺序建表,先建主表,再建中间表,最后建业务表。字段名统一小写,避免大小写混用。我一般会先把脚本里的表名按依赖关系列一张清单,逐个建完再做关联检查,不要一次性全选执行。报错后第一反应也不要改脚本,先确认是不是依赖顺序的问题,改顺序比改字段名安全得多。
4.2 中文乱码:表建好了,插进去的数据全是问号
现象:执行insert语句后,查询看到的中文全是???,前端页面显示乱码。
原因:建表脚本或数据库连接指定的字符集不是utf8mb4,数据库服务器和客户端字符集不一致。新版本数据库默认字符集通常是utf8mb4,但旧库迁移或手工建库时很容易漏掉。
解决:建库时指定utf8mb4字符集,给varchar字段显式指定utf8mb4;执行脚本前先执行set names utf8mb4;数据库连接URL加上characterEncoding=utf8和useUnicode=true。这三处都确认过,乱码基本不会出现。如果已经进了脏数据,修改字符集后还要对存量数据做一次转码,不要只改连接串就完事。
4.3 软删除字段与唯一索引冲突
现象:删除一个用户后,重新创建同名用户,insert直接报“违反唯一约束”,数据表里却看不到旧的用户记录。
原因:用户名字段上有唯一索引,删除用户只是把del_flag设为1,数据还留在表里,唯一索引依然生效。这是软删除设计最常见的副作用,可视化工具里看不到已删数据,但索引还在。
解决:常见做法有三种——第一种,用户名不加唯一索引,查询时全部带上del_flag=0过滤;第二种,唯一索引建在“username+del_flag”组合上,让已删除的记录和新记录可以共存;第三种,保留物理唯一索引,同时在删除逻辑里把用户名改成一个带时间戳的临时值。无论选哪种,写SQL时del_flag条件都不能漏。我的习惯是选第二种,既能保证业务校验,又不阻塞重建同名数据。
4.4 外键没建索引,关联查询越来越慢
现象:数据量几千时关联查询没问题,到几万后按部门查用户明显变慢,查看执行计划发现关联字段走了全表扫描。
原因:用户表的dept_id没建索引,关联查询字段每次都要全表扫描。数据库设计文档画了外键关系,但物理建表时外键字段不一定都带了索引,这是文档和物理实现常见的脱节点。
解决:给所有外键字段补上普通索引,比如sys_user.dept_id、wf_instance.process_id、wf_task.instance_id。导完库后可以用下面这条SQL查出缺失索引的外键字段,批量补上。
SELECT TABLE_NAME, COLUMN_NAME FROM information_schema.KEY_COLUMN_USAGE WHERE REFERENCED_TABLE_NAME IS NOT NULL AND (INDEX_NAME IS NULL OR INDEX_NAME = 'PRIMARY');这个查询能把所有外键关系里没有索引的字段找出来,效率比人工翻文档高很多。补索引不需要改表结构,ALTER TABLE加一个普通索引就行,对已有数据无损。
提示:这里补的是普通二级索引,不是唯一索引。外键字段允许重复,用普通索引就够,唯一索引反而会限制数据。
4.5 时间字段默认值不生效
现象:插入数据时create_time为NULL,查询结果排序乱掉,或者代码里反复做空值判断。
原因:数据库版本对datetime默认值的写法要求不同。高版本datetime默认值不能直接写当前时间,需要显式写DEFAULT CURRENT_TIMESTAMP,否则执行建表脚本时默认值被忽略。文档里如果只写了字段类型,没有写默认值表达式,就会踩到这个坑。
解决:把时间字段定义统一改成下面的写法:
CREATE TABLE sys_user ( user_id BIGINT PRIMARY KEY AUTO_INCREMENT, create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, update_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;update_time的ON UPDATE CURRENT_TIMESTAMP表示记录更新时自动刷新这个字段,业务代码里就不用再手动维护更新时间。如果文档已给定了建表脚本,先核对每个时间字段的默认值写法再执行。这个坑很隐蔽,不好查,属于建表时多看一眼就能避免的类型。
5. 把设计文档用到交付:从ER图到可执行脚本的转换技巧
文档到手之后,最直接的用法是把它变成一套能跑的数据库。转换顺序有讲究,直接全表执行最容易翻车。我一般按下面三步走,每步都有明确的检查点。
5.1 按依赖顺序生成三批建表脚本
先拆依赖。把文档里的表分成三批:主表(sys_dept、sys_user、sys_role、sys_menu)、中间表(sys_user_role、sys_role_menu)、业务表(wf_process、wf_instance、wf_task、oa_document、oa_meeting)。每批单独存一个SQL文件,按顺序执行。主表和中间表之间不能跳过,否则后建的关联表执行时报错。建表脚本生成后,先检查两件事:每张表是否都有主键、每张表是否都指定了utf8mb4字符集,避免后续再返工。
5.2 初始化数据也按依赖顺序插
先插部门、用户,再插角色、菜单,然后插用户角色关联和角色菜单关联,最后插流程定义和一条示例业务数据。文档如果带了初始SQL就直接用;没带的话,至少构造一份最少样本集——一个部门、三个用户、两个角色、一个“请假审批”流程定义,就能把登录、菜单渲染、发起审批、审批通过这条主链路完整验一遍。不要一上来就灌大量假数据,数据越多,排错越难。
5.3 数据库层面反查设计缺陷的五项检查
不用等代码写完,在数据库层面就能做一轮体检:一查所有外键字段是否有索引;二查所有必填字段是否标了NOT NULL;三查状态字段有没有默认值;四查关联表有没有联合主键;五查业务表有没有process_instance_id这类关联字段。这五项每项用一条SQL就能验完,查完能避免大部分运行时才暴露的设计问题。
这套检查习惯帮我挡过不少次改动返工。前年做一套审批功能,连表查半天才发现业务表根本没挂流程实例ID,整个链路是断的,重新补字段和数据花了两天时间。从那以后,我每次接到一套新的数据库设计文档,都强制走一遍这个检查清单再开始写业务代码,希望帮到你。
本文还有配套的精品资源,点击获取