☰
SpringBoot+Vue前后端分离政务系统实战解析
2026/9/30 7:58:20 网站建设 项目流程

1. 为什么是"前后端分离":在线政务中心的架构与模块边界

做政务类的在线服务中心,最容易被卡住的往往不是业务流程本身,而是前后端分离这套组合怎么从零到一地落到一个可以被交付的代码库。之前带团队做过一个基于SpringBoot+Vue+MyBatis+MySQL的前后端分离在线政务服务中心项目,仓库代号叫nrlwabo——说是政务服务中心,本质上是把线下的申报、审批、进度查询这些动作搬到网页端,对外提供统一入口,对内提供管理后台。项目本身并不新鲜,但它的价值在于完整:有登录鉴权、有事项申报、有后台管理、有文件上传,还附带一套可以直接照着做的部署教程。这篇文章我想把它拆开聊清楚,适合那些手里已经有点SpringBoot和Vue基础、但没完整做过一个前后端分离交付项目的人。

1.1 从政务大厅到网页端的业务映射

政务服务中心这个词听起来很大,真正落到系统里其实就是几个固定动作:用户登录、查看办事指南、提交申报材料、查询审批进度、后台人员审核、统计归档。线下大厅里"取号→等窗口→交材料→拿回执"这条链路,搬到网页上就变成了"注册登录→选事项→填表单→传附件→提交申报→查状态"。nrlwabo在拆需求的时候,业务方特别强调要把"事项"和"申报"分开。事项是死的,比如"企业设立登记""社保卡补办",它包含办事条件、材料清单、办理时限;申报是活的,是用户针对某个事项发起的一次具体申请。这个区分在数据库设计上帮了大忙,后续加业务模块时基本不用动核心表结构。

1.2 后端按业务域拆,前端按页面拆

前后端分离项目最容易犯的错,是把后端里的Controller也照着前端页面去拆。比如前端有个"个人中心"页面,后端就建一个PersonalCenterController,这种做法前期写起来快,后期一加需求就非常痛苦。nrlwabo的做法是,后端严格按业务域拆:用户认证一套、事项管理一套、申报流程一套、附件存储一套;前端才按页面拆:登录页、首页、事项列表页、申报填写页、进度查询页、后台管理页。两边通过接口文档对齐,而不是通过"页面名"对齐。这样后端接口可以被PC端、管理后台甚至以后的手机端复用,前端页面掉了一个也不太影响整体接口层。

1.3 技术栈取舍:为什么是这个组合

技术选型上,SpringBoot+Vue+MyBatis+MySQL是当前Java政务类项目里最保守也最稳的组合,没有之一。SpringBoot解决的是配置地狱问题,内嵌Tomcat让部署从"装Tomcat→扔war包→配数据源"变成"一个jar跑起来"。Vue做页面交互效率高,配合Element Plus这类组件库,表单、表格、弹窗、分页这些政务系统高频控件直接拿来用。MyBatis则是Java政务项目里的常青树,SQL由开发自己控制,遇到复杂多表关联时不至于被ORM的映射规则带跑偏。MySQL更不用说了,运维成本低,政务类系统的数据量撑到百万级没有任何问题。

这套组合能火这么多年,核心在于"每个环节都有人会、出了问题都有人能接"。它不炫技,但每一层都有大量可查的踩坑记录,对交付项目来说这是最大的优势。nrlwabo选型周期很短,基本是第一天定了这套,第二天就开始建库建表。

2. 数据库设计:政务场景的表结构怎么定

数据库是一套系统的地基,nrlwabo在表结构上踩过一轮坑之后重新设计了一遍,这里挑几张最核心的表说说思路。整个库一共十来张表,分三类:系统类(用户、角色、部门)、业务类(事项、申报、附件)、日志类(操作日志、登录日志)。

2.1 核心表关系

先看用户和角色的设计,政务系统里权限几乎是标配,但不是每个系统都要上SpringSecurity那套细粒度权限。nrlwabo这里用了一个简化RBAC模型:

CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键', username VARCHAR(50) NOT NULL COMMENT '登录名', password VARCHAR(100) NOT NULL COMMENT 'BCrypt密文', real_name VARCHAR(50) COMMENT '真实姓名', dept_id BIGINT COMMENT '所属部门ID', status TINYINT DEFAULT 1 COMMENT '1启用 0禁用', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_username (username) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表'; CREATE TABLE sys_role ( id BIGINT PRIMARY KEY AUTO_INCREMENT, role_code VARCHAR(50) NOT NULL COMMENT '角色编码', role_name VARCHAR(50) NOT NULL ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='角色表'; CREATE TABLE sys_user_role ( user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, PRIMARY KEY (user_id, role_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

用户表只存登录凭证和基础信息,角色用编码区分,比如APPLICANT代表办事群众,APPROVER代表后台审批人。前端拿到角色编码后决定显示什么菜单、能不能点审核按钮。数据权限这一层先不做,因为政务服务场景下,普通用户只能看自己的申报记录,后台审批人按部门过滤,这两条SQL都很好写,不需要引入复杂的规则引擎。

2.2 业务表与状态机设计

业务表是nrlwabo迭代最多的部分。事项表biz_service_item存的是静态指南信息,包括事项名称、办理条件、所需材料JSON、办理时限。申报表biz_application存用户提交的实例,核心字段如下:

CREATE TABLE biz_application ( id BIGINT PRIMARY KEY AUTO_INCREMENT, application_no VARCHAR(32) NOT NULL COMMENT '申报编号,如GZ202506001', item_id BIGINT NOT NULL COMMENT '事项ID', user_id BIGINT NOT NULL COMMENT '提交人ID', form_data TEXT COMMENT '表单数据JSON', status TINYINT NOT NULL DEFAULT 0 COMMENT '0草稿 1已提交 2审批中 3已通过 4已驳回', approver_id BIGINT COMMENT '审批人ID', approve_comment VARCHAR(500) COMMENT '审批意见', create_time DATETIME DEFAULT CURRENT_TIMESTAMP, update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, KEY idx_user_id (user_id), KEY idx_status (status) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='申报表';

这里最值得说的是状态字段。政务项目中审批状态非常容易出现"草稿、提交成功、审批中、待补充材料、已通过、已驳回、已归档"这种七八种状态,如果你用字符串裸存,后期写统计SQL的时候一定想砸键盘。nrlwabo的做法是给每个状态定义数字枚举,并且把状态转换写死在Service层。状态流转设计没有用工作流引擎,因为审批链路就是"提交→受理→审批→通过/驳回"一条直线,上Activiti或者Flowable属于用大炮打蚊子,维护成本还高。后面如果要加撤回、补正、会签这类分支动作,再在Service层加对应的方法即可,不改变表结构。

2.3 附件表:政务项目离不开的设计

政务申报必须传附件,身份证扫描件、营业执照、申请表盖章件,这些都是标配。附件单独建表而不是在申报表里塞一个JSON字段,是为了后续做文件归集、过期清理、下载审计。nrlwabo的附件表设计是"一条申报记录对应多条附件,每条附件记录只存文件的元信息和存储路径":

CREATE TABLE biz_attachment ( id BIGINT PRIMARY KEY AUTO_INCREMENT, biz_type VARCHAR(30) NOT NULL COMMENT '业务类型:APPLICATION/AVATAR', biz_id BIGINT NOT NULL COMMENT '业务主键ID', file_name VARCHAR(200) NOT NULL, file_path VARCHAR(300) NOT NULL COMMENT '相对路径/存储对象key', file_size BIGINT COMMENT '字节数', upload_user_id BIGINT, create_time DATETIME DEFAULT CURRENT_TIMESTAMP, KEY idx_biz (biz_type, biz_id) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='附件表';

文件本身存在本地磁盘目录下,数据库只存路径。这套方案在单机部署阶段最省事,后面如果迁移到云服务器,把file_path换成OSS的key值,改造量也不大。

2.4 逻辑删除与公共字段

政务系统对数据留存要求高,删除操作基本不做物理DELETE,nrlwabo的统一做法是加deleted字段。MyBatis里通过全局配置实现逻辑删除自动拼接,比如配置逻辑删除值为1,未删除为0,所有查询SQL都会自动带上deleted=0条件,开发人员写SQL时可以忘掉这件事,但执行结果不会错。这件事一开始没做,后来补上时全表刷了一遍SQL,教训是建表第一天就把deleted加上,别回头补。

3. 后端实现:SpringBoot接口侧的核心链路

业务表设计好了,接下来就是后端把这套东西串起来。这一节不讲全部代码,重点讲nrlwabo后端里几个"过了一道坎"的地方:统一返回结构、登录鉴权、TypeHandler处理JSON字段、以及申报提交的事务控制。

3.1 工程结构与统一返回体

后端包结构是这样:controller、service、mapper、entity、common、config。common里放统一返回类Result、异常处理Handler、工具类。统一返回结构是Json格式的code、message、data:

public class Result<T> { private Integer code; // 200成功 401未登录 500业务异常 private String message; private T data; }

所有Controller返回值都包一层Result,前端axios拦截器统一判断code。这个习惯看起来增加了一点代码量,但联调阶段非常省心——前端不用在每一个接口里单独处理错误逻辑,后端全局异常处理器把参数校验异常、业务异常、系统异常分别映射到不同code,前端只需要写一次"code不等于200就弹message"。

3.2 登录鉴权:为什么选JWT+拦截器而不是SpringSecurity

政务系统对安全的要求高,但nrlwabo没有引入完整的SpringSecurity框架,原因很实际:这个项目的权限模型只有两类角色,SpringSecurity那套过滤器链、UserDetailsService、方法级安全注解对团队来说维护成本大于收益。最后选型是JWT+自定义拦截器,二十多行代码解决登录态问题。

登录接口逻辑为:用户提交账号密码后,后端用BCrypt校验密码,通过后生成一个JWT,把userId和角色编码放进去,token有效期设为两小时,返回给前端。前端每次请求都在Authorization头带上Bearer token。后端写一个HandlerInterceptor,在preHandle里解析token、校验签名和过期时间、把userId放入ThreadLocal,校验失败直接返回401。这个方案的优点是零状态,后端重启不会把用户踢下线;缺点是token没法主动失效,所以nrlwabo在改密码接口里额外做了token版本号,密码一改,旧token签名验证时版本号不一致就会被拦下来。

3.3 MyBatis的TypeHandler:把JSON字段优雅地映射成对象

前面表结构里事项表有个"所需材料JSON"字段,申报表有个"表单数据JSON"字段,这些JSON在Java里如果都用String接收,写业务代码时每次都要手动序列化/反序列化,太烦了。MyBatis的TypeHandler正好解决这个问题。

@MappedTypes(List.class) public class StringListTypeHandler extends BaseTypeHandler<List<String>> { @Override public void setNonNullParameter(PreparedStatement ps, int i, List<String> parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, JSON.toJSONString(parameter)); } @Override public List<String> getNullableResult(ResultSet rs, String columnName) throws SQLException { String value = rs.getString(columnName); return value == null ? new ArrayList<>() : JSON.parseArray(value, String.class); } // 其余两个重载方法同理 }

在实体字段上标注@TableField(typeHandler = StringListTypeHandler.class),写SQL时显式指定:

<resultMap id="ServiceItemMap" type="ServiceItem"> <result column="required_materials" property="requiredMaterials" typeHandler="com.nrlwabo.common.handler.StringListTypeHandler"/> </resultMap>

这样业务代码里拿到ServiceItem对象,getRequiredMaterials()直接就是List ,省掉了中间环节。这个设计在后面接入表单动态渲染引擎时帮了大忙,前端可以根据材料清单自动生成上传控件。

3.4 事务控制:一次申报动作不能只做一半

政务申报的提交动作涉及三张表:申报主表插入记录、附件表批量插入文件记录、可能还要更新用户的申报次数统计。这三步必须在同一个事务里,否则就会出现"申报主表有数据,附件查不到"这种让人崩溃的脏数据。

nrlwabo在Service方法上加@Transactional(rollbackFor = Exception.class),并且特别注意rollbackFor要指定Exception.class。因为Spring默认只在运行时异常时回滚,如果你在方法里try-catch吞掉了异常,或者抛了一个受检异常,事务是不会回滚的。这个坑很经典:有个同事在提交申报的方法里对附件上传做了try-catch,自信地认为事务没问题,结果主表成功、附件全丢。排查半天发现是catch块吞掉了异常导致@Transactional没生效。加了rollbackFor之后,所有异常统一往外抛,由全局异常处理器兜底。

4. Vue前端:工程化初始化到业务页面

前端是政务系统里用户直接感知的那层,nrlwabo前端代码写得不复杂,但工程化该有的都有:环境变量、路由守卫、axios封装、权限菜单、组件复用。这一节按实际开发顺序讲下来。

4.1 环境准备与脚手架搭建

Vue项目创建用的是Vite,因为Webpack在大型项目里冷启动确实慢,Vite的开发服务器秒开。环境准备阶段有两个容易卡住的位置:Node版本和npm镜像源。nrlwabo项目锁定的Node版本是16以上,如果你本机Node版本太高(比如18以上的某些小版本),装依赖时可能出现node-sass相关的报错,建议直接用Vite官方模板加上element-plus,一步到位。

npm create vite@Latest nrlwabo-front -- --template vue创建完基础工程之后,依次安装vue-router、pinia、axios、element-plus、sass。这里要提醒,Element Plus是按需引入还是全量引入,建议直接全量引入。按需引入虽然能减包体积,但政务后台项目对首屏性能没那么敏感,全量引入可以少配一套unplugin-vue-components,少踩很多插件版本冲突的坑。

4.2 axios封装:token注入与401处理

axios封装是前后端分离项目前端的"地基工程",nrlwabo的封装逻辑如下:

import axios from 'axios' const service = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL || '/api', timeout: 10000 }) service.interceptors.request.use(config => { const token = localStorage.getItem('nrlwabo_token') if (token) { config.headers['Authorization'] = 'Bearer ' + token } return config }) service.interceptors.response.use( response => { const res = response.data if (res.code === 200) { return res } if (res.code === 401) { localStorage.removeItem('nrlwabo_token') window.location.href = '/login' return Promise.reject(new Error('登录已过期')) } ElMessage.error(res.message || '系统异常') return Promise.reject(new Error(res.message)) }, error => { ElMessage.error('网络请求失败') return Promise.reject(error) } ) export default service

这里两个细节值得说:第一,baseURL不要写死,写在.env.development和.env.production两个环境变量文件里,开发环境指向http://localhost:8080/api,生产环境指向/api,通过Nginx反向代理把请求转发到后端,规避跨域;第二,401处理里不要只跳登录页,先清token再跳,否则会出现"跳了登录页但刷新后还带着旧token重复请求"的诡异现象。

4.3 路由守卫与权限菜单

路由表拆分成两套:公共路由(登录页、政务公开页)和业务路由(申报、进度查询、个人中心)。路由守卫统一在router.beforeEach里处理:

router.beforeEach((to, from, next) => { const token = localStorage.getItem('nrlwabo_token') if (!token && to.path !== '/login') { next('/login') return } const role = localStorage.getItem('nrlwabo_role') if (to.meta.roles && !to.meta.roles.includes(role)) { next('/403') return } next() })

页面级权限通过meta.roles控制,按钮级权限通过自定义指令v-permission控制。比如审批通过按钮,只有role === 'APPROVER'才渲染。权限菜单的动态生成则是登录后根据角色,从后端拿到菜单列表,前端递归渲染成侧边栏。这套方案比纯前端写死菜单好维护,后端新增一个菜单项,前端不用发版。

4.4 两个核心页面的实现套路

事项列表页就是典型的"搜索区+表格+分页"三段式,用Element Plus的el-table配合el-pagination。申报填写页稍微复杂一点:左侧是事项说明卡片,右侧是动态表单。由于不同事项的表单字段不一样,nrlwabo采用schema驱动渲染,后端返回该事项的表单字段定义JSON,前端用v-for遍历生成输入框、下拉框、上传组件。这个设计前期比写死表单多花了一天,但后面新增"预约办事""材料补正"这类页面时,全部复用同一套渲染器,收益很大。

进度查询页用el-steps组件展示审批节点,后端返回每个节点的状态码,前端映射成"已提交、已受理、审批中、已完成"。政务用户最关心的就是这个页面,所以数据加载失败时不要白屏,要显示"查询超时,请刷新重试"的兜底提示,这个小细节被业务方专门表扬过一次。

5. 前后端联调:跨域、文件上传与调试方法论

前后端分离项目,开发期和部署期的联调问题,核心就是三个:跨域、文件上传、环境不一致导致的"在我电脑上好好的"。这一节写点实在的联调经验。

5.1 跨域问题:开发期与生产期要分开看

开发期前端跑在5173端口,后端跑在8080端口,跨域是必然的。nrlwabo的处理是后端写了一个WebMvcConfigurer,统一配置跨域规则:

@Configuration public class CorsConfig implements WebMvcConfigurer { @Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping("/**") .allowedOriginPatterns("*") .allowedMethods("GET", "POST", "PUT", "DELETE", "OPTIONS") .allowedHeaders("*") .allowCredentials(true) .maxAge(3600); } }

注意allowedOriginPatterns("*")配合allowCredentials(true),低版本SpringBoot里用allowedOrigins("*")配credentials会直接报错,这是很多人一启动后端就白屏的原因。生产环境其实走不到这个配置,因为前端静态文件和后端接口都挂了同一个域名,Nginx根据路径前缀分流,不存在跨域。

5.2 文件上传:MultipartFile与静态资源映射

申报材料上传走的是POST /api/application/upload,参数类型用MultipartFile。后端把文件存到服务器指定目录,返回相对路径给前端。这里有个政务项目特有的要求:文件名要做安全处理,不能直接用用户原始文件名,因为政务系统涉及个人隐私,文件名里经常有身份证号、姓名这些敏感信息。nrlwabo的统一做法是用UUID重命名文件,原文件名存数据库附件表。

上传目录对外访问需要做静态资源映射:

@Configuration public class WebConfig implements WebMvcConfigurer { @Override public void addResourceHandlers(ResourceHandlerRegistry registry) { registry.addResourceHandler("/files/**") .addResourceLocations("file:" + System.getProperty("user.dir") + "/upload/"); } }

浏览器里访问http://ip:8080/files/xxx.pdf就能直接预览上传的附件。注意这个映射配置在localhost下没问题,部署到服务器后如果发现图片能上传但不能预览,多半是路径里的相对目录解析错了,建议直接用绝对路径配置。

5.3 联调中最常踩的三个坑

第一个坑是时间格式不一致。后端返回的LocalDateTime默认序列化成数组格式[2025,6,1,10,30,0],前端拿到这个数据一头雾水。解决方法是统一配置Jackson序列化规则,LocalDateTime一律格式化yyyy-MM-dd HH:mm:ss,这个配置在application.yaml里固定好,全项目生效。

第二个坑是Long型ID精度丢失。SpringBoot默认用Jackson序列化Long为数字,前端JavaScript的Number类型在超过2^53后会丢失精度。如果数据库主键是雪花ID这类超长数字,前端拿到的ID会莫名其妙地"失真",比后来的查询全错。解决办法是在实体ID字段上加@JsonSerialize(using = ToStringSerializer.class),把Long转成字符串给前端,或者干脆主键用数据库自增,政务项目数据量没到分布式ID的程度。

第三个坑是参数校验不统一。前端的校验是required必填、后端的校验是@NotBlank,两边规则如果不一致,就会出现"前端能提交,后端报参数错误"的尴尬情况。nrlwabo的应对是把校验规则统一写在后端,前端只做基础必填判断,复杂校验以接口返回的错误信息为准。

6. 部署上线:nrlwabo从源码到公网访问

部署环节是很多人卡住的地方,前后端分离项目最难的不是写代码,而是把前端打包产物、后端jar包、数据库、Nginx四样东西在服务器上协调起来。下面按部署步骤完整讲一遍,这套流程在单台云服务器上验证过很多次。

6.1 服务器环境准备与MySQL初始化

服务器建议选Linux发行版,2核4G起步。装好JDK17(SpringBoot 3.x必须JDK17)、MySQL8.0、Nginx。MySQL安装完成后第一件事是改root密码和设置远程访问权限,但政务项目上线后强烈建议把数据库ACL收紧,只允许本机访问,外网访问一律通过后端接口。

数据库初始化这块容易出问题的是字符集。建库时指定utf8mb4,否则中文乱码:

CREATE DATABASE nrlwabo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

然后导入nrlwabo.sql初始化脚本,检查几张核心表是否创建成功。这里有个小技巧:初始化脚本务必要有"基础数据导入口令",比如默认管理员账号、默认角色、预置的几个办事事项。没有基础数据,系统启动后前端页面全是空白,你都不知道是前端问题还是数据库问题。

6.2 后端构建:是一个jar不是war

SpringBoot项目不需要外置Tomcat,直接打包成可执行jar,这是部署方式上一个很关键的思路转变。打包命令:

mvn clean package -DskipTests

打出来的jar在target/nrlwabo-server.jar。启动前先检查application-prod.yaml配置,重点看数据库连接地址、端口号、JWT密钥、文件上传路径,这些都要从开发值改成生产值。数据库连接配置里,我习惯把时区显式写上去:

spring: datasource: url: jdbc:mysql://localhost:3306/nrlwabo?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai username: root password: your-password

很多刚接触的人会报SSL连接错误,原因就是MySQL8默认启用SSL,而JDBC连接串没关闭。上面设置了useSSL=false可以绕开,真正要上生产建议提前配好证书并打开SSL,但开发部署阶段先关掉不阻塞进度。

启动命令建议用nohup,避免SSH断开后进程被杀掉:

nohup java -jar nrlwabo-server.jar --spring.profiles.active=prod > app.log 2>&1 &

启动后tail -f app.log看日志。看到"Started NrlwaboApplication"说明后端起来了,浏览器直接访问http://服务器IP:8080/api/user/info验证接口连通。端口8080注意在安全组里放行,这是部署新人最常忽略的,本地接口通,外网就是连不上,多半就是云厂商安全组没开端口。

6.3 前端构建与Nginx静态托管

前端打包:

npm run build

产物在dist目录。把dist上传到服务器,比如/opt/nrlwabo-front/dist。然后配置Nginx,核心思路是:静态文件由Nginx托管,凡是以/api开头的请求转发给后端8080端口,前端代码里不写后端地址、只写相对路径。

server { listen 80; server_name your_domain_or_ip; root /opt/nrlwabo-front/dist; index index.html; location /api/ { proxy_pass http://127.0.0.1:8080/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } location /files/ { proxy_pass http://127.0.0.1:8080/files/; proxy_set_header Host $host; } location / { try_files $uri $uri/ /index.html; } }

配置里最后那个try_files是Vue单页应用能不能刷新的关键。没有这一行,你在页面内部跳转没问题,但按F5刷新或者直接访问/application/detail这个路由,Nginx会因为找不到对应文件而返回404。加上try_files $uri $uri/ /index.html,所有前端路由都回退到index.html,由Vue Router接管。

6.4 部署完成后的整体验证

Nginx配置完执行nginx -t检查语法,然后nginx -s reload。验证链路如下:浏览器访问域名或IP,看到登录页,输入管理员账号登录,登录成功后能跳转首页;再提交一个测试申报,上传附件,刷新页面看进度查询。这一条链路走通,说明前端静态资源、后端接口、数据库、文件上传四层全部打通。

部署环节最容易出的问题是用8080端口访问API时,location /api/的proxy_pass指向http://127.0.0.1:8080/,注意末尾的斜杠。如果你写成proxy_pass http://127.0.0.1:8080;(没有末尾斜杠),那么/api/login会原样转发到后端,变成/api/api/login,后端没有这个路径就会报404。加上末尾斜杠后,/api/login会被重写为/login转发给后端。这个细节很多人配置文件看了半天才发现。

6.5 部署后的MyBatis缓存与连接池设置

部署稳定之后,有两类配置要回头调一遍,不然上线半个月后必出问题。第一是MyBatis缓存,默认情况下一级缓存是SqlSession级别的,在一次请求内部重复查询相同SQL会命中缓存,这个没问题;但二级缓存是默认关闭的,如果你在mapper.xml里显式开启了<cache/>,要小心跨SqlSession的脏数据问题——政务系统数据变更频繁,建议保持二级缓存关闭,靠MySQL自身的查询缓存和Redis来扛高并发,别让ORM层的缓存参与业务逻辑。

第二是数据库连接池参数。SpringBoot默认的HikariCP配置在低流量下没问题,但政务系统早上9点到11点经常有瞬时高峰。nrlwabo生产配置里调整了几个关键参数:maximum-pool-size设为20,minimum-idle设为5,connection-timeout保持默认30秒。服务启动后可以通过/actuator/health检查数据库连接状态。曾经遇到过一个问题:应用启动时MySQL还没完全就绪,导致数据源初始化失败直接进程退出。解决办法是启动脚本里加一个nc -z localhost 3306的等待循环,确保MySQL先起来再启动jar包。

7. nrlwabo二次开发:加功能时先动哪里

系统交付只是第一步,政务类系统的特点是"上线之日就是需求开始变多之日"。这里整理一下nrlwabo在二次开发时的通用套路,给接手这套代码的人指个方向。

7.1 新增一个"网上预约"模块的完整改法

假设要加"网上预约办事"功能,底层逻辑和申报很像但又有区别:用户选择事项、选择时间段、提交预约。按这个顺序改:

第一步,数据库加biz_appointment表,字段参考biz_application,加一个appointment_time字段,其余状态字段照抄。第二步,后端新建AppointmentController和AppointmentService,Controller层处理URL映射和参数校验,Service层复制申报模块的代码改改业务名。第三步,前端在router表加一个预约页面,侧边栏菜单在角色菜单配置里加一项。第四步,权限上把APPROVER角色的菜单配置加上预约审核入口。这个流程走完,新模块上线。看上去很机械,但这正是业务系统架构稳定的价值所在——你不需要重新发明轮子,照着已有的成熟模式扩展就行。

7.2 代码里值得坚持的两个约定

第一个约定是Controller只做"接参数、调Service、返回Result"三件事,所有业务逻辑一律进Service。初始版本有的同事图方便在Controller里直接写业务代码,后来加需求时改得痛不欲生——Controller里那段逻辑没人敢动,因为不清楚它依赖什么上下文。第二个约定是所有状态修改操作必须写操作日志。政务系统随时要应对审计,用户改了自己的手机号、审批人驳回了一个申报,这些动作都记日志,表结构一分钟就能建好,关键是团队要养成习惯。

7.3 我踩过最值得说的三个坑

第一个是打包时前端环境变量没切过来。本地开发用VITE_API_BASE_URL=http://localhost:8080/api,部署时忘了改成/api,结果打包产物里的请求全指向localhost,用户手机上访问当然全是"网络错误"。

第二个是MySQL连接池的wait_timeout问题。MySQL默认8小时断开空闲连接,而连接池里如果还留着旧连接,客户端第一次请求时就会报"Communications link failure"。把这个参数在application.yaml里调小,或者配置HikariCP的keepalive-time,都能规避。

第三个是附件目录的磁盘空间。政务系统图片、PDF上传量比想象中大,nrlwabo上线半年后磁盘报警,一查全是附件。建议部署脚本里加一个每周清理临时文件的定时任务,并给upload目录单独挂一块磁盘,避免日志和文件把系统盘塞满。

这套系统从零到一完整走下来,最大的体会是:政务类项目技术难度其实有限,真正的挑战在于大量"不在代码里"的东西——表状态怎么设计、部署链路怎么打通、踩坑之后怎么快速定位。把这些沉淀成文档和脚本,比代码本身的复用价值更高。nrlwabo这套组合也许不算惊艳,但它足够完整、足够稳,这也是为什么很多政务项目最终都长成了这幅模样。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询