Java协同办公OA系统源码二次开发:环境搭建、权限与审批流实战

发布时间:2026/10/7 14:31:21
Java协同办公OA系统源码二次开发:环境搭建、权限与审批流实战 简介这是一套基于SpringBoot的Java协同办公OA系统源码面向需要学习企业级办公自动化开发的学生、初级开发者及技术团队。前台采用springbootfreemarkjpamybatismysql组合后台以springboot为核心持久层融合jpa与mybatis模板引擎使用freemark整体架构完整适合作为毕业设计、课程实训或二次开发的基础项目。压缩包共2443个文件约25.54MB包含285个java源码、301个ftl模板、172个js脚本、122个css样式、106个xml配置及241个class文件另有png、gif等界面素材覆盖前后端与资源层。系统功能涵盖系统管理、用户管理、考勤管理、流程管理、公告管理、邮件管理、任务管理、日程管理、计划管理和文件管理十大模块可实现数据字典、角色权限、费用报销、出差加班申请、内部邮件收发、任务状态修改、日历报表及文件分类归档等业务。目前已有872人学习下载适合借此理解OA系统的模块划分、权限设计与流程审批实现思路。1. Java协同办公OA系统源码从能跑起来到敢改一处审批流很多团队第一次拿到一套 Java 协同办公 OA 系统源码第一反应是“先跑起来再说”结果mvn spring-boot:run一敲数据库连不上、Redis 没起、前端 404折腾一下午连登录页都没见到。协同办公 OA 系统源码本质上是一套已经写好组织架构、权限模型、审批引擎、消息通知的后端工程它解决的不是“从零写一个 OA”而是“在成熟骨架上改出自己公司的流程”。适合谁适合手里有 Java 基础、懂 Spring Boot 和 MyBatis、被公司要求两周内出一个内部审批系统雏形的工程师。这篇不聊虚的就按我实际改过几套 OA 源码的顺序把环境、表结构、审批流、权限、踩坑一次讲透让你拿到源码后知道先动哪、后动哪、哪里千万别动。2. 把 OA 源码跑起来环境、依赖与最小启动路径2.1 先看清一套 OA 源码通常由哪几块拼成协同办公 OA 系统源码不是单个 jar常见结构是「后端多模块 前端静态资源 SQL 初始化脚本 配置文件」。后端一般拆成common、system、workflow、oa几个 Maven 模块system管用户角色菜单workflow管审批流定义和实例oa放公告、日程、考勤这些业务。前端多数是 Vue 打包后的dist也有老项目用 JSP 或 Thymeleaf 直接渲染。数据库以 MySQL 5.7/8.0 为主缓存用 Redis定时任务用 Quartz 或 Spring Task。拿到源码先别急着改代码先做一件事把pom.xml的模块依赖画出来。我一般会看根pom的modules再逐个看子模块的dependencies确认workflow是否依赖system、oa是否依赖workflow。这一步决定了你后面改审批流时会不会牵一发动全身。很多翻车现场就是没看清模块依赖在oa里直接调workflow的 Mapper结果循环依赖启动报错。2.2 用 Docker 起 MySQL 和 Redis 的最小命令环境这块最省事的做法是用 Docker别在宿主机装 MySQL 再配半天。下面是我常用的启动命令MySQL 用 8.0Redis 用 7端口按源码配置文件里的来别自己乱改。# 启动 MySQL映射 3306设置 root 密码和时区 docker run -d --name oa-mysql \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDoa123456 \ -e TZAsia/Shanghai \ mysql:8.0 \ --character-set-serverutf8mb4 \ --collation-serverutf8mb4_general_ci # 启动 Redis映射 6379开启持久化 docker run -d --name oa-redis \ -p 6379:6379 \ redis:7 \ redis-server --appendonly yes逻辑说明MySQL 必须用utf8mb4OA 里存中文姓名、部门名、审批意见用utf8会丢 emoji 和部分生僻字。TZ设成上海否则审批时间会比实际少 8 小时这个坑我踩过审批记录时间对不上排查半天以为是代码问题。Redis 开appendonly是为了重启后登录 token 和缓存不丢开发阶段无所谓但你要是直接拿这套当测试环境不开会丢会话。参数说明MYSQL_ROOT_PASSWORD改成你源码application.yml里配的密码别用默认。端口如果宿主机被占用改-p左边右边容器内端口别动动了要同步改配置文件。2.3 导入 SQL 与改配置文件的顺序不能反启动容器后先导入源码sql目录下的初始化脚本。常见有两个文件oa_schema.sql建库建表oa_data.sql插初始数据。顺序是先 schema 后 data反了会报外键约束失败。# 进入容器执行导入注意路径换成你本地的 docker exec -i oa-mysql mysql -uroot -poa123456 ./sql/oa_schema.sql docker exec -i oa-mysql mysql -uroot -poa123456 ./sql/oa_data.sql导入完再改application-dev.yml重点改三处spring.datasource.url的库名、username/password、spring.redis.host/port。改完别急着启动先确认url里有没有serverTimezoneAsia/Shanghai没有就加上否则 MySQL 8 驱动会报时区错误。提示有些源码把配置写在 Nacos 或 Apollo 里本地跑要先把配置中心关掉在bootstrap.yml里把spring.cloud.nacos.config.enabled设为 false否则启动时一直连不上配置中心日志刷屏。2.4 启动类与前端资源的两个检查点后端启动类一般在oa-admin或oa-web模块下找SpringBootApplication那个类。启动前检查MapperScan的包路径是否覆盖了所有 Mapper漏了会报Invalid bound statement。前端如果是dist静态资源确认application.yml里spring.web.resources.static-locations指向了classpath:/static/并且dist已经拷进去。如果是前后端分离前端npm run dev时改vue.config.js的proxy指向后端端口。启动成功后访问登录页默认账号密码通常在oa_data.sql的sys_user表里密码是 BCrypt 加密的别想着反推直接看源码里有没有注释写明明文或者用new BCryptPasswordEncoder().encode(123456)生成一个替换进去。这一步做完你才算真正“拿到”这套 OA 源码。3. 读懂组织架构与权限表改权限前必须搞清的 4 张表3.1 用户、角色、菜单、部门四张核心表的关系OA 源码的权限模型基本是 RBAC 变体核心四张表sys_user、sys_role、sys_menu、sys_dept。用户通过sys_user_role关联角色角色通过sys_role_menu关联菜单用户通过sys_user的dept_id关联部门。菜单表里menu_type区分目录、菜单、按钮perms字段存权限标识如oa:notice:add。改权限前先查这三张关联表的数据别直接改sys_role_menu就以为生效了很多源码有缓存改完要清 Redis 里的sys_menu缓存或者调源码里的刷新接口。我一般会先跑一条 SQL 看某个角色到底有哪些菜单-- 查角色 id 为 2 的角色拥有的菜单权限 SELECT m.menu_id, m.menu_name, m.perms FROM sys_menu m JOIN sys_role_menu rm ON m.menu_id rm.menu_id WHERE rm.role_id 2 AND m.menu_type IN (C, F);逻辑说明menu_type为C是菜单F是按钮权限M是目录。查出来如果发现某个按钮的perms为空前端v-hasPermi指令会直接隐藏按钮后端PreAuthorize也会拦这时候不是代码问题是数据没配全。3.2 数据权限怎么落到 SQL 上部门树与行级过滤协同办公里最容易被业务方追着问的就是“为什么张三能看到李四部门的审批单”。这涉及数据权限常见实现是在 Mapper 的 SQL 里拼dept_id IN (...)部门范围通过sys_role_dept表配置。源码里一般有个DataScopeAspect切面在方法执行前把部门 id 集合塞进BaseEntity的params里。你要改数据权限先找到这个切面看它怎么取当前用户的角色和部门。常见逻辑是如果是超级管理员不加过滤如果是自定义数据权限查sys_role_dept如果是本部门及以下递归查sys_dept的ancestors字段。ancestors存的是祖级列表如0,100,101递归时用FIND_IN_SET或LIKE。这里有个性能坑部门多了之后LIKE查不动我一般改成先查子部门 id 列表再IN。// 数据权限切面里拼部门条件的常见写法 if (StringUtils.isNotEmpty(dataScope.getDeptIds())) { // 把部门 id 集合拼成 SQL 的 IN 条件 String deptSql AND dept_id IN ( dataScope.getDeptIds() ); // 塞进 paramsMapper 里用 ${params.dataScope} 取 baseEntity.getParams().put(dataScope, deptSql); }参数说明dataScope.getDeptIds()是逗号分隔的字符串别直接拼用户输入有 SQL 注入风险源码里一般从数据库查出来可控。${}在 MyBatis 里是直接拼接不是预编译所以这个字段绝对不能来自前端。3.3 菜单缓存与前端路由的联动前端路由通常从后端/getRouters接口拿菜单树动态生成。你改了sys_menu表前端不刷新是因为路由缓存。源码里一般用 Redis 存sys_menu的树key 类似sys_menu:all。改完数据要么重启后端要么调/system/menu/refreshCache这类接口。我见过有人改完菜单发现前端 404查半天是path字段配错了component字段指向的 Vue 文件路径不对。component存的是system/user/index这种相对路径前端会拼成/views/system/user/index.vue。如果你新增了页面记得在views下建对应文件否则路由匹配不到页面白屏。3.4 改权限的最小验证闭环改完权限别只看数据库走一遍完整验证用目标账号登录看左侧菜单是否出现点进去看按钮是否显示调接口看后端是否放行。三步都过才算改对。我一般会准备两个账号一个管理员一个普通用户改完切换登录测。如果普通用户还能看到不该看的菜单先查sys_role_menu再查 Redis 缓存最后查前端有没有硬编码路由。这个顺序能省你很多时间。4. 审批流引擎怎么接从流程定义到待办任务的落地4.1 先确认源码用的是自研流还是 Activiti/Flowable协同办公 OA 系统源码里审批流实现分两派一派自研用oa_flow、oa_flow_node、oa_flow_instance几张表自己推状态一派集成 Activiti 或 Flowable用ACT_开头的表。你拿到源码先看pom.xml有没有activiti-engine或flowable-engine依赖再看数据库有没有ACT_RE_PROCDEF表。自研流改起来简单但功能弱引擎流功能强但表多、概念多。我一般建议如果公司流程不超过 10 个节点、没有会签和驳回重走自研流够用如果有并行网关、会签、子流程老老实实用 Flowable。别想着自研一套能媲美引擎的最后坑的是自己。4.2 自研流的核心表与状态机自研流通常三张表oa_flow存流程定义oa_flow_node存节点和审批人oa_flow_instance存实例和当前节点。状态用数字表示比如 0 草稿、1 审批中、2 通过、3 驳回。审批人存在node表的approver_ids字段逗号分隔。发起流程时插一条instance状态 1当前节点指向第一个节点。审批时根据当前节点查node表判断当前用户是否在approver_ids里是则更新状态否就拒绝。通过后找下一个节点没有下一个就状态改 2。// 自研流审批通过的核心逻辑片段 public void approve(Long instanceId, Long userId, String comment) { OaFlowInstance instance instanceMapper.selectById(instanceId); // 校验当前用户是否有权审批当前节点 OaFlowNode node nodeMapper.selectByFlowAndNode(instance.getFlowId(), instance.getCurrentNode()); if (!Arrays.asList(node.getApproverIds().split(,)).contains(userId.toString())) { throw new ServiceException(无权审批该节点); } // 记录审批意见 approvalMapper.insert(new OaApproval(instanceId, node.getNodeId(), userId, comment)); // 找下一个节点 OaFlowNode next nodeMapper.selectNextNode(instance.getFlowId(), node.getNodeId()); if (next null) { instance.setStatus(2); // 通过 } else { instance.setCurrentNode(next.getNodeId()); } instanceMapper.updateById(instance); }逻辑说明这段代码的关键是审批人校验和下一节点查找。approverIds用逗号分隔是常见做法但用户多了之后字符串会很长查询效率低我一般会拆一张oa_flow_node_approver关联表。selectNextNode按node_sort排序取下一个别用node_id自增排序流程改版后 id 会乱。参数说明instanceId和userId必须从后端 session 或 token 里取别信前端传的。comment要做长度校验防止超长文本入库报错。4.3 待办任务怎么查状态、节点与用户三条件待办列表的 SQL 是审批流里最容易写错的。正确逻辑是查oa_flow_instance里状态为审批中、且当前节点的审批人包含当前用户的记录。如果审批人存在关联表就 join如果存在逗号分隔字段就用FIND_IN_SET。-- 查当前用户的待办审批 SELECT i.instance_id, i.title, i.create_time, n.node_name FROM oa_flow_instance i JOIN oa_flow_node n ON i.flow_id n.flow_id AND i.current_node n.node_id WHERE i.status 1 AND FIND_IN_SET(#{userId}, n.approver_ids);逻辑说明FIND_IN_SET在数据量大时走不了索引这是自研流的通病。优化方向是把approver_ids拆表或者加一张oa_flow_task表发起和流转时写入待办记录查待办直接查task表状态改完删掉。我一般会加task表虽然多一次写入但查询快很多也方便做已办和抄送。4.4 驳回与撤回的边界处理驳回不是简单把状态改成 3要区分“驳回到发起人”还是“驳回到上一节点”。源码里常见做法是instance表加reject_node字段记录驳回目标节点。撤回则是发起人在流程未结束时把实例状态改回草稿同时删掉已产生的审批记录。这里有个坑驳回后重新提交审批记录是追加还是覆盖我一般追加保留历史意见前端按时间倒序展示。覆盖的话业务方会问“上次谁驳的怎么没了”。另外撤回要判断当前节点是否已经有人审批如果有人审过撤回要通知已审批的人否则人家白审了。5. 避坑与排查OA 源码改到一半最容易翻车的 5 个点5.1 启动报 Invalid bound statementMapper 没扫到现象启动时或调接口时报Invalid bound statement (not found)提示某个 Mapper 方法找不到。原因通常是MapperScan的包路径没覆盖到新增的 Mapper或者 XML 文件没放在resources/mapper下。解决检查启动类的MapperScan确认包含所有 Mapper 接口所在包检查application.yml的mybatis-plus.mapper-locations是否指向classpath*:mapper/**/*.xml。如果是多模块XML 要放在被扫描的模块 resources 下。5.2 审批时间差 8 小时时区没统一现象审批记录里的create_time比实际时间少 8 小时或者前端显示的时间不对。原因MySQL 容器时区是 UTCJDBC 连接没设serverTimezone或者 Jackson 序列化没配时区。解决MySQL 启动加-e TZAsia/ShanghaiJDBC url 加serverTimezoneAsia/Shanghaiapplication.yml里加spring.jackson.time-zoneGMT8。三处都改少一处都可能差 8 小时。5.3 改了菜单不生效Redis 缓存没清现象数据库sys_menu改了前端菜单没变。原因菜单树缓存在 Rediskey 类似sys_menu:all改数据不会自动清。解决调源码里的刷新缓存接口或者手动redis-cli del sys_menu:all再刷新前端。如果还不行看前端有没有本地缓存路由清浏览器 localStorage。5.4 审批人配了却收不到待办FIND_IN_SET 类型不匹配现象approver_ids里明明有当前用户 id待办列表就是查不出来。原因FIND_IN_SET要求字段和值都是字符串如果user_id是 Long传进去可能变成1,2匹配不上1。解决SQL 里用FIND_IN_SET(CAST(#{userId} AS CHAR), n.approver_ids)或者把approver_ids存成带逗号的字符串如,1,2,查询用LIKE %,1,%。我一般用后者简单直接。5.5 前端打包后 404静态资源路径没配对现象npm run build后把dist拷到后端static下访问页面 404 或白屏。原因Vue 的publicPath配成了/但后端 context-path 不是根或者static-locations没包含classpath:/static/。解决vue.config.js里publicPath改成./后端确认spring.web.resources.static-locations包含classpath:/static/并且dist里的index.html在static根目录下不是嵌套在dist文件夹里。6. 二次开发进阶把 OA 源码改造成自己公司的三个技巧6.1 用代码生成器先出 CRUD再改业务逻辑多数 OA 源码带代码生成器能根据表结构生成 Controller、Service、Mapper、Vue 页面。我一般先用它生成公告、日程这类标准 CRUD再在生成的代码上改业务。这样省掉建文件、写基础增删改查的时间也保证风格和源码一致。生成前把表注释写清楚生成器会把注释带到前端 label 上省得后面一个个改。# 常见代码生成器的建表 SQL 示例注释要写全 CREATE TABLE oa_meeting ( meeting_id BIGINT NOT NULL AUTO_INCREMENT COMMENT 会议ID, title VARCHAR(200) NOT NULL COMMENT 会议标题, start_time DATETIME COMMENT 开始时间, end_time DATETIME COMMENT 结束时间, room VARCHAR(100) COMMENT 会议室, status TINYINT DEFAULT 0 COMMENT 状态 0待开 1已开 2取消, PRIMARY KEY (meeting_id) ) ENGINEInnoDB COMMENT会议室预约表;逻辑说明注释里的0待开 1已开 2取消会被生成器解析成前端下拉选项省得你手动配字典。表名oa_meeting对应生成的前端路径oa/meeting和源码模块结构一致。6.2 把审批流抽象成配置别硬编码节点我见过最坑的改法是把审批节点写死在 Java 代码里加一个节点就要改代码重新发版。正确做法是把节点、审批人、条件都放数据库代码只负责按配置流转。条件可以用 SpEL 表达式存字段比如#days 3表示请假超过 3 天走总经理审批。这样业务方自己就能在后台配流程你只维护引擎。6.3 用单元测试锁住审批流转逻辑审批流改多了容易出玄学 bug今天改 A 流程把 B 流程改挂了。我一般给核心流转写单元测试用 H2 内存库跑覆盖发起、通过、驳回、撤回四个场景。每次改完跑一遍比手工点页面靠谱。// 审批流单元测试的核心断言 Test public void testApprovePass() { // 发起流程 Long instanceId flowService.start(flowId, userId); // 审批通过 flowService.approve(instanceId, approverId, 同意); // 断言状态为通过 OaFlowInstance instance instanceMapper.selectById(instanceId); assertEquals(2, instance.getStatus()); }参数说明flowId和userId用测试数据初始化时插入别依赖生产数据。assertEquals的期望值 2 对应通过状态改状态枚举时记得同步改测试。6.4 一个我常犯的教训改 OA 源码最忌讳一上来就动权限和审批流核心表。我早期接手一套源码觉得部门表字段不够用直接加了个leader_id结果数据权限切面里递归查部门时没处理新字段导致部分用户看不到任何数据排查了一整天才定位到。后来我养成习惯动核心表之前先备份改完先跑一遍全量用户的菜单和数据权限验证确认没问题再继续。希望帮到你。本文还有配套的精品资源点击获取

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询