ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

SpringBoot+Vue前后端分离政务系统实战解析

SpringBoot+Vue前后端分离政务系统实战解析 1. 为什么是前后端分离在线政务中心的架构与模块边界做政务类的在线服务中心最容易被卡住的往往不是业务流程本身而是前后端分离这套组合怎么从零到一地落到一个可以被交付的代码库。之前带团队做过一个基于SpringBootVueMyBatisMySQL的前后端分离在线政务服务中心项目仓库代号叫nrlwabo——说是政务服务中心本质上是把线下的申报、审批、进度查询这些动作搬到网页端对外提供统一入口对内提供管理后台。项目本身并不新鲜但它的价值在于完整有登录鉴权、有事项申报、有后台管理、有文件上传还附带一套可以直接照着做的部署教程。这篇文章我想把它拆开聊清楚适合那些手里已经有点SpringBoot和Vue基础、但没完整做过一个前后端分离交付项目的人。1.1 从政务大厅到网页端的业务映射政务服务中心这个词听起来很大真正落到系统里其实就是几个固定动作用户登录、查看办事指南、提交申报材料、查询审批进度、后台人员审核、统计归档。线下大厅里取号→等窗口→交材料→拿回执这条链路搬到网页上就变成了注册登录→选事项→填表单→传附件→提交申报→查状态。nrlwabo在拆需求的时候业务方特别强调要把事项和申报分开。事项是死的比如企业设立登记社保卡补办它包含办事条件、材料清单、办理时限申报是活的是用户针对某个事项发起的一次具体申请。这个区分在数据库设计上帮了大忙后续加业务模块时基本不用动核心表结构。1.2 后端按业务域拆前端按页面拆前后端分离项目最容易犯的错是把后端里的Controller也照着前端页面去拆。比如前端有个个人中心页面后端就建一个PersonalCenterController这种做法前期写起来快后期一加需求就非常痛苦。nrlwabo的做法是后端严格按业务域拆用户认证一套、事项管理一套、申报流程一套、附件存储一套前端才按页面拆登录页、首页、事项列表页、申报填写页、进度查询页、后台管理页。两边通过接口文档对齐而不是通过页面名对齐。这样后端接口可以被PC端、管理后台甚至以后的手机端复用前端页面掉了一个也不太影响整体接口层。1.3 技术栈取舍为什么是这个组合技术选型上SpringBootVueMyBatisMySQL是当前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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT系统用户表; CREATE TABLE sys_role ( id BIGINT PRIMARY KEY AUTO_INCREMENT, role_code VARCHAR(50) NOT NULL COMMENT 角色编码, role_name VARCHAR(50) NOT NULL ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT角色表; CREATE TABLE sys_user_role ( user_id BIGINT NOT NULL, role_id BIGINT NOT NULL, PRIMARY KEY (user_id, role_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;用户表只存登录凭证和基础信息角色用编码区分比如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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 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) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4 COMMENT附件表;文件本身存在本地磁盘目录下数据库只存路径。这套方案在单机部署阶段最省事后面如果迁移到云服务器把file_path换成OSS的key值改造量也不大。2.4 逻辑删除与公共字段政务系统对数据留存要求高删除操作基本不做物理DELETEnrlwabo的统一做法是加deleted字段。MyBatis里通过全局配置实现逻辑删除自动拼接比如配置逻辑删除值为1未删除为0所有查询SQL都会自动带上deleted0条件开发人员写SQL时可以忘掉这件事但执行结果不会错。这件事一开始没做后来补上时全表刷了一遍SQL教训是建表第一天就把deleted加上别回头补。3. 后端实现SpringBoot接口侧的核心链路业务表设计好了接下来就是后端把这套东西串起来。这一节不讲全部代码重点讲nrlwabo后端里几个过了一道坎的地方统一返回结构、登录鉴权、TypeHandler处理JSON字段、以及申报提交的事务控制。3.1 工程结构与统一返回体后端包结构是这样controller、service、mapper、entity、common、config。common里放统一返回类Result、异常处理Handler、工具类。统一返回结构是Json格式的code、message、datapublic class ResultT { 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 BaseTypeHandlerListString { Override public void setNonNullParameter(PreparedStatement ps, int i, ListString parameter, JdbcType jdbcType) throws SQLException { ps.setString(i, JSON.toJSONString(parameter)); } Override public ListString 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 idServiceItemMap typeServiceItem result columnrequired_materials propertyrequiredMaterials typeHandlercom.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 viteLatest 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起步。装好JDK17SpringBoot 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不是warSpringBoot项目不需要外置Tomcat直接打包成可执行jar这是部署方式上一个很关键的思路转变。打包命令mvn clean package -DskipTests打出来的jar在target/nrlwabo-server.jar。启动前先检查application-prod.yaml配置重点看数据库连接地址、端口号、JWT密钥、文件上传路径这些都要从开发值改成生产值。数据库连接配置里我习惯把时区显式写上去spring: datasource: url: jdbc:mysql://localhost:3306/nrlwabo?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: your-password很多刚接触的人会报SSL连接错误原因就是MySQL8默认启用SSL而JDBC连接串没关闭。上面设置了useSSLfalse可以绕开真正要上生产建议提前配好证书并打开SSL但开发部署阶段先关掉不阻塞进度。启动命令建议用nohup避免SSH断开后进程被杀掉nohup java -jar nrlwabo-server.jar --spring.profiles.activeprod app.log 21 启动后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设为20minimum-idle设为5connection-timeout保持默认30秒。服务启动后可以通过/actuator/health检查数据库连接状态。曾经遇到过一个问题应用启动时MySQL还没完全就绪导致数据源初始化失败直接进程退出。解决办法是启动脚本里加一个nc -z localhost 3306的等待循环确保MySQL先起来再启动jar包。7. nrlwabo二次开发加功能时先动哪里系统交付只是第一步政务类系统的特点是上线之日就是需求开始变多之日。这里整理一下nrlwabo在二次开发时的通用套路给接手这套代码的人指个方向。7.1 新增一个网上预约模块的完整改法假设要加网上预约办事功能底层逻辑和申报很像但又有区别用户选择事项、选择时间段、提交预约。按这个顺序改第一步数据库加biz_appointment表字段参考biz_application加一个appointment_time字段其余状态字段照抄。第二步后端新建AppointmentController和AppointmentServiceController层处理URL映射和参数校验Service层复制申报模块的代码改改业务名。第三步前端在router表加一个预约页面侧边栏菜单在角色菜单配置里加一项。第四步权限上把APPROVER角色的菜单配置加上预约审核入口。这个流程走完新模块上线。看上去很机械但这正是业务系统架构稳定的价值所在——你不需要重新发明轮子照着已有的成熟模式扩展就行。7.2 代码里值得坚持的两个约定第一个约定是Controller只做接参数、调Service、返回Result三件事所有业务逻辑一律进Service。初始版本有的同事图方便在Controller里直接写业务代码后来加需求时改得痛不欲生——Controller里那段逻辑没人敢动因为不清楚它依赖什么上下文。第二个约定是所有状态修改操作必须写操作日志。政务系统随时要应对审计用户改了自己的手机号、审批人驳回了一个申报这些动作都记日志表结构一分钟就能建好关键是团队要养成习惯。7.3 我踩过最值得说的三个坑第一个是打包时前端环境变量没切过来。本地开发用VITE_API_BASE_URLhttp://localhost:8080/api部署时忘了改成/api结果打包产物里的请求全指向localhost用户手机上访问当然全是网络错误。第二个是MySQL连接池的wait_timeout问题。MySQL默认8小时断开空闲连接而连接池里如果还留着旧连接客户端第一次请求时就会报Communications link failure。把这个参数在application.yaml里调小或者配置HikariCP的keepalive-time都能规避。第三个是附件目录的磁盘空间。政务系统图片、PDF上传量比想象中大nrlwabo上线半年后磁盘报警一查全是附件。建议部署脚本里加一个每周清理临时文件的定时任务并给upload目录单独挂一块磁盘避免日志和文件把系统盘塞满。这套系统从零到一完整走下来最大的体会是政务类项目技术难度其实有限真正的挑战在于大量不在代码里的东西——表状态怎么设计、部署链路怎么打通、踩坑之后怎么快速定位。把这些沉淀成文档和脚本比代码本身的复用价值更高。nrlwabo这套组合也许不算惊艳但它足够完整、足够稳这也是为什么很多政务项目最终都长成了这幅模样。
返回列表