资讯详情

Spring Boot企业OA管理系统:权限模型与审批流设计

📅 2026/9/16 14:11:24 | 华诺云谱 👁 阅读
Spring Boot企业OA管理系统:权限模型与审批流设计
简介基于Spring Boot开发的企业OA管理系统毕业设计项目面向准备毕业设计的计算机专业学生及需要项目实战的Java学习者可同时用于课程设计或期末大作业帮助理解企业办公自动化系统的完整开发流程。整个资源包共348个文件包括209个Java源码、41个JS脚本、28个XML配置、27个CSS样式以及SQL数据库脚本、Dockerfile部署配置等压缩包仅19.67MB目录结构清晰便于按模块查阅和二次开发。项目中包含请假、培训、公告等典型OA业务场景的BPMN流程定义并配有项目说明文档能够展现从数据库设计、后端逻辑到前端页面的完整实现过程。该系统经过严格调试可直接运行已有165人学习下载适合想通过完整项目提升Spring Boot开发能力、冲刺高评分毕业设计的读者。1. 一个 Spring Boot 的企业 OA 管理系统高分不在页面好看当「Java毕业设计」和「企业OA管理系统」放在一起时最容易做成一堆增删改查但真正能拿高分的项目往往赢在权限模型和审批流是否成体系。Spring Boot 以自动配置和庞大的 starter 生态成为 Java 后端快速落地 OA 的主流选择这也是「基于 Spring Boot 开发的企业 OA 管理系统源码文档说明」成为常见选题的原因。我会沿着搭同类系统的顺序先把骨架和表结构立住再讲登录与 RBAC 权限然后拆审批流状态机最后说源码阅读和文档说明里容易被忽略的加分项。无论你想自己写一个 Spring Boot OA还是拿到一套现成源码做二次开发都可以按下文的顺序找到关键位置。适合的读者是正在做毕业设计、需要短时间交付可运行工程的 Java 方向学生以及想在企业后台复用 OA 通用能力的初、中级后端工程师。下文的建表思路、JWT 片段和状态机配置可以直接照搬到自己项目里。2. Spring Boot OA 系统骨架从分包到表结构与 ORM 选型2.1 一开始就按边界分包后面改需求才不慌OA 系统最典型的特点是持续迭代这周加请假下周加报销再下周加用章申请。如果所有逻辑堆在 Controller 里改一个审批动作就要拖动三个类。我一般会把工程拆成controller、service、service.impl、mapper、entity、dto、config、common流程相关单独开一个process包。com.example.oa ├── OaApplication.java ├── config │ ├── SecurityConfig.java │ ├── MybatisPlusConfig.java │ └── StateMachineConfig.java ├── controller │ ├── AuthController.java │ ├── UserController.java │ └── ProcessController.java ├── service │ ├── impl ├── mapper ├── entity ├── dto ├── common │ ├── Result.java │ └── exception └── process └── StateMachineService.java这个结构的关键是启动类必须放在最外层保证组件扫描覆盖所有子包dto只负责请求和响应entity严格对应表字段controller只做参数接收和路由转发所有状态判断都下沉到service层。以后接 Redis、消息推送或者更多流程类型改动都会被限制在对应包内。有一个容易踩的边界不要把pageNum、confirmPassword这样的非表字段直接塞进entity。否则 MyBatis Plus 的Wrapper构造查询条件时会把它们当作表列名启动时可能不报错运行到分页或条件查询时才暴雷。正确做法是单独建xxxDTO接收再在 service 里转成实体或查询参数对象。2.2 企业 OA 的数据库表不能只有用户表和部门表很多第三方参考项目把sys_user、sys_role、sys_menu三张表建完就声称支持权限系统但放在企业 OA 里审批流没有实例表和数据记录是跑不起来的。下面这组表是我常用的精简核心集合表名关键字段作用sys_userid, username, password, dept_id, status用户账号与状态sys_roleid, role_code, role_name角色定义sys_menuid, parent_id, path, perms菜单树与权限标识sys_user_roleuser_id, role_id用户与角色关联sys_role_menurole_id, menu_id角色与菜单/权限关联oa_process_instanceid, business_key, process_type, current_state, initiator_id, version流程实例oa_approval_recordid, process_id, approver_id, action, comment, create_time审批操作流水oa_transitionid, current_state, event, next_state, editable_by状态机流转规则前五张表解决「谁能登录、能看哪些按钮」后三张表是审批流的底座。注意oa_process_instance里的business_key可以指向业务单据 IDprocess_type用字符串区分请假、报销、用章current_state只存数字状态值不要直接存中文。给oa_approval_record增加create_time索引因为审批数据会持续增长给oa_transition的current_state event加联合唯一索引防止同一状态同一事件出现多条规则。2.3 ORM 选型MyBatis Plus 更贴合 OA 的 CRUD 节奏Spring Boot 里写 OA 时常用 ORM 无非是 Spring Data JPA 和 MyBatis Plus。企业 OA 的大多数数据操作是单表 CRUD、分页查询和条件更新MyBatis Plus 的LambdaQueryWrapper写法直观分页插件配置简单遇到复杂多表查询时又可以直接写 SQL。实体类上的常用注解如下TableName(sys_user) public class SysUser { TableId(type IdType.AUTO) private Integer id; private String username; JsonIgnore private String password; TableLogic private Integer deleted; TableField(fill FieldFill.INSERT) private LocalDateTime createTime; TableField(fill FieldFill.INSERT_UPDATE) private LocalDateTime updateTime; }代码里最关键的是三个细节TableLogic会把deleteById变成UPDATE sys_user SET deleted 1 WHERE id ?审批记录这类审计数据不会被物理删除TableField(fill ...)配合MetaObjectHandler统一填充创建时间和更新时间新加入的员工表、流程表都不用再手动 set 时间JsonIgnore放在密码字段上避免接口把密码哈希抛给前端。参数说明TableName默认启用驼峰转下划线实体里写createTime表里用create_time即可不需要额外配置。逻辑删除字段建议全库统一叫deleted0 为未删除1 为已删除。IdType.AUTO对应 MySQL 自增主键如果以后需要分布式环境再改成ASSIGN_ID雪花ID业务代码不受影响。2.4 代码生成器只生成底座不生成审批逻辑很多 Spring Boot OA 源码用 MyBatis Plus Generator 一股脑生成 Controller、Service、Mapper出来的代码全是空壳 CRUD。真正加分的是只让生成器生成entity和mapper业务层、状态机、权限校验全部手写。否则后面接需求时自动生成的代码反而会成为重构障碍。public class CodeGenerator { public static void main(String[] args) { FastAutoGenerator.create(jdbc:mysql://localhost:3306/oa, root, root) .globalConfig(builder - builder.author(developer).outputDir(src/main/java)) .packageConfig(builder - builder.parent(com.example.oa)) .strategyConfig(builder - builder.addInclude(sys_user, oa_process_instance)) .execute(); } }逻辑说明FastAutoGenerator.create接收数据源地址、用户名和密码globalConfig里的outputDir控制生成文件输出位置.strategyConfig.addInclude指定要生成的表避免对整个库做无差别覆盖。生成后建议删除自动生成的 Controller 和 Service只保留实体类和 Mapper 接口。3. 登录、JWT 与 RBAC 权限OA 系统的第一道边界3.1 RBAC 模型比「用户表加角色字段」更抗扩展简单项目喜欢在「用户表里加一个 role_code 字段」但在企业 OA 里员工可能同时是普通员工和部门审批人一个用户的权限会横跨多个角色。RBAC 把用户、角色、权限拆分用户只关联角色角色再绑定菜单和操作权限正好能表达这种多对多关系。从权限粒度看至少分成两类菜单权限控制「能不能看到这个入口」操作权限控制「能不能点提交或者审批」。以一个常见的角色权限配置为例角色典型权限使用场景普通员工发起申请、查看自己的流程请假、报销发起部门主管审批本部门流程、查看部门申请待办审批系统管理员用户管理、角色配置、菜单配置系统初始化与权限分配登录成功后不再每次请求都查库拿权限而是把用户的角色、菜单权限、操作权限全部放入LoginUser对象再配合 Spring Security 做二次校验。前端控制按钮显隐后端控制接口访问两边认证一致。3.2 用 JWT 保存登录态业务代码无 Session前后端分离是现在的主流交互方式Session 在多端、移动端和浏览器扩展场景下不那么顺手JWT 的无状态特性更适合。一个直接的 JWT 生成工具类如下Component public class JwtUtil { Value(${jwt.secret}) private String secret; Value(${jwt.expireMs}) private long expireMs; public String generateToken(Integer userId, String username) { return Jwts.builder() .setSubject(username) .claim(userId, userId) .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() expireMs)) .signWith(SignatureAlgorithm.HS256, secret) .compact(); } }逻辑说明subject存放用户名claim里追加用户 ID后续接口如果要获取当前用户直接解析 Token 里的userId不需要再走一次数据库查询expireMs是过期时间配合setExpiration生效一般设置 8 到 24 小时。HS256是对称签名算法密钥长度至少 32 字节过短的密钥会被部分库直接拒绝。需要注意JWT 过期前无法主动销毁遇到用户被禁用或改密码时需要在 Redis 里维护一个黑名单。OA 用户量不大用SETEX userId token存禁用标识定时清理即可。3.3 在 Spring Security 中配置动态权限校验固定写死「哪些 URL 要 Admin 角色」在 OA 里不现实因为菜单和权限都是可配置的。常见的做法是自定义FilterInvocationSecurityMetadataSource从数据库动态读取每个 URL 需要的权限标识。Component public class OaSecurityMetadataSource implements FilterInvocationSecurityMetadataSource { private final SysMenuMapper menuMapper; public OaSecurityMetadataSource(SysMenuMapper menuMapper) { this.menuMapper menuMapper; } Override public CollectionConfigAttribute getAttributes(Object object) throws IllegalArgumentException { String requestURI ((FilterInvocation) object).getRequest().getRequestURI(); ListString perms menuMapper.selectPermsByUrl(requestURI); return perms.stream().map(SecurityConfig::createList) .flatMap(Collection::stream) .collect(Collectors.toList()); } }代码的核心是selectPermsByUrl这条查询请求进来后按 URI 查数据库得到该地址需要的权限字符列表例如/process/submit对应oa:process:submit。如果查询结果为空表示接口不需要特殊权限继续走其它过滤逻辑如果有结果再交给AccessDecisionManager校验当前用户是否拥有其中一个权限。参数说明sys_menu表的path字段要建索引否则每次请求都全表扫描。如果前端按钮显示和后端接口校验共用同一个perms字符串就不会出现「前端隐藏了按钮但接口还能直接访问」的绕过场景。3.4 配置文件里不要写死数据库密码和 JWT 密钥OA 源码经常被拿去克隆、分享如果application.yml里直接写明文密码相当于把一个内部系统底牌交了出去。我现在习惯配合环境变量和默认值使用spring: datasource: url: ${DB_URL:jdbc:mysql://localhost:3306/oa?useUnicodetruecharacterEncodingutf8} username: ${DB_USER:root} password: ${DB_PASSWORD:root} jwt: secret: ${JWT_SECRET:change-me-please} expireMs: 86400000${DB_URL:默认值}的含义是优先读取环境变量DB_URL不存在时使用冒号后面的默认值。这样本地启动不需要额外设置而源码推到远端也不会暴露真实敏感信息。密码存储使用BCryptPasswordEncoder登录时用matches比较不要写String.equals比较密码这条在答辩时是一个明确的加分点。4. 审批状态机把办公流程从 if-else 里解救出来4.1 为什么 OA 系统不能靠 if-else 写流程请假审批至少包括草稿、待审批、已通过、已驳回、已撤回这些状态。如果每个流程类型都写if (status 1 action approve)流程一长就会失控。企业 OA 里更重要的是扩展性审批驳回后能再次提交报销单可以加签用章流程可能还要转交。状态机的本质只有四样东西当前状态、触发事件、下一状态、可执行角色。把规则放进数据库比写在代码里更容易维护也更容易对照文档画流程图。4.2 状态机配置表的结构与示例数据沿用第 2 章的oa_transition表状态用数字表示0 草稿、1 待审批、2 已通过、3 已驳回、4 已撤回。请假审批的最小规则如下current_stateeventnext_stateeditable_by0SUBMIT1发起人1APPROVE2审批人1REJECT3审批人1CANCEL4发起人3RESUBMIT1发起人editable_by不直接写角色 ID而是写「发起人」或「审批人」这种抽象身份。因为部门和流程不同实际审批人可能来自不同角色状态机只负责判断「能不能跳转」至于谁能审批由业务 Service 查询后决定。4.3 用 Spring Boot 实现一个通用状态机服务先写一个最简的状态机服务Service public class StateMachineService { private final TransitionMapper transitionMapper; public StateMachineService(TransitionMapper transitionMapper) { this.transitionMapper transitionMapper; } public int nextState(int currentState, String action) { Transition transition transitionMapper.selectOne( new LambdaQueryWrapperTransition() .eq(Transition::getCurrentState, currentState) .eq(Transition::getEvent, action)); if (transition null) { throw new BusinessException(当前状态不允许执行操作: action); } return transition.getNextState(); } }逻辑说明LambdaQueryWrapper通过 lambda 方法引用实体字段避免拼字符串列名。selectOne只允许返回一条数据所以表设计时要在current_state event上建唯一索引。如果找不到流转规则说明这是非法操作直接抛出带状态码的业务异常让前端能提示用户而不是静默失败。这个服务没有成员状态是线程安全的可以直接注入到任何需要状态流转的地方。注意调用方要加Transactional让状态更新和审批记录插入在同一个数据库事务里。4.4 最小可运行的请假审批 Service在 Controller 和 Service 之间流程是这样被驱动的Transactional public Long submitLeave(LeaveDTO dto, Integer userId) { ProcessInstance process new ProcessInstance(); process.setProcessType(LEAVE); process.setCurrentState(0); process.setInitiatorId(userId); process.setBusinessKey(dto.getBusinessKey()); processInstanceMapper.insert(process); ApprovalRecord record new ApprovalRecord(); record.setProcessId(process.getId()); record.setAction(SUBMIT); record.setComment(dto.getComment()); record.setApproverId(userId); approvalRecordMapper.insert(record); process.setCurrentState(stateMachineService.nextState(0, SUBMIT)); processInstanceMapper.updateById(process); return process.getId(); }每次状态变化都写一条oa_approval_record这样事后可以直接根据process_id追溯整个审批链路不需要翻日志。updateById之前给ProcessInstance加version字段配合 MyBatis Plus 乐观锁插件避免两个审批人同时点击「同意」时互相覆盖状态。参数说明processType建议用大写枚举字符串不要用中文businessKey存业务单据 ID例如请假条的 ID审批记录里最好带上操作前的状态和操作后的状态只记结果不记过程会让排错很痛苦。驳回后再提交的场景只需要在状态表里增加一条3 - 1的规则Service 层不用改。4.5 撤回、加签、并发的工作量不在状态机上而在校验上状态机定义的是规则真正耗时的是操作人身份校验。撤回必须校验发起人身份且当前状态必须为待审批加签是在同一个当前状态下插入一条新的审批记录让原审批人变成知会人并发审批靠version乐观锁解决。这些异常场景都要在文档里写清楚只看正常通过路径无法让评审认可。5. 从源码到高分文档说明几个可以立刻动手的细节拿到一套基于 Spring Boot 的 OA 源码或者写完自己的项目接下来要面对的是「如何让读者和评委快速看懂」。有四个细节比堆截图更能提分。5.1 README 里给出阅读导线README 不要只写「导入数据库、启动项目」而是按「启动类 → application.yml → SecurityConfig → StateMachineService → 业务 Controller」的顺序说清楚每个模块负责什么。例如写明config包里的 SecurityConfig 控制哪些 URL 放行process包里的状态机服务负责流程跳转。评审能顺着思路走而不是在 IDE 里乱翻。5.2 用状态事件表替代大段文字文档说明里放一张与oa_transition保持一致的状态事件表当前状态事件下一状态操作角色草稿提交申请待审批发起人待审批审批通过已通过审批人待审批驳回已驳回审批人待审批撤回已撤回发起人已驳回再次提交待审批发起人这张表可以直接复制到文档的流程设计章节比画复杂的时序图更好维护也能和数据库内容一一对应。5.3 本地验证的最小命令演示或自测前准备好三条命令mysql -uroot -p sql/init.sql mvn spring-boot:run curl -X POST http://localhost:8080/oa/auth/login \ -H Content-Type: application/json \ -d {username:admin,password:admin123}第一条导入初始化 SQL脚本必须包含建库、建表、初始角色和管理员账号第二条启动 Spring Boot第三条用 curl 验证登录接口是否返回 token。如果看到accessToken不为空说明认证链路已经跑通。项目里带 Swagger 时启动后访问/swagger-ui/index.html登录后先在 Authorize 里粘贴 token再调试其它接口。5.4 演示前准备审批中数据不要到答辩现场再从填申请单开始展示。正确做法是在数据库里预先插入一条current_state 1的流程实例同时准备一个待审批账号。演示时先展示待办列表再点同意状态从「待审批」变成「已通过」整个流程可以压缩到两分钟内。如果用CommandLineRunner初始化默认账号要在文档中写清初始密码并提醒首次登录后修改这个细节会直接影响评审对项目安全性的印象。本文还有配套的精品资源点击获取
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。