做了这么多年系统开发跟 OA 打交道的时间着实不短。市面上的老牌产品像泛微、致远、蓝凌功能很重实施周期和报价也不低一套标准化的 SaaS 办公软件流程灵活性又往往跟不上公司内部的特殊规矩。所以不少团队最终会考虑到自己动手基于 SpringBoot 做一套 OA 自动化办公系统。这个选择听起来“不就是增删改查嘛”但真正从零做一遍你会发现在工作流设计、权限控制、附件存储、消息推送、安全防护这些环节里每一步都有坑在等着。这篇文章就是我沉淀下来的一套 SpringBoot OA 自动化办公系统从设计到落地的全过程结合我在实际项目中踩过的坑、验证过的方案、反复调整过的细节梳理成可以直接参考的实操路线。如果你正准备做 OA 相关的毕设、公司内部系统改造或者正在评估自研 OA 的可行性这篇内容应该能让你少走很多弯路。1. 项目定位、需求分析与技术选型1.1 需求分析先做减法哪些功能必须做OA 系统很容易做成“大杂烩”碰到企业什么都想要功能清单越拉越长。我个人的建议是第一版只做核心闭环把线下最常见的办公流程搬到线上即可先跑通再迭代。我在设计这套SpringBoot OA时参考了很多提问者的关键词比如流程审批、会签或签、公文流转、会议管理、附件下载、登录口令这些都是 OA 使用场景中真正的高频模块值得优先拆解。落地到具体功能域我的第一版清单是五个工作台待办/待阅/通知、审批中心请假、报销、用章、采购、合同等流程、会议管理预约、纪要、提醒、公文管理发文、收文、归档、系统管理用户、角色、菜单、日志、字典。每个功能域都有一条业务闭环比如审批中心必须做到“发起→审批→结束→可查”不能出现提交之后流程断掉的情况。很多做 OA 的失败案例不是功能不够多而是审批链条没有形成闭环。我见过有的系统里审批单提交后状态还是“审批中”但审批人压根没收到任何提醒最后变成业务人员天天催。所以需求评审时我重点验证一件事每个流程节点失败之后怎么办。节点超时、审批人离职、流程驳回、撤回重提这些场景必须在设计阶段就给出答案否则开发完就是满地补丁的局面。1.2 技术栈选型为什么是 SpringBoot 2.7.18 MyBatis-Plus 单体架构SpringBoot 在 OA 系统里的地位不用多说生态成熟、上手快、部署简单。但版本选择有讲究。我用的是 SpringBoot 2.7.18这是 2.x 的最后一个维护版本稳定性高而且对 JDK 8 的支持非常友好。很多公司生产环境的 JDK 还停留在 8如果盲目上 SpringBoot 3.x意味着团队开发环境、服务器运行环境、运维部署脚本都得跟着升级连带成本非常可观。“springboot版本太高”是很多人踩过的坑版本不是越新越好而是越适合实际环境越好。后端配套方面持久层我选了 MyBatis-Plus 而不是 JPA。OA 系统里多表关联查询、报表统计、复杂条件筛选特别多MyBatis 写 SQL 的掌控力更强MyBatis-Plus 又帮我们省掉了大量单表 CRUD 的模板代码。分页插件用 MyBatis-Plus 自带的 PaginationInnerInterceptor具体用法后面会讲到。认证授权方案我在 Spring Security JWT 和 Shiro 之间纠结过最终选了前者。原因有两个第一OA 系统以后大概率要对接企业微信、钉钉或者其他单点登录入口Spring Security 的过滤器链扩展点更成熟第二方法级权限控制可以用 PreAuthorize 注解精确到按钮级别在权限颗粒度上更符合 OA 的管理诉求。数据库方案MySQL 8 存业务数据Redis 存缓存、验证码、登录态和分布式锁。文件存储我选了 MinIO后面在附件管理部分展开。前端选了 Vue 2 Element UI这套组合做后台管理系统的效率没得说列表、表单、弹窗、树形控件开箱即用特别适合 OA 这种表单密集型应用。架构层面我特意保留了单体应用。这几年微服务的声音很大但 OA 系统的真实并发量其实不高一个几百人的企业日活能有几百就算不错了。单体应用的部署成本低排查问题简单事务控制也比微服务容易得多。微服务是解决团队协作和水平扩展问题的不是用来给 OA 系统撑门面的。等到某个模块确实扛不住了再把工作流、消息推送这种相对独立的模块拆出去也不迟。1.3 分层架构具体怎么切一套清晰的代码结构比任何设计文档都重要。我采用的还是经典的四层结构Controller 层只做参数接收、鉴权校验、结果封装不写任何业务逻辑。Service 层业务逻辑的收容所事务边界在这一层事务不放在 Controller。Mapper 层只做数据访问SQL 写在 XML 里或者用注解。Domain 层实体对象、DTO、VO 明确区分不把数据库表结构直接暴露给前端。这套规范看似简单实际项目中经常看到有人在 Controller 里写一堆 SQL 拼装逻辑或者在实体类里塞各种页面展示字段结果就是项目越改越乱。好的分层结构有个最直观的判断标准新人接手一天之内能找到“功能入口在哪、业务逻辑在哪、表在哪”。2. 核心功能模块拆解从权限到流程的完整实现2.1 权限设计RBAC 模型落地的关键细节OA 系统的权限模型是地基地基不牢后面所有功能都是危房。我见过很多项目在用户表里直接加一个 role_id 字段角色一多就完全失控。标准的 RBAC 五张表是抄作业的答案sys_user、sys_role、sys_menu、sys_user_role、sys_role_menu。实际开发中还必须考虑数据权限也就是“能看到哪些数据”的问题。OA 里经常有这样的需求部门经理只能看到本部门的审批单总经理能看到全公司数据项目组成员只能看到自己参与的项目数据。我做了五种数据权限级别仅本人、本部门、本部门及下级部门、按自定义组织范围、全部数据。实现方式是在查询方法上加一个自定义注解 DataScope然后用 MyBatis 拦截器自动拼接过滤条件。比如Aspect Component public class DataScopeAspect { Around(annotation(custom.annotation.DataScope)) public Object around(ProceedingJoinPoint pjp) throws Throwable { // 从登录用户上下文取出部门编码和权限级别 // 拼接 WHERE 条件如 dept_id IN (...) 或者 user_id ? return pjp.proceed(); } }这样做的好处是业务代码不需要到处手动加过滤条件权限逻辑收敛到一个地方不容易漏。坏处是拦截器拼 SQL 时容易出错所以我的建议是对外查询接口要写完整的集成测试尤其是多表联查场景不然很容易出现权限条件被 LEFT JOIN 带偏的情况。菜单权限和按钮权限也一起说下。菜单表 sys_menu 里维护按钮级权限点比如 sys:user:add、sys:user:delete前端根据当前用户的按钮权限列表决定渲染哪些按钮后端再用 Spring Security 的方法级注解二次拦截。2.2 审批中心用 Flowable 实现会签、或签与灵活跳转审批是 OA 的灵魂也是最难做好的模块。第一版我也图省事自己用状态机设计审批流做到“同意、驳回、撤回”还好等业务方提出“部门经理审批后自动转给财务总监”“会签部门必须全部通过才算完成”“审批人可以加签、转办、退回上一步”这些需求时自研状态机开始失控。最终换了 Flowable 工作流引擎。Flowable 是开源 BPMN 2.0 引擎支持流程定义、流程实例、任务分配、会签或签、条件分支、子流程等。集成后最大的变化是审批流不再写死在代码里而是可以在流程设计器里拖拽配置。管理员调整流程节点不用发版本灵活性完全不是自研状态机能比的。很多人一看到 Flowable 自动生成的几十张以 ACT_ 开头的表就慌其实不需要管它们。集成时重点关心两个配置项flowable: database-schema-update: true async-executor-activate: truedatabase-schema-update 为 true 时首次启动会自动建表async-executor-activate 开启异步任务执行器避免流程任务阻塞主线程。会签和或签是热词里出现频率很高的概念这里详细说说。会签是指某个节点需要多个审批人审批所有人都同意才算通过或签是任意一个审批人同意流程就可以继续。Flowable 里用多实例节点实现在节点 XML 中配置 collection 变量和 completionCondition 条件例如并行会签userTask idapprovalTask name会签审批 flowable:assignee${assignee} multiInstanceLoopCharacteristics isSequentialfalse activiti:collectionassigneeList completionCondition${nrOfCompletedInstances nrOfInstances}/completionCondition /multiInstanceLoopCharacteristics /userTask这里 isSequential 为 false 表示并行会签所有审批人同时收到任务completionCondition 里面 nrOfCompletedInstances nrOfInstances 意味着必须全部完成才通过。如果改成 ${nrOfCompletedInstances 1}就是或签任意一个完成就流转。实际使用中要注意 Flowable 和 SpringBoot 的版本兼容。我熟悉的是 Flowable 6.x 搭配 SpringBoot 2.x非常稳定Flowable 7 系列对新版 JDK 和 SpringBoot 3 有更好支持但迁移成本不低。网上很多人遇到“表不存在”“历史表读取失败”之类的问题多半是版本匹配出了问题先检查这两个依赖的版本。2.3 公文管理版式、归档、防篡改一个都不能少公文管理和普通审批不一样它更强调格式规范和归档严肃性。企业内部红头文件、通知、签报发出去的每一个版本都要留痕归档后不能随便改。我的实现思路是正文部分用富文本编辑器录入生成 HTML 存储套红模板用 FreeMarker 模板引擎动态渲染红头、文号、落款文件版本用专门的版本表管理每次修改生成一个新版本记录正文内容快照存一份避免后改的人覆盖前人的内容。归档防篡改这块我用的方案是对归档文件计算 MD5 摘要把摘要存库定期做对账校验。文件本身存储时打成只读权限。附件元数据表至少要记录这几个字段字段说明file_id文件唯一IDbusiness_type业务类型发文/收文/审批附件business_id关联业务记录IDoriginal_name原始文件名stored_path存储路径MinIO的bucket/object keymd5文件摘要upload_user上传人create_time上传时间这个表格是 OA 附件管理的标准落法后续扩展转存、统计、审计都很方便。2.4 会议管理、日程提醒与消息推送会议模块看起来简单做细了也挺有内容。会议室资源要统一管理预约时做冲突检测。我最初的实现是查询时间段内是否有已批准的会议记录有冲突就拒绝。后来业务方要求“允许冲突预约但要标记待确认”最终改成了预约单走审批流的形式先到先得不再硬编码。日程提醒的实现相对常规用 Spring 自带的 Scheduled 定时任务扫描未来半小时内待提醒的日程然后推送站内信和企业微信消息。这里有个典型坑将来系统以多实例部署时每个实例都会跑同一个定时任务导致重复推送。解决办法是加 Redis 分布式锁key 用任务名称SETNX 成功后只有拿到锁的实例执行任务执行完再释放。消息推送这块我用了 WebSocket 做站内信实时提醒。一要注意心跳检测前端每 30 秒发一个 ping服务端连续几次收不到就主动断开防止死连接占着资源二要注意多实例部署时WebSocket 连接是分散在不同机器上的所以推送不能只查本地连接要用 Redis Pub/Sub 广播所有实例收到消息后各自查找自己维护的连接列表再推送。如果你们部署的是单实例这段可以忽略但最好是提前预留扩展空间。3. 安全防护体系OA 系统最容易翻车的地方3.1 从某 OA 产品的 XXE 漏洞说起办公系统是安全攻击的高发区因为企业内部系统的数据价值高、出口多。这几年多家 OA 产品被爆出 XXE 漏洞原理并不复杂XML 外部实体注入。OA 系统里有大量和外部系统做数据交换的场景比如对接财务系统的 WebService、导入导出的 XML 配置文件、甚至某些报表工具的底层解析。如果 XML 解析器配置得不够严格攻击者可以通过构造恶意 XML 外部实体让服务器读取任意本地文件比如 /etc/passwd、数据库配置文件甚至发起内部网络探测SSRF。Java 里最典型的 XXE 防护套路是设置 DocumentBuilderFactory 的特性DocumentBuilderFactory dbf DocumentBuilderFactory.newInstance(); dbf.setFeature(http://apache.org/xml/features/disallow-doctype-decl, true); dbf.setFeature(http://xml.org/sax/features/external-general-entities, false); dbf.setFeature(http://xml.org/sax/features/external-parameter-entities, false); dbf.setXIncludeAware(false); dbf.setExpandEntityReferences(false);核心思路就是两步禁用 DTD禁用外部实体。如果你仅仅是解析 JSON 就完全够用没必要用 XML。OA 系统里凡是能改成 JSON 对接的接口我都优先改 JSON从源头上消灭这一类的攻击面。3.2 XSS 攻击与恶意 PDF 上传的联合防护XSS 是 OA 里的常客因为 OA 有大量用户输入富文本、评论、审批意见这些内容还要被其他人打开查看。攻击者如果往审批意见里塞一段