1. 项目背景与定位解读
这几年我接触过不少来自机关单位、事业单位的信息化项目需求,其中“审批”两个字出现频率极高。小到内部请假盖章,大到跨部门事项流转,传统模式基本就是纸质单子来回跑、领导签字排队等。时间一长,流程卡在哪、谁压着没处理、到底办到哪一步了,全凭电话催和人工翻台账。用SpringBoot做一套行政审批系统,本质上是把这类繁琐的线下协同搬到线上,让“跑腿”变成“点鼠标”,同时避免采购那些动辄几十万、重得跑不动的企业级OA平台。
这套项目在高校毕业设计里也比较常见,但它绝不只是一个应付答辩的玩具。一个功能完整的基于SpringBoot的政务事项在线审批平台,可以真实用于某单位内部的事务申请、公文流转、事项备案等场景。它的核心价值在于轻量化:不用部署庞大的中间件集群,不依赖复杂的商业工作流引擎,一套SpringBoot应用加上MySQL数据库就能跑起来,非常适合中小规模机关单位的内部审批需求。开发难度也比较友好,但涉及的技术点和业务逻辑足够撑起一个高质量的项目答辩。
这篇文章的思路是围绕“面向机关单位的轻量化审批流转系统”这一核心定位展开,从整体架构设计、数据模型拆解、核心流程实现、权限控制细节到部署与答辩准备,完整走一遍这类系统的建设路径。无论你是准备拿它做毕业设计,还是刚接手一个类似的内部审批系统需求,都应该有值得参考的地方。
要理解这套系统,先明确三个核心概念:审批、流转、轻量化。审批是业务本质,流转是过程形态,轻量化是技术约束。后面所有的设计决策,都会反复围绕这三个关键词展开。
2. 总体架构与关键技术选型
2.1 为什么选SpringBoot而不是其他框架
先回答一个最常见的问题:政务审批场景下,为什么首选SpringBoot而非SSH或者Spring Cloud?
行政审批系统属于典型的管理信息系统(MIS),它的技术挑战不在于高并发、大数据量,而在于业务逻辑清晰、状态流转严谨、权限控制细密。SpringBoot的价值在这里体现得非常直接:自动配置能力强,内嵌Tomcat,项目启动不需要外部容器,开发调试效率高;配合SpringMVC和MyBatis/JPA,写CRUD和事务控制非常顺手;对前后端分离支持也成熟。
有人说政务项目用Spring Cloud更“高级”,但实际从需求倒推,内部审批系统用户量往往就几百人,并发峰值可能连每分钟一百次请求都到不了。上微服务意味着要引入注册中心、配置中心、网关、分布式事务等一系列基础设施,复杂度翻倍,部署成本也高。一套单体的SpringBoot应用,配合合理的数据库设计和缓存策略,就能轻松扛住这类业务规模。微服务是用来解决复杂组织架构下的弹性伸缩和独立部署问题的,不是用来给一百个人的内部系统制造麻烦的。
选型上还要考虑一点:在机关单位或高校实验室环境里,运维能力参差不齐。SpringBoot打一个可执行Jar包,扔到服务器上java -jar就完事,比维护一堆微服务容器省心得多。这就是所谓轻量化的第一层含义:开发轻、部署轻、运维轻。
2.2 数据存储方案:MySQL为主,Redis为辅
审批系统对数据一致性要求比较高,尤其是审批状态和流程记录,绝对不能丢。MySQL的事务特性在处理这类强一致性的业务上有天然优势。表结构设计上围绕工单、审批节点、操作记录、用户权限这几条主线展开。
Redis在这套系统里不是必须项,但加上会舒服很多。典型用途有两个:一是缓存用户的菜单权限和部门信息,减少每秒钟都可能发生的数据库重复查询;二是做审批提交时的接口幂等性控制,防止前端重复点击导致同一张审批单被提交两次。
部署形态上,MySQL和Redis都跑在单机或者内网虚拟机里就足够。这套系统的瓶颈一定不在数据库性能,而在流程设计的合理性上。
2.3 前端技术栈取舍
当前的毕业设计或小团队项目里,前端方案大致有三条路:
- 传统服务端模板渲染(Thymeleaf):开发简单,适合纯后端为主的项目,但交互体验一般,前后端职责不够清晰。
- 前后端分离+Vue/React:现在的主流方案,前端工程化程度高,页面交互流畅,答辩时展示效果好,但需要Node环境,构建链路长一点。
- 国产轻量UI框架 + CDN引入方式:不搞Node工程,直接用Vue3的CDN版本配合Element Plus的CDN,写单页应用。
我个人更推荐第二种里的轻量变体:用Vue3 + Element Plus + Vite构建前端工程,后端只提供JSON接口。这样做的好处有两个:第一,代码逻辑分层清楚,后端专注审批流转,前端专注表单交互,答辩问起来条理清晰;第二,Element Plus的表格、表单、树形控件、步骤条组件非常适合审批系统的界面表达,比如审批进度可以用Steps组件一页展示,直观又好看。
如果你前端基础薄弱,也不建议退回JSP那套老路。前端代码量控制一下,核心页面控制在十个以内:登录页、首页工作台、待办审批、已办事项、发起申请、申请列表、审批单详情、用户管理、角色权限配置、系统日志。这套页面量级用Vue3完全可驾驭。
2.4 工作流引擎:自研还是用Flowable/Activiti
这是行政审批系统开发里一个绕不开的决策点。很多人在起步阶段会纠结:要不要集成Flowable或者Activiti这类开源工作流引擎?
我的明确建议是:毕业设计或轻量级内部系统,不要引入重型工作流引擎。
原因有几点。Flowable/Activiti的核心理念是BPMN 2.0流程建模,用XML描述流程定义,运行时由引擎驱动任务分配与流转。功能很强大,但它要求开发人员掌握引擎的API、事件机制、身份模型,还要设计流程图的在线建模界面。这部分学习成本不低,调试起来也很折腾。更现实的问题是,这类引擎默认使用的身份体系(用户组、权限)和机关内部系统的RBAC模型未必完全匹配,要适配还得写不少桥接代码。
对于行政审批这类流程高度标准化、节点数量有限的系统,自己设计一张审批流转表反而更清爽。流程状态就那几类:待提交、审批中、已通过、已驳回、已撤回、已归档。审批层级通常也不超过三级:申请人所在科室负责人、归口管理部门、单位分管领导。用状态机加一张流转记录表完全可以覆盖,代码可读性也更好。答辩时向老师解释自研路由逻辑,比解释Flowable的引擎机制要容易得多,也更容易展示你对业务的理解。
3. 数据库设计与核心表结构拆解
3.1 权限模型设计:用户、角色、部门三位一体
机关单位的组织架构通常是树形的:某单位 → 某科室 → 某岗位。系统权限设计需要沿用这个结构,让数据天然按组织维度隔离和聚合。
我这里采用所谓“RBAC + 数据范围”的组合设计。基础表包括:
- sys_user:用户表,存账号、密码(BCrypt加密)、姓名、手机号、状态、所属部门ID。
- sys_role:角色表,存角色编码和角色名称,比如“普通员工”、“科室负责人”、“归口管理员”、“系统管理员”。
- sys_user_role:用户角色关联表。
- sys_menu / sys_permission:菜单权限表,存前端路由标识和后端接口权限标识。
- sys_role_permission:角色权限关联表。
- sys_dept:部门表,树形结构,用parent_id关联上级部门。
数据范围控制是这类系统的灵魂。同样拥有“查看申请单”权限的用户,科室负责人只能看本科室的单子,归口部门管理员可以看全单位对应业务类型的单子,系统管理员则什么都看得到。这个逻辑不建议在前端做过滤,必须在后端每个查询接口里根据当前登录用户的部门归属动态拼接查询条件。我常用的方案是在用户实体上挂一个deptId字段,然后通过自定义注解+AOP切面,在Mapper查询前自动注入数据权限SQL片段。虽然实现上多一点代码,但安全性要扎实很多。
3.2 审批业务表设计:单据主表与流转记录分离
审批单的数据天然分两块:一是业务表单本身的字段,比如“请假申请”的起止日期、事由、天数;“用章申请”的文件名称、用途、份数。二是流转数据,比如当前节点、审批人、审批意见、操作时间。设计时一定要把两者分开,否则表结构会变得异常臃肿。
我倾向这样设计:
- approval_form(审批单主表):保存业务公共字段。比如id、form_type(申请类型编码)、title、content(表单JSON或富文本内容)、status(当前状态)、current_node(当前节点编码)、submitter_id、submit_time、update_time。
- approval_record(审批流转记录表):一条数据对应一次审批动作。包括id、form_id、node_code、approver_id、approver_name、action(同意/驳回/退回)、comment、operate_time。
- approval_node(流程节点定义表):配置不同类型审批单的节点顺序和审批角色。比如type_code、node_code、node_name、sort_order、approver_role_code。
主表里的status与current_node是冗余设计的典型例子。status描述业务处于什么阶段,current_node描述这个阶段具体在哪个节点上等待谁处理。比如status=审批中时,current_node=dept_manager,配合节点定义表就知道当前要等的是科室负责人。这套模型的好处是查询待办时又快又直观:select * from approval_form where current_node = ? and status = ?,完全不需要去关联复杂的流程实例表。
提到form_type,这里有个设计要点。不同类型审批单的字段差异比较大,直接建多张业务子表是一种方案,但会导致代码重复度高。另一种方案是主表加一个content字段存JSON,不同类型的前端表单解析不同JSON结构。后一种方式灵活,但查询统计比较麻烦。实践上我建议折中:公共字段放主表,特殊字段用JSON扩展。比如请假单里的具体天数可以提取为主表字段,出差地点等个性化内容存JSON。这样既能保证核心数据的结构化,又能让系统保留扩展能力。
3.3 关键索引与数据一致性设计
审批系统表数据量不会大到分库分表,但查询性能依然要重视。核心索引不能省:
- approval_form表:联合索引(form_type, status)、索引(submitter_id)、索引(current_node)。
- approval_record表:索引(form_id),支撑按单据ID查完整流转历史。
- sys_user表:唯一索引(username)。
事务控制主要在两个场景:一是提交审批时,要同时更新主表状态和插入第一条流转记录,必须在一个事务里完成,否则可能出现单子状态变了、流转记录为空的情况;二是审批操作时,更新主表当前节点并写入本次审批意见,也要保证原子性。
这里有一个很多人忽略的细节:并发审批控制。两个人同时打开同一张单子,如果都点了同意,状态更新要靠乐观锁或者条件更新来兜底。我习惯在approval_form表加一个version字段,更新时带上where version = #{oldVersion},影响行数为0说明已被其他人处理,直接提示“该申请单已被处理,请刷新后查看”。这个机制代码成本极低,但能让答辩评委觉得你考虑问题很全面。
4. 核心功能模块实现与操作详解
4.1 登录认证与Token会话管理
政务审批系统对账号安全有要求,不能像普通练习项目那样明文存密码。密码存储必须用BCrypt加密,Spring Security自带BCryptPasswordEncoder可以直接用。登录成功之后,我选择返回一个自定义Token,也就是UUID串,存到Redis里,key为login_token:{token},value为当前用户ID和权限缓存,有效期设为2小时,每次请求由拦截器校验。
为什么不直接引入Spring Security的完整过滤链?因为毕业设计或轻量项目里,完整的Security配置会让简单需求变得复杂,尤其是自定义权限注解时需要处理一堆配置细节。我的做法是写一个HandlerInterceptor,拦截除了登录接口以外的所有请求,从Header里拿token,去Redis查用户信息,查不到就返回401。配合一个自定义注解@RequirePermission标记在接口方法上,再用AOP做细粒度权限校验。这套方案的代码量可控,逻辑透明,出问题也容易排查。
Token会话管理里有一个细节值得展开:注销登录时必须删除Redis中的token。如果忘了这一步,用户点了退出但token还有效,会留下安全隐患。另外可以在Redis里给同一个用户只保留最新一个token,实现单点登录效果,即后登录的会把前一个踢下线。机关内部系统对账号同时在线数量通常有管控需求,这个小功能很加分。
4.2 发起审批与动态表单解析
发起审批是整个流程的入口,页面交互上包含三个要素:选择申请类型、填写业务数据、提交给下一步节点。申请类型决定表单模板,前端根据类型编码动态渲染不同字段。
我实践中的做法是:在系统初始化时,前端调用/api/form/template/{typeCode}接口,后端返回一个JSON Schema结构,描述该类型表单包含哪些字段、类型是输入框还是日期选择器、是否必填、最大长度等。前端拿到Schema后用动态渲染组件生成表单页。这样做一次,后续新增申请类型时不需要改动前端页面结构,后端只需要新增一条流程节点定义和表单模板配置。
表单提交时,前端把业务数据作为JSON对象传后端。后端要做两层校验:第一层是基础非空校验,第二层是业务规则校验。比如请假申请的结束时间必须晚于开始时间,这个逻辑后端必须再写一遍,不能只靠前端判断,否则绕过页面直接调接口会出现脏数据。
提交成功后,表单主表status设为“待审批”,current_node指向第一个审批节点。假如某类事项只需要部门负责人审批,那么待办列表里这个事项就出现在该负责人的工作台中。
4.3 审批流转核心逻辑:同意、驳回与退回修改
审批操作不是简单地改一个状态字段,要把它做成一连串有约束的流转行为。
以一条三级审批流程为例:员工提交 → 科室负责人审批 → 归口管理员审批 → 分管领导审批 → 归档。
- 同意:当前节点处理人写入审批记录,action为agree,然后判断是否还有下一节点。有则把current_node向后推进;没有则把status改为“已通过”,完成整个流程。
- 驳回:分为两种语义。一种是直接终止流程,表单回到“已驳回”状态,发起人可以在列表里看到驳回意见,修改后重新提交;另一种是退回上一步,让上一步的审批人重新处理。实现上我都会在界面上做两个按钮:驳回与退回,分别对应不同的流转策略。
- 撤回:当发起人提交后发现填错信息,在下一个节点尚未处理时,允许撤回申请。这个操作不是所有系统都有,但加上会显得业务理解很深。
从代码实现上看,我给审批操作设计了一个统一接口:approve(formId, action, comment, currentNode),action支持字符串常量:AGREE、REJECT、RETURN、RECALL。每次操作都先校验当前用户是否为current_node对应的角色,再校验操作前后的节点状态是否匹配,然后开启事务执行主表更新和流转记录插入。
流转逻辑用状态机来表达最清晰。每种申请类型的节点顺序在approval_node表里定义好了,代码里只需要一个nextNode(typeCode, currentNode)的查询方法,就能得到下一步去向。这种设计天然支持配置化,以后要在一二级审批之间插入一个环节,只需要改表数据,不用改代码。
4.4 待办与已办查询优化
待办查询和已办查询是审批人每天打开系统后最常用的功能。待办的定义是:审批单当前节点对应当前登录人的角色,且审批单状态为“审批中”。已办的定义是:当前登录人在approval_record表里有过操作记录的单子,取最近一条操作时间倒序展示。
待办查询的SQL看起来简单,但有一个坑:如果某个人同时拥有多个角色,例如既是科室负责人又是归口管理员,那么他可能需要在同一列表里看到两个节点下的待办事项。我用find_in_set(role_code, current_node_roles)的思路来处理有点别扭,更推荐的做法是把节点配置表里的审批角色做成多对多关系,查询时用IN匹配角色编码集合,或者直接用EXISTS子查询判断当前用户角色是否与节点配置角色有交集。
已办查询要防止重复数据。因为approval_record表里一条单子可能有多条记录,直接用表单主表join记录表会让同一单子出现多行。我用子查询先取每个form_id最新的操作时间,再关联主表,这样列表展示的就是“我最近处理过的申请单”,而不是每次操作的碎记录。列表页建议加上时间范围筛选和关键字搜索,答辩演示时也更好讲。
4.5 首页工作台与数据统计
首页工作台是一个很能体现产品完成度的模块。我建议做四块内容:
- 今日待办数量:当前登录人各节点待办总数。
- 本月已办数量:当前登录人本月审批完成次数。
- 审批中事项状态分布:用饼图展示所有申请单在不同状态下的数量。
- 最近动态:当前用户提交或审批过的最近10条记录。
统计SQL不复杂,重点是注意group by的正确使用和时间范围的边界条件。比如“今日待办”应该是动态取当天零点,而不是写死某一天。“本月”同理,用Java8的LocalDate计算出月初再拼查询条件。前端图表使用ECharts即可,社区资料多,样式也专业。
5. 轻量化与安全合规设计要点
5.1 部署方案与文件存储策略
机关单位内部系统的部署环境通常比较朴素:一台Windows Server或CentOS虚拟机,内网访问。SpringBoot应用的部署方式前面提到过,打可执行Jar包,配合systemd或者Windows计划任务做开机自启,这是最朴素的方案。如果需要更强的可观测性,可以加一个Spring Boot Admin,但这对轻量项目来说通常不是必须的。
审批系统往往涉及附件上传:申请单里附上文件、盖章材料扫描件等。附件存储不建议直接塞进数据库,也不建议放到应用目录下,因为重启或重新部署时容易丢失。推荐的轻量做法是配置一个独立的磁盘目录,例如/data/approval/files,数据库只存文件的相对路径或由UUID生成的文件名。后续有条件再迁移到对象存储,改动也小。
上传接口要限制文件类型和大小。常见坑是只校验了文件后缀,没校验MIME类型,导致有人传一个改名后的可执行文件。稳妥做法是后缀名白名单加类型白名单双重校验,同时用随机UUID重命名文件,避免路径穿越问题。文件大小限制建议设置为单文件20MB以内,超过的在网关上直接拦掉。
5.2 操作日志与数据审计
机关单位的业务系统对可追溯性要求很高。审批记录本身是天然审计数据,但系统层面的操作也要留痕,比如谁在什么时间给某个用户分配了角色、谁修改了流程节点配置。
我设计了sys_operation_log表,字段包括operate_user_id、operate_user_name、operate_type、operate_content、request_uri、ip、operate_time、cost_time_ms。实现方式很简单:写一个AOP切面,@Around拦截所有带有@Log注解的Controller方法,执行前记录开始时间,执行后把方法参数、返回状态、耗时统一写入日志表。
有个操作细节:日志写入不要阻塞主业务流程。异步写日志是标配做法,用Spring的@Async注解就能实现,但要注意异步线程池的配置,别默认的SimpleAsyncTaskExecutor无限创建线程。我一般配置一个核心线程数固定为4的ThreadPoolTaskExecutor,队列容量1000,够了。
5.3 安全防护要点与权限校验细节
轻量化不等于裸奔,基础安全措施必须到位。第一道防线是密码安全,BCrypt加密+登录失败次数限制。登录失败超5次锁定账号15分钟,这是机关单位比较常见的策略,实现也不复杂,Redis里存失败次数的计数器即可。
第二道防线是接口层面的越权防护。审批系统里最容易出现的漏洞是水平越权:普通用户直接改URL里的formId去操作别人的申请单。开发时必须始终坚持一个原则:审批单操作前,除了验证角色,还要验证当前用户与该单据是否有业务关联。比如提交者只能撤回自己的单子,审批者只能处理当前节点属于自己的单子。这个校验放在每个操作方法的第一行,不依赖前端隐藏按钮,因为接口是可以被直接调用的。
第三道防线是SQL注入。使用MyBatis时,能在XML里写死条件的就绝不拼接字符串,动态排序字段用白名单映射。这些细节不一定在答辩时展开讲,但代码审查时老师一眼就能看出你有没有安全意识。
6. 前端页面设计与交互体验优化
6.1 页面结构与导航设计
后台管理系统的页面结构有很成熟的模式:左侧菜单栏、顶部用户信息栏、主体内容区。审批系统要额外关注的是“工作台”的入口权重,它是用户每天打开系统后第一时间要看的地方。
菜单建议分为四大块:工作台、申请中心、审批中心、系统管理。申请中心里展示用户可以发起的申请类型列表,每种类型配一个卡片图标,点击直接进入对应表单页。审批中心里分待办与已办两个Tab,待办列表里的每一行都显示申请类型、标题、发起人、提交时间、当前节点,右侧是“处理”按钮。系统管理是管理员专属板块,包含用户管理、角色权限、流程配置、操作日志。
从交互细节上,待办列表要支持点击行直接进入详情页,不要让人找了半天才发现“处理”按钮在哪。详情页采用上下布局:上面是申请单的原始信息展示,下面是审批流转记录时间线,最底部是审批操作区。这种布局也方便后期加移动端适配,直接从“信息下方堆操作”的模式抽取成独立的操作面板即可。
6.2 审批进度可视化
审批进度可视化是展示开发完成度时最亮眼的模块。Element Plus的Steps组件天然适合做审批节点进度条,但单纯的步骤条只能展示“到哪一步了”,不能展示每一步是谁在什么时间处理的、意见是什么。所以我会搭配一个Timeline时间线组件,把approval_record里的流转记录按时间倒序渲染成瀑布式的操作历史。
实现上需要注意:Steps组件的active值要根据当前节点编号计算,而不同申请类型的节点数量不同,所以前端拿到审批单详情时,要同时返回一个节点列表和当前节点的索引下标。后端接口设计为返回一个ApprovalDetailVO,里面包含表单数据、当前状态、节点列表、流转记录列表、操作按钮可用状态。这样一个接口就能渲染整个详情页,前端逻辑也更清晰。
6.3 表单校验与用户体验细节
表单交互有一个显性问题:动态表单的校验规则和Schema绑定。每一类申请表配置JSON Schema时,字段除了类型信息,还要带上rules数组,前端根据rules生成Element Plus的表单校验规则。例如请假单的“结束时间”字段,rules里加一个自定义validator:endTime > startTime。后端提交时用Hibernate Validator再做一次同样的校验,防止绕过前端。
另外,审批系统对“空状态”的体验要求很高。一个新入职员工打开待办列表,往往一条数据也没有。这时候页面不能是空白一片,要给出引导文字和发起申请入口。这些细节成本极低,但对产品完成度的提升立竿见影,答辩演示时也是加分项。
7. 部署上线与环境配置实战
7.1 开发环境与初始化数据准备
开发阶段的推荐环境是:JDK 1.8或11、Maven 3.6+、MySQL 5.7或8.0、Redis 5.x以上、Node 16左右。项目初始化时,Maven依赖建议锁定版本,SpringBoot用2.7.x系列即可,太新的3.x版本部分依赖兼容有坑。MyBatis用mybatis-plus可以少写很多XML,但如果你希望展示原生SQL功底,用MyBatis原版也行,看个人取舍。
数据库初始化SQL除了建表语句,还要准备一批演示数据。演示数据非常关键,包括:三个科室、五个用户、不同角色分配、三类申请表单配置、已经走了一半流程的审批单若干条。这些数据会让你打开系统后立刻有事可做,而不是对着空表格发呆。为了让答辩演示顺畅,建议先把演示账号单独列出:一个系统管理员账号用于配置管理,一个科室负责人账号用于审批,一个普通员工账号用于发起申请。
7.2 后端打包与Linux服务器部署步骤
后端部署步骤如下:
- 修改
application-prod.yml配置文件,数据源指向正式库、Redis地址改为内网IP、日志路径调整为绝对路径。 - 在项目根目录执行
mvn clean package -DskipTests,在target目录下得到可执行Jar。 - 上传Jar到服务器
/opt/approval/目录,编辑application-prod.yml中相关参数,确认数据库初始化和demo数据脚本已执行。 - 使用systemd配置服务自启,或者直接
nohup java -jar approval-system.jar --spring.profiles.active=prod &启动。 - 使用Nginx反向代理本机端口,同时托管前端静态资源。
前端部署更简单:npm run build生成dist目录,把dist里的静态文件复制到Nginx的html/approval下,配置Nginx将/api/前缀的请求转发到后端服务端口。
有一个常见的坑一定要提醒:后端接口路径和前端访问路径要保持一致的上下文。例如后端项目配置server.servlet.context-path=/api,那么Nginx转发时就不要再把/api剥掉,否则会出现404。我在实际项目中习惯统一前端发请求使用/api前缀,Nginx把它们全量转发到SpringBoot,后端Controller的RequestMapping不需要重复加/api,逻辑最清晰。
7.3 日志配置与日常运维
生产环境日志建议按天滚动。SpringBoot里配置logback-spring.xml,设置两种日志:info日志输出到logs/info/info-%d{yyyy-MM-dd}.log,保留30天;error日志单独输出到logs/error/error-%d{yyyy-MM-dd}.log,保留90天。同时把控制台输出调整为只打印WARN以上级别,防止刷屏。
日常运维观察两个东西:系统吞吐量和error日志量。可以用jstat -gc查看JVM GC情况,用jstack查看线程状态。如果系统长期运行后响应变慢,优先检查数据库连接池配置是否合理,HikariCP默认最大连接数是10,对于百人规模系统够用,但连接泄漏时很容易耗尽。检查代码时重点看Redis连接、数据库连接、异步任务是否都存在正确的关闭或归还逻辑。
8. 常见坑点与排查经验
8.1 审批状态与流转记录不一致
这是我自己在开发过程中踩得比较惨的一个坑。早期版本里,更新主表状态和插入流转记录分了两个Service方法调用,由于没有加事务,偶尔会出现主表状态已经改成“已通过”,但流转记录里却没有最后一条审批意见的情况。这种现象在本地低并发测试时几乎不出现,一旦有演示环境多用户操作,问题就冒出来了。
解决思路很简单:审批操作统一收敛到一个Service方法里,方法上标注@Transactional(rollbackFor = Exception.class)。注意rollbackFor一定要指定,否则默认只对RuntimeException回滚,自定义的BizException如果继承自Exception就不会触发回滚,那事务就形同虚设了。
另一个容易忽略的是:事务方法不能同类内调用,否则Spring AOP代理不生效,@Transactional会静默失效。如果代码里出现同类方法直接调用内部审批方法的写法,事务就不起作用。排查时可以用日志观察SQL是否在同一事务内提交,或者直接看数据库连接是否被复用。
8.2 待办数量统计不准确
待办数量容易出错的场景出现在多角色用户身上。某人同时是A类申请的科室负责人和B类申请的归口管理员,查询待办总数时如果直接用current_node = roleCode做条件,就会漏掉另一类节点的数据。我改成角色编码集合IN匹配的方式之后,数据才准确。
还有一个隐蔽的边界情况:被驳回的申请单状态已经是“已驳回”,如果发起人还没重新提交,它不应该出现在任何人的待办列表里,但仍然会出现在发起人的“申请列表”中。很多初学者在写待办查询时容易把status条件写漏,导致驳回单还赖在审批人待办里,看起来就像臭虫一样。
8.3 前端联调时的跨域问题
前后端分离项目在本地开发时,前端端口是8080,后端接口是8081,跨域问题不可避免。网上最常见的解决办法是在后端加CorsFilter全局允许跨域,但直接allowedOriginPatterns("*")配合allowCredentials(true)在一些浏览器版本上有隐患。开发环境这样做图省事问题不大,生产环境通过Nginx同域部署后,反而建议把后端跨域配置关掉,让Nginx统一管理请求来源。
如果后端关掉跨域后前端联调又遇到问题,可以先用curl模拟请求确认后端本身逻辑正常,再查Nginx的proxy_set_header Host等配置。我遇到过前端明明带了token但还是401的情况,排查到最后是Nginx里没有正确透传Authorization请求头,加上proxy_set_header Authorization $http_authorization;后解决。
8.4 文件上传大小生效配置多级联调
SpringBoot的文件上传有两个配置项:spring.servlet.multipart.max-file-size和max-request-size。如果只配了前者没配后者,多文件同时上传时请求体总大小超限还是会被拦截。同时如果用了Nginx,还有client_max_body_size参数要一起调整,默认1MB会导致大文件上传返回413。
这类配置问题有个共同特点:报错信息往往不直观,排查起来一层一层试。最佳实践是前端在上传前先做一次文件大小和类型的预校验,把大多数不合法的请求在前端拦掉,后端和Nginx的限制只是兜底,这样用户体验最好,也不会被各种代理层配置坑到。
9. 项目优化方向与答辩准备建议
9.1 可扩展方向一:流程模板化配置
目前系统里审批节点的顺序是通过数据库表配置的,但节点类型、审批角色组合还可以做得更灵活。比如增加一个流程模板管理页面,系统管理员在界面上拖拽或配置表单编码对应的节点序列,不用改代码就能上线一个新的审批类型。这个方向适合时间充裕且想拔高项目档次的开发者。
实现思路是把流程定义抽象成JSON字符串存库,节点之间的条件分支也顺带定义进去。后端在审批时解析JSON流程定义,根据当前节点编码和业务数据决定下一步走向。这块做好之后,系统就不只是“某几个固定审批流程”的工具,而是一个真正意义上的流程平台雏形。
9.2 可扩展方向二:消息提醒与移动端适配
内部审批系统的用户体验很大一部分取决于消息触达。当审批单流转到某个节点时,如果系统能主动给审批人发送消息提醒,流程效率会有明显提升。轻量方案是接入企业微信或钉钉的自定义机器人Webhook,后端在审批流转时调用Webhook推送文本消息。再进一步可以做一个站内信表,登录后在首页展示未读消息数。
移动端方面不用单独开发App,把前端页面适配成响应式布局,或者直接用H5模式嵌入企业微信工作台,成本低且实用。审批人大多数时候只需要看详情和点同意/驳回,这些操作在移动端的适配难度并不高。
9.3 毕业设计答辩的重点准备清单
答辩时老师的问题通常会集中在“为什么要这样做”和“如果出现某种情况怎么办”。建议准备以下内容:
- 画一张系统架构图:前端Vue、后端SpringBoot、MySQL、Redis、Nginx五层结构,标注数据流向。
- 讲清楚核心流程:以请假申请为例,从发起、部门审批、归档全过程,每一步的表结构和状态变化都了然于胸。
- 准备好权限模型的追问:为什么用RBAC加数据范围?角色和数据权限如何在SQL层面过滤?
- 危机场景应答:问及并发重复审批时,可以回答使用version版本号乐观锁;问及系统故障恢复时,可以回答MySQL事务和流转记录保障数据一致。
- 演示脚本要预演多遍,尤其是提交申请、审批、驳回、重新提交的闭环流程,不要现场才操作。
9.4 从毕业设计到真实落地的差距提醒
如果这套系统要真正放到机关单位内部运行,除了功能开发,还要考虑数据备份策略、服务器安全基线、个人信息保护要求等。数据库每天备份,保留至少30天;系统需要有初始化管理员账号,并要求第一次登录强制改密码;关键业务操作日志需要长期留存,避免被清理掉。
这些都是真实环境里必须面对的问题,但它们恰恰也是这个项目从“能演示”走向“能落地”的分水岭。作为毕业设计,把它做到功能闭环、代码整洁、文档清晰,已经是一个很好的水准。如果学有余力把运维侧的内容也纳入项目文档,那这一套作品的完成度就相当高了。
10. 写在最后的个人体会
开发行政审批系统这类项目,给我的最大感受是:技术难度不在于某个框架用得多炫,而在于对业务状态流转的理解是否足够深入。审批这个动作看似只是点一下按钮,背后跟着的是状态变更、权限校验、数据审计、消息通知一整套连锁反应。任何一个节点没有考虑到,用户在真实使用中就会感到别扭、不信任。
如果你是第一次接触这类项目,我建议先别急着写代码。花一个下午把审批场景完整走一遍:谁发起、填什么、谁审批、不同意怎么办、想撤回怎么办、哪些人能看见这张单子。把这些业务问题都列清楚了,表设计和代码结构自然会浮出水面。这也是我带过几个同学之后的共同经验:业务想得越透,代码出问题的概率越低。
最后补一句手头能直接用的经验:无论工期多紧,审批操作的事务边界和权限校验边界绝对不能省。这两个地方一旦出问题,不是修一个bug那么简单,而是整套系统的可信度都会崩塌。把这两条守住,这个项目就算成功了一大半。