ARTICLE DETAIL

资讯详情

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

Spring Boot政务系统部署实战:bat脚本、MyBatis与Vue联调

Spring Boot政务系统部署实战:bat脚本、MyBatis与Vue联调 简介面向政府管理系统开发场景这份基于Spring Boot、MyBatis、Vue的技术栈完整源码包适合计算机、电子信息等相关专业学生作为毕业设计、课程设计或期末大作业参考也可用于快速了解B/S架构下政务系统的工程化实现。压缩包共422个文件大小36.36MB。其中109个Java文件承载后端业务与数据访问60个Vue文件构成前端管理界面13个XML用于MyBatis映射及工程配置辅以SVG图标、PNG/JPG图片、JS脚本、CSS样式等资源另附1-install.bat、2-run.bat、3-build.bat三件套一键脚本搭配必读推荐文档上手门槛较低。内容覆盖政府管理系统的常见功能模块提供前后端源码、数据库初始化脚本、构建启动说明以及配套演示媒体可帮助读者对照代码理解SpringBoot项目分层、Maven依赖管理与前端交互逻辑。目前已有121人学习下载适合需要完整实战代码并动手调试的学习者。1. 政务项目从交付到运行的最后一公里拿到一个基于 Spring Boot 的政府管理系统源码包第一反应别急着install先看清交付形态。这套项目源码里同时出现了1-install.bat、2-run.bat、3-build.bat三个批处理外加编译后的app.c8d260d9.css、chunk-vendors.1f0a25b2.css和index.html说明它并不是一个单纯的 Spring Boot 后端工程而是前后端分离、前端构建产物直接打进静态资源目录的混合形态。这种交付方式在政务内网项目里非常常见——业务系统部署在隔离网络运维人员不关心源码结构只关心「双击能不能跑起来」。整个系统的技术边界很清晰JDK 1.8、Maven 3.6、MySQL 5.7、Tomcat 8.0/9.0后端用 Spring Boot MyBatis前端是 Vue 编译后的静态文件通过 B/S 架构对外提供服务。这篇文章按「启动脚本 → 数据库与持久层 → 接口与权限 → 前端联调 → 部署排错」的顺序拆把每一层的关键配置、参数含义和容易踩的坑讲透。适合正在做政务、企业级管理系统交付的人也适合拿这套代码做毕业设计或课程设计、需要快速跑通并二次开发的学生。2. 从 bat 脚本看 Spring Boot 项目的启动链路2.1 三个批处理文件的真实分工三个 bat 文件命名很直白1-install.bat负责初始化环境2-run.bat负责启动服务3-build.bat负责重新打包。先看最核心的启动脚本它的内容通常是这样echo off set PORT8080 set JAVA_HOMEC:\Program Files\Java\jdk1.8.0_202 set PATH%JAVA_HOME%\bin;%PATH% title government-system java -jar -Dserver.port%PORT% target/government-system-0.0.1-SNAPSHOT.jar pause这段脚本做的事情很直接先固定 JDK 路径避免系统里装了多个 Java 版本导致版本漂移再把 Java 的 bin 目录加入 PATH最后用java -jar启动 Spring Boot 的可执行 Jar。-Dserver.port是 Spring Boot 外部化配置的一种方式它的优先级高于application.yml里的server.port配置意味着运维可以在不修改配置文件的情况下临时改端口。然后是1-install.bat。这类脚本在不同项目里差异很大这个项目里它的核心职责是建库、导数据和装依赖。常见写法如下echo off set DB_NAMEgovernment_db set DB_USERroot set DB_PASS123456 mysql -u%DB_USER% -p%DB_PASS% -e CREATE DATABASE IF NOT EXISTS %DB_NAME% DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; mysql -u%DB_USER% -p%DB_PASS% %DB_NAME% sql/init.sql call mvn clean install -DskipTests pause注意这里CREATE DATABASE指定了utf8mb4字符集和utf8mb4_general_ci排序规则。政务系统里经常要存居民姓名、地址这类含生僻字的文本utf8mb4是 MySQL 5.7 下最稳妥的选择。如果建表时用了默认的latin1后面插入中文大概率出现乱码或者Incorrect string value报错。初始化 SQL 脚本由mysql ... sql/init.sql执行要求 mysql 客户端命令在 PATH 中否则会报mysql 不是内部或外部命令。SQLyog 或 Navicat 也可以完成同样的工作但批处理的方式更适合交付给不熟悉数据库工具的客户。2.2 启动脚本里容易被忽视的问题3-build.bat一般是前端构建和后端打包的组合echo off cd frontend call npm install call npm run build cd .. call mvn clean package -DskipTests -Dmaven.test.skiptrue pause这个脚本反映了一个很多开发者容易忽视的问题Vue 项目构建后生成的dist目录需要被复制到 Spring Boot 的src/main/resources/static下Maven 打包时才会把它一并打进去。看交付包里的app.c8d260d9.css和index.html就是 Vue 执行npm run build之后的产物。实际交付时npm install在网络受限的内网环境经常失败。我一般在构建机上先装好依赖把node_modules以压缩包的形式带到现场或者直接用npm ci替代npm install——它按package-lock.json精确安装版本速度快且结果可复现。另外-DskipTests和-Dmaven.test.skiptrue有细微差别前者仍然编译测试代码但不执行后者连测试代码都不编译。政务项目二次开发后跑测试类容易因为数据库连接失败导致构建中断-Dmaven.test.skiptrue更省事但代价是测试代码的编译错误不会暴露在构建阶段。3. 数据持久层MyBatis 映射与数据库连接串的配置3.1 application.yml 里的关键参数Spring Boot 政府系统的持久层通常落在 MyBatis 上因为政务项目里 SQL 复杂、报表多MyBatis 手写 SQL 的方式比 JPA 更可控。打开src/main/resources/application.yml核心配置如下server: port: 8080 servlet: context-path: /gov spring: datasource: driver-class-name: com.mysql.jdbc.Driver url: jdbc:mysql://localhost:3306/government_db?useUnicodetruecharacterEncodingutf8useSSLfalseserverTimezoneAsia/Shanghai username: root password: 123456 jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT8 mybatis: mapper-locations: classpath:mapper/*.xml type-aliases-package: com.gov.system.entity configuration: map-underscore-to-camel-case: true log-impl: org.apache.ibatis.logging.stdout.StdOutImpl这里每一行参数都值得说清楚。driver-class-name用的是com.mysql.jdbc.Driver这是 MySQL 5.7 时代对应的驱动类名如果是 MySQL 8.x要换成com.mysql.cj.jdbc.Driver否则启动直接报ClassNotFoundException。.jdbc.url里useSSLfalse是因为政务内网通常没有配 SSL 证书MySQL 5.7 默认开启 SSL 校验不关掉会报大量 warning 甚至拒绝连接。serverTimezoneAsia/Shanghai解决时区问题——很多应用部署的服务器时区是 UTC不显式指定的话 JDBC 读出来的时间会比本地时间少 8 小时。map-underscore-to-camel-case: true这个配置解决数据库下划线字段和 Java 驼峰属性的映射问题。政务系统的表字段常见org_name、create_user_id这种风格实体类里对应orgName、createUserId。开启这个开关后MyBatis 自动做转换不需要在每一条查询后用as给字段起别名。如果关掉它查询结果是null而且不报错——这是持久层最隐蔽的坑之一。3.2 Mapper XML 与动态 SQL 的写法MyBatis 核心在mapper目录下的 XML 文件。看一个典型的方法管理模块的查询select idselectUserPage resultTypecom.gov.system.entity.SysUser SELECT u.id, u.username, u.real_name, u.org_id, o.org_name, u.status, u.create_time FROM sys_user u LEFT JOIN sys_org o ON u.org_id o.id where if testusername ! null and username ! AND u.username LIKE CONCAT(%, #{username}, %) /if if testorgId ! null AND u.org_id #{orgId} /if if teststatus ! null AND u.status #{status} /if /where ORDER BY u.create_time DESC LIMIT #{offset}, #{pageSize} /select这段 SQL 里where标签是 MyBatis 动态 SQL 的标志——它自动处理第一个条件前的AND。如果三个查询条件都不传生成的 SQL 不会带WHERE子句避免语法错误。LIKE CONCAT(%, #{username}, %)而不是写LIKE %${username}%是为了防 SQL 注入。${}是字符串拼接#{}是预处理参数占位政务系统用#{}是底线要求。LIMIT #{offset}, #{pageSize}是 MySQL 的分页语法offset需要计算成(pageNum - 1) * pageSize。在 Mapper 接口里对应的方法签名类似于ListSysUser selectUserPage(Param(username) String username, Param(orgId) Integer orgId, Param(status) Integer status, Param(offset) Integer offset, Param(pageSize) Integer pageSize);Param注解用于给参数命名因为 Java 反射拿不到编译期参数名除非加-parameters编译选项如果不加注解XML 里只能写成#{param1}、#{param2}这种无法维护的形式。注意这个接口方法名必须和 XML 里的id完全一致MyBatis 通过namespace加id定位 SQL 语句。政务项目中经常有多条件组合查询的需求上面的写法是标准方案。3.3 服务层事务控制的边界持久层只负责数据存取真正的逻辑编排在 Service 层。政务系统里典型的场景是新增一条审批记录的同时要更新申请人状态这两步必须在一个事务里。Spring Boot 用Transactional声明式事务控制但很多人把它加错了位置Service public class ApprovalServiceImpl implements ApprovalService { Autowired private ApprovalMapper approvalMapper; Autowired private UserMapper userMapper; Transactional(rollbackFor Exception.class) Override public void submitApproval(ApprovalDTO dto) { approvalMapper.insert(dto); userMapper.updateStatus(dto.getUserId(), 1); } }Transactional加在实现类上而不是接口上。rollbackFor Exception.class尤其关键——Spring 默认只回滚RuntimeException如果这里不指定方法里抛出IOException这类受检异常时前面的INSERT会提交成功数据就脏了。政务系统涉及审批、公文流转这种对一致性要求高的场景这个细节值得注意。另一个排查经验Transactional只对经过 Spring 代理的外部调用生效。如果同一个类里submitApproval调用了approvalMapper.insert因为insert不是独立的事务方法这样是没问题的。但如果有人在同类里用this.xxx()调Transactional方法代理不生效事务就没了。遇到事务不回滚第一步确认是不是同类自调用。4. Controller 层接口设计与权限控制的边界4.1 RESTful 接口的返回体设计政府管理系统的 Controller 层通常采用统一的 JSON 返回结构。看这段典型代码RestController RequestMapping(/api/user) public class UserController { Autowired private SysUserService userService; GetMapping(/list) public ResultPageResultSysUser list(RequestParam(defaultValue 1) Integer pageNum, RequestParam(defaultValue 10) Integer pageSize, RequestParam(required false) String username) { PageResultSysUser page userService.pageQuery(pageNum, pageSize, username); return Result.success(page); } PostMapping(/add) public ResultVoid add(RequestBody Valid SysUserVO userVO) { userService.addUser(userVO); return Result.success(); } }ResultT是这个项目里自定义的统一响应体一般包含三个字段code业务码、message提示信息、data业务数据。code200表示成功非 200 由全局异常处理器RestControllerAdvice统一输出业务错误。这种设计的好处是前端轮询、判断异常时逻辑一致不用每个接口各写一套成功判定。常写的前端拦截器逻辑就是if (res.code ! 200) { this.$message.error(res.message); }。注意分页参数用了RequestParam(defaultValue 1)和RequestParam(defaultValue 10)——用户不传pageNum时默认查第一页传了空字符串也会触发默认值。而RequestParam(required false) String username允许关键字缺省前端可以只分页不过滤。参数校验通过Valid完成实体类上配合NotBlank(message 用户名不能为空)这类注解请求体不合法时 Spring 自动抛出MethodArgumentNotValidException由全局异常处理器统一转成 JSON 返回。这样 Controller 层代码干净校验逻辑集中在实体上。4.2 基于拦截器与注解的权限模型政务系统对权限的要求是「菜单可见、按钮可点、数据可查」三层。这套源码如果基于 Spring Boot 做权限常见的方式是拦截器加注解Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) public interface RequiresPermission { String value(); }Component public class PermissionInterceptor implements HandlerInterceptor { Autowired private RedisTemplateString, String redisTemplate; Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { if (!(handler instanceof HandlerMethod)) { return true; } HandlerMethod handlerMethod (HandlerMethod) handler; RequiresPermission permission handlerMethod.getMethodAnnotation(RequiresPermission.class); if (permission null) { return true; } String token request.getHeader(Authorization); String userJson redisTemplate.opsForValue().get(login:token: token); if (userJson null) { response.setStatus(401); return false; } // 校验用户是否拥有 permission.value() 对应权限 if (!hasPermission(userJson, permission.value())) { response.setStatus(403); return false; } return true; } }Interceptor 在使用上有几个关键配置点。第一HandlerMethod的判断不能漏——静态资源请求可能走到拦截器里它们不是HandlerMethod类型直接放行。第二token 存 Redis 而不是本地 Session政务系统中用户可能从内网多个应用入口登录用RedisTemplate统一管理登录态可以做到单点登录。第三方法注解区分「所有人可见」和「必须登录」两种状态——不加注解的接口直接在拦截器返回true加了的则走权限校验避免每个 Controller 被迫继承一个 BaseController。第三层「数据可查」靠 MyBatis 的拦截器做数据权限比较困难多数项目在实际落地时用 JOIN 或子查询实现。比如用户只能看本部门的数据SQL 层联sys_user表判断org_id。这里的核心思想是权限不仅控制接口层的「能不能访问」还要控制数据层的「能看哪些行」。实现上是在 Service 层获取当前登录用户的部门 ID作为查询条件传入 Mapper。如果用拦截器做要考虑 MyBatis 的插件机制侵入性较大不推荐在政府项目中优先考虑。5. 前端 Vue 构建产物与 Spring Boot 的联调细节5.1 前端项目的构建与代理配置Vue 前端在开发环境使用 Webpack Dev Server 提供热更新并通过proxy代理访问后端接口。在vue.config.js里看到的关键配置如下module.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true, pathRewrite: { ^/api: /api } } } } }这个代理的含义是前端开发服务器运行在8081端口页面上发起的所有以/api开头的 axios 请求都会被 Dev Server 转发到http://localhost:8080即后端地址。changeOrigin: true会把请求头中的Host字段改成目标地址的域名避免后端某些基于域名做校验的过滤器拦截。pathRewrite这里没做地址改写因为前后端接口前缀统一是/api如果前端请求路径是/api/user/list而后端接口映射是/user/list就写成^/api: 。axios 的基础配置里请求拦截器会做两件事带上 token、统一处理超时axios.interceptors.request.use(config { const token localStorage.getItem(admin_token) if (token) { config.headers[Authorization] token } return config }, error { return Promise.reject(error) })token 存 localStorage 而不是 cookie因为同一域名下前后端不共享 Session——前端页面由 Dev Server 提供后端接口是另一个端口用 cookie 跨域会有各种限制。而在生产环境里前端构建产物直接放进后端static目录由 Tomcat 同一端口对外服务跨域问题不存在了但 localStorage 仍然照常可用。Authorization头的命名要和后端拦截器里取的一致这里拦的是request.getHeader(Authorization)前文 Spring Security 或拦截器校验的就是它。5.2 页面渲染与计算属性的使用场景政府管理系统的前端核心是数据表格和表单的交互。看一段 Vue 2 的列表页面代码template div el-table :datafilteredList border stripe el-table-column proprealName label姓名 width120/el-table-column el-table-column proporgName label所属部门/el-table-column el-table-column label状态 template slot-scopescope el-tag :typescope.row.status 1 ? success : danger {{ scope.row.status 1 ? 启用 : 停用 }} /el-tag /template /el-table-column /el-table /div /template script export default { data() { return { userList: [], keyword: } }, computed: { filteredList() { return this.userList.filter(item item.realName.includes(this.keyword)) } } } /scriptfilteredList用了 Vue 的计算属性——它依赖userList和keyword两个响应式数据任意一个变化都会重新求值。这里如果写在methods里模板每次渲染都会重新调用一次方法计算属性带缓存性能更好。Element UI 的el-table配合typesuccess和danger的el-tag做状态展示是政务系统后台的标准组合。slot-scopescope拿到当前行数据scope.row.status就是后端返回的字段值。在后端返回的数据结构里realName对应数据库的real_name字段。因为 MyBatis 开启了map-underscore-to-camel-caseJSON 序列化后自动是驼峰格式前端无需再做字段名映射。这是打通后端到前端的最后一环很多项目在这一步漏了配置导致前端拿到undefined。5.3 生产环境的资源路径坑最后看 Vue 构建产物的引用方式。前端项目vue.config.js里的publicPath决定打包后的静态资源引用路径module.exports { publicPath: process.env.NODE_ENV production ? ./ : / }publicPath设为./表示相对路径。如果写成默认的/打包出来的index.html里引用的是/js/app.js此时 Spring Boot 部署在http://localhost:8080/gov这个 context-path 下浏览器请求的就是http://localhost:8080/js/app.js而实际文件在http://localhost:8080/gov/js/app.js页面会白屏。改成./后浏览器会基于当前页面路径拼接资源地址就能正常加载。这是前后端混合部署最常见的坑Jar 包里文件都在页面就是空白浏览器的 Network 面板会看到一堆404。政务系统交付时如果发现登录页打不开优先检查这个配置。6. Tomcat 部署与启动失败的验证清单6.1 外部 Tomcat 部署 Spring Boot JarSpring Boot 默认打成可执行 Jar用java -jar直接跑。但政务内网有些运维规范要求必须部署到现有的 Tomcat 8.0/9.0 环境里这时候工程需要改成 war 包。具体改法是在pom.xml里把打包方式改成warpackagingwar/packaging同时启动类继承SpringBootServletInitializerSpringBootApplication public class GovernmentSystemApplication extends SpringBootServletInitializer { Override protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) { return builder.sources(GovernmentSystemApplication.class); } public static void main(String[] args) { SpringApplication.run(GovernmentSystemApplication.class, args); } }整个项目用mvn clean package打出的 war 包放到tomcat/webapps/目录下Tomcat 启动时自动解压部署。这时访问路径变成http://localhost:8080/包名/与 Spring Boot 内嵌容器访问路径不同。SpringBootServletInitializer的作用是让外部 Tomcat 启动时能找到 Spring Boot 的入口配置类不写这个类 war 包目录下的META-INF/MANIFEST.MF里没有Main-Class信息Tomcat 会报Unable to instantiate Servlet之类的错误。本项目里${server.port}配置在 war 包部署到外部 Tomcat 时不再生效端口由 Tomcat 的server.xml控制。数据库连接配置仍然从application.yml读取位置不再受 Jar 包路径限制可以直接放在tomcat/conf或 war 包WEB-INF/classes下修改。6.2 启动失败的常见异常排错顺序无论 Jar 包还是 war 包方式启动失败的诊断逻辑是相近的按下面的流程排查效率最高现象可能原因检查命令/方法启动后端口被占用8080 已被其他进程占用netstat -aon | findstr 8080找到 PID 后taskkill /F /PID pid控制台报Caused by: java.sql.SQLException: Access denied for user rootlocalhost数据库账号密码错误用 SQLyog/Navicat 手动连接排除网络和账号问题报Unknown database government_db未执行init.sql建库脚本检查1-install.bat是否成功执行或手动执行sql/init.sql页面空白但接口可访问Vue 资源路径publicPath配置错误按 F12 看 Network 面板中 JS/CSS 是否返回 404前端publicPath改为./重新构建中文乱码数据库字符集不是 utf8mb4执行SHOW CREATE DATABASE government_db查看字符集遇到启动失败我一般不看完整日志直接搜最后一个Caused by和第一个ERROR。Spring Boot 的日志对新手不友好前面一大段是 Spring 的启动横幅和 Bean 初始化信息真正的错误在中间或后面。在application.yml里临时加一段把 ERROR 日志独立输出到文件更快定位logging: file: name: logs/error.log level: root: INFO com.gov.system: DEBUGcom.gov.system设为 DEBUG这个包下的 MyBatis Mapper 会打印完整的 SQL 执行语句配合log-impl: StdOutImpl可以看到查询条件是否按预期传入。排错完成后把日志级别改回 INFO——政府线上环境打印 DEBUG 会严重拖慢日志 I/O而且大量 SQL 输出可能包含敏感查询条件。6.3 启动前必须确认的三件事启动这套系统前不要直接双击2-run.bat先确认三件事可以节省大量排查时间数据库是否已初始化、JDK 是否真的是 1.8、端口是否被占用。数据库层面进 MySQL 执行SHOW TABLES看到sys_user、sys_org、sys_role这些核心表存在说明初始化完成。JDK 层面cmd 里执行java -version输出1.8.0_x而不是openjdk version 17这类高版本。政务系统如果遇到 JDK 版本过高导致启动报错通常是反射相关异常比如IllegalArgumentException: Unsupported class file major version—— 这是 Spring Boot 2.x 和 JDK 高版本不兼容的典型信号。这三个条件确认后再执行2-run.bat看到Started GovernmentSystemApplication in xx.xxx seconds这行日志说明服务启动成功浏览器访问http://localhost:8080/gov就能进入系统了。本文还有配套的精品资源点击获取
返回列表