☰
SpringBoot+Vue+MyBatis全栈实战:疫情防控管理系统源码解析与部署指南
2026/10/10 13:32:52 网站建设 项目流程

最近后台收到不少刚入行的同学留言,都在问能不能找一套“能直接跑起来、代码结构又比较规范”的管理系统源码作为练手项目。刚好我手上有一套基于 SpringBoot + Vue 的疫情防控管理系统,用到了 MyBatis 和 MySQL,是 2025 年重新整理过依赖版本和前端构建流程的版本。这套东西虽然名字带着“疫情”,但本质上就是一个标准的“健康信息上报 + 后台管理”全栈项目,把用户端、管理端、数据统计、权限控制都串了起来。无论你是想拿来应付毕业设计,还是想学 SpringBoot 和 Vue 的整合方式,甚至是想参考一下前后端分离项目的真实目录结构和接口设计,都有很直接的参考价值。

这篇文章我会从技术选型到数据库设计,把核心前后端接口的实现思路和踩过的坑全部摊开来讲,尽量做到让一个刚学完 SSM 或 Vue 基础的人也能跟着做出来。文章里的代码都是我整理过的核心片段,可以直接抄进自己的项目里改一改再用。

1. 项目整体设计与技术选型思路

1.1 为什么是 SpringBoot + Vue + MyBatis + MySQL 这套组合

先说结论:这套组合在 2025 年依然是中小型管理系统里性价比最高的选择,没有之一。

SpringBoot 解决的是后端“配置地狱”的问题。如果你以前写过 SSM 整合,一定记得那一堆 XML 配置、扫描包配置、事务配置。SpringBoot 用自动配置把这些全压下去了,一个@SpringBootApplication启动类就能跑起整个项目。而且它内置了 Tomcat,部署的时候不用再单独装容器,一条java -jar就能上线。

Vue 这边,我选的是 Vue 3 + Vite 的组合。2025 年 Vue 2 已经进入维护末期,新项目再写 Vue 2 属于给自己挖坑。Vite 的冷启动速度和热更新体验比 Webpack 好太多了,尤其在你前后端分离开发的时候,改一行前端代码页面马上刷新,这种效率提升是实打实的。当然,如果你手里拿到的老版本源码还是 Vue 2 + Webpack,也能跑,但建议尽早迁移,后面我会提几个迁移的关键点。

MyBatis 和 MySQL 这一层,主要是为了灵活和轻量。项目里有大量动态查询条件,比如按日期、按姓名、按状态、按地区组合查询健康上报记录,MyBatis 的动态 SQL 写起来比 JPA 里的 Specification 直接得多。而 MySQL 本身就是最常见的生产级数据库,配合 Navicat 或 DataGrip 调试非常方便。

1.2 系统需要解决的核心业务问题

这套系统的业务背景不复杂,但流程比较典型,很适合作为全栈练手。围绕“健康信息上报与管控”,主要解决三个问题:

  • 用户端:每天提交自己的体温、行程、健康码状态、异常症状等信息。
  • 管理端:各级管理员查看所辖范围的上报数据,处理异常人员,发布通知。
  • 数据端:对上报数据进行汇总统计,支持按日期、区域、状态多维度筛选导出。

这三个问题对应到系统里,就是三个核心模块:用户上报模块、后台管理模块、数据统计模块。再加上支撑这些模块的登录认证和权限管理,整个系统的骨架就出来了。我画过一张简单的功能脑图(这里用文字描述):用户端是首页填报 + 历史记录 + 个人信息,管理端是人员管理 + 上报审核 + 异常处理 + 通知公告,统计端是图表展示 + 报表导出。后端所有接口都按/api前缀统一暴露,权限用 Spring Security + JWT 控制。

1.3 项目目录结构的规划

一个好的目录结构,能让你在写完第一版之后少掉一半的头发。我的后端包结构大概是这样的:

com.example.epidemic ├── config // 配置类:Security、CORS、MyBatis分页等 ├── controller // 接口层:接收参数,返回Result ├── service // 业务层:写业务判断和事务 ├── mapper // Dao层:MyBatis接口 ├── entity // 实体类:和数据库表对应 ├── dto // 数据传输对象:封装请求参数和响应结果 ├── vo // 视图对象:给前端展示用的封装 ├── utils // 工具类:JWT工具、Excel导出工具 └── common // 统一返回结果、异常处理、常量

前端 Vue 项目目录用标准 Vite 结构:

src ├── api // 所有请求接口封装 ├── assets // 静态资源 ├── components // 通用组件 ├── router // 路由配置 ├── store // Pinia 状态管理 ├── views // 页面组件 ├── utils // token 存取、axios 封装 └── App.vue

这个结构的好处是,前后端各层职责清晰,controller 里只负责参数校验和调用 service,不写任何业务逻辑。mapper 里只放 SQL 相关操作。这样就算以后想换掉 MyBatis 改 MyBatis-Plus 或者 JPA,只需改 mapper 层,上层完全不用动。

2. 数据库设计与核心数据表解析

2.1 数据表结构需要覆盖哪些维度

数据库设计是这个系统里最不能糊弄的部分。我第一版设计吃了不少亏,比如用户表里直接存角色名称,导致后来加权限功能的时候还得拆表。所以这次重新整理,我严格表了职责,一共设计了 6 张核心表:

  • sys_user:系统用户表,包含登录账号、密码、姓名、手机号、所属部门、角色ID。
  • sys_role:角色表,存角色编码和角色名称。
  • user_health_report:健康上报记录表,存体温、症状、行程卡颜色、健康码颜色、所在地点、上报日期。
  • user_contact_log:行程轨迹记录表(如果要扩功能可以用,基础版可以合并到上报表里)。
  • notice_info:通知公告表,存标题、内容、发布人、发布时间。
  • exception_log:异常处理记录表,记录异常人员的处理状态和跟进人。

有些表之间通过外键逻辑关联,但我不建议在数据库物理层建太多外键约束。原因很简单:高并发场景下外键会额外消耗性能,而且 MyBatis 本身并不依赖物理外键。只要在业务层保证数据一致性就够。如果你只是做毕设,建物理外键也无所谓,但作为有经验的开发,我更倾向于用逻辑外键。

2.2 健康上报表的设计要点

健康上报表是整张系统的核心,直接上建表语句:

CREATE TABLE `user_health_report` ( `id` bigint NOT NULL AUTO_INCREMENT, `user_id` bigint NOT NULL COMMENT '上报人ID', `report_date` date NOT NULL COMMENT '上报日期', `temperature` decimal(4,1) DEFAULT NULL COMMENT '体温', `symptom_type` varchar(255) DEFAULT NULL COMMENT '症状描述', `health_code` varchar(20) DEFAULT NULL COMMENT '健康码状态:green/yellow/red', `travel_code` varchar(20) DEFAULT NULL COMMENT '行程卡状态:green/yellow/red', `location` varchar(255) DEFAULT NULL COMMENT '当前位置', `is_risk` tinyint(1) DEFAULT '0' COMMENT '是否异常:0正常 1异常', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, PRIMARY KEY (`id`), KEY `idx_user_date` (`user_id`,`report_date`), KEY `idx_date` (`report_date`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='健康上报记录表';

这里几个关键点我单独说明一下:

  • report_date用了date类型而不是datetime,因为业务上一个人每天只能上报一次,用日期做唯一性判断更精准。在业务层插入前会先查询当天是否已存在记录,避免重复上报。
  • health_code和travel_code用varchar存枚举字符串,而不是用数字,因为前端展示直接映射颜色和文字,省了一层转换。虽然稍微浪费一点存储,但胜在直观。
  • 联合索引idx_user_date非常重要。因为最频繁的查询就是“查某人某天的上报记录”和“查某天所有上报记录”,这个联合索引能直接命中,避免全表扫描。
  • 表名我用了user_health_report而不是health_report,因为业务上每个用户都有多条记录,前缀user_能更清楚地表达这层归属关系。

2.3 用户表与角色表的权限设计

用户表和角色表我做成了一对一关系(一个用户只有一个角色),因为这套系统的角色固定就三类:普通用户、部门管理员、系统管理员。如果你以后扩展出多个角色,改成一对多也就是拆一张中间表的事。

CREATE TABLE `sys_user` ( `id` bigint NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL COMMENT '登录账号', `password` varchar(100) NOT NULL COMMENT 'BCrypt加密后的密码', `real_name` varchar(50) DEFAULT NULL, `phone` varchar(20) DEFAULT NULL, `department` varchar(100) DEFAULT NULL COMMENT '所属部门或区域', `role_id` bigint DEFAULT NULL, `status` tinyint(1) DEFAULT '1' COMMENT '账号状态:1启用 0禁用', `create_time` datetime DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`), UNIQUE KEY `uk_username` (`username`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='系统用户表';

注意密码字段我预留了 100 长度,因为 BCrypt 加密后的串是 60 位,虽然 60 够用,但留点余地更保险。登录时用 Spring Security 的BCryptPasswordEncoder校验密码,明文存储这种事情千万别干,一旦数据库泄露,所有账号直接裸露。

角色表更简单:

CREATE TABLE `sys_role` ( `id` bigint NOT NULL AUTO_INCREMENT, `role_code` varchar(50) NOT NULL COMMENT '角色编码:ROLE_USER/ROLE_ADMIN', `role_name` varchar(50) NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `uk_role_code` (`role_code`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

在controller层我会用@PreAuthorize("hasRole('ADMIN')")这类注解直接做接口级权限控制,比在 Service 里手动 if 判断优雅得多。Spring Security 的过滤器链会拦截所有/api/**请求,先解析 JWT,再把用户 ID 和角色信息塞进SecurityContext,业务代码里随时可以取。

3. 核心功能模块的实现与踩坑记录

3.1 登录认证:JWT + SpringSecurity 的整合细节

先提一个最容易踩的坑:Security 配置类必须放行 Swagger、放行登录接口、放行静态资源,否则会白屏 401 半小时。

我的安全配置核心思路是:/api/auth/login完全匿名访问,其他/api/**都必须带上Authorization: Bearer <token>。自定义 JWT 过滤器继承OncePerRequestFilter,在过滤器里解析 token,如果合法就把UsernamePasswordAuthenticationToken塞进上下文。

JWT 工具类主要做三件事:生成 token、解析 token、校验过期时间。这里我踩过一个特别经典的坑:JWT 的过期时间不能设置太长,否则用户被盗过一次 token 就能持续作恶。我最终把过期时间设成 24 小时,前端在 axios 拦截器里检测到 401 就跳回登录页重新登录。

前端 axios 封装的核心代码长这样:

// request.js import axios from 'axios' import { getToken, removeToken } from '@/utils/auth' const request = axios.create({ baseURL: '/api', // 通过 Vite 代理转发,避免跨域 timeout: 10000 }) request.interceptors.request.use(config => { const token = getToken() if (token) { config.headers.Authorization = `Bearer ${token}` } return config }) request.interceptors.response.use( response => response.data, error => { if (error.response.status === 401) { removeToken() window.location.href = '/login' } return Promise.reject(error) } )

为什么用/api作为 baseURL 而不是写完整的后端地址?因为我在vite.config.js里配了代理:

server: { proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true, rewrite: path => path.replace(/^\/api/, '') } } }

这样前端代码里所有请求都是相对路径,本地开发和生产部署都不需要改前端代码。生产部署时,直接把前端 build 出来的文件扔进 SpringBoot 的src/main/resources/static/目录,用同一个端口访问,跨域问题直接不存在。

3.2 用户健康上报的完整业务链路

健康上报是用户端最核心的操作。业务上需要做到:

  1. 用户登录后进入填报页,页面显示当前日期和上次上报信息。
  2. 用户提交体温、身体状况、健康码颜色、行程卡颜色、当前位置。
  3. 后端先校验当天是否已经上报,如果上报过则返回“今日已上报,请勿重复提交”。
  4. 校验通过后插入记录,并自动判断是否需要标记异常。

判断异常的逻辑代码如下:

public void checkAndSetRisk(UserHealthReport report) { if (report.getTemperature() != null && report.getTemperature().compareTo(new BigDecimal("37.3")) >= 0) { report.setIsRisk(1); } if ("red".equals(report.getHealthCode()) || "red".equals(report.getTravelCode())) { report.setIsRisk(1); } if (StringUtils.hasText(report.getSymptomType())) { report.setIsRisk(1); } }

这里我把“是否风险”的判断放到了 service 层,而不是数据库层。好处是以后规则变了,比如体温阈值从 37.3 改成 37.2,只需要改这一处,不用动 SQL。

前端填报页我用了el-form做表单校验,提交按钮加 loading 和防重复提交。用户狂点提交按钮时,后端即使做了唯一性校验,也可能因为并发同时插入两条。解决方法是:先在数据库表上加UNIQUE KEY uk_user_date (user_id, report_date),然后业务层捕获DuplicateKeyException,并转换为友好提示。这样即使并发也能保证一条数据。

3.3 后台管理端的列表查询:MyBatis 动态 SQL

管理端最常用的就是列表查询,上面我再加一个按日期范围筛选。MyBatis 的动态 SQL 写多了以后,我觉得最需要注意的是:每次查询必须带上 limit,不能让用户一次查几十万条数据。分页插件用 PageHelper,配置简单,用法也直接,但它和多数据源时不兼容,一个项目里如果没有多数据源需求,放心用。

一个典型的管理端查询 Mapper XML 片段:

<select id="selectReportPage" resultType="com.example.epidemic.vo.ReportVO"> SELECT r.id, u.real_name, u.department, r.report_date, r.temperature, r.health_code, r.travel_code, r.is_risk FROM user_health_report r LEFT JOIN sys_user u ON r.user_id = u.id <where> <if test="keyword != null and keyword != ''"> AND (u.real_name LIKE CONCAT('%', #{keyword}, '%') OR u.username LIKE CONCAT('%', #{keyword}, '%')) </if> <if test="startDate != null"> AND r.report_date &gt;= #{startDate} </if> <if test="endDate != null"> AND r.report_date &lt;= #{endDate} </if> <if test="isRisk != null"> AND r.is_risk = #{isRisk} </if> </where> ORDER BY r.report_date DESC, r.id DESC </select>

这里有两个小细节:

  • CONCAT('%', #{keyword}, '%')而不是直接在 SQL 里写'%${keyword}%'。前者是预编译,不会产生注入风险;后者是文本拼接,一定要避免。MyBatis 的${}是字符串替换,#{}是占位符,凡是用户输入的参数一律用#{}。
  • 日期比较用&gt;=和&lt;=,因为 XML 里>和<是保留字符,不转义会解析报错。有些人会写<![CDATA[ >= ]]>,能用,但看起来丑,我习惯用实体转义。

3.4 数据统计与 Excel 导出

统计模块我用的是 ECharts 图表。管理员进入首页的统计面板,能看到今日上报人数、异常人数、各区域上报率趋势图。这些数据的获取逻辑可以统一走一个统计 SQL,按日期分组:

SELECT report_date, COUNT(*) AS total_count, SUM(CASE WHEN is_risk = 1 THEN 1 ELSE 0 END) AS risk_count FROM user_health_report WHERE report_date BETWEEN #{startDate} AND #{endDate} GROUP BY report_date ORDER BY report_date

这张统计表每天都会增长,如果查询范围太长,必须加索引。我建了(report_date, is_risk)联合索引,这条 SQL 走索引效率很高。

关于 Excel 导出,我踩过一个特别无语的坑:用 EasyExcel 导出的时候,如果实体字段中有日期类型,默认导出的格式是一串数字,必须加@DateTimeFormat注解才能显示成年月日。后来我直接在 VO 里把日期转成字符串,反而更稳。

EasyExcel 的核心导出代码:

String fileName = URLEncoder.encode("健康上报统计_" + LocalDate.now(), "UTF-8"); response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); response.setCharacterEncoding("utf-8"); String header = "attachment; filename*=UTF-8''" + fileName + ".xlsx"; response.setHeader("Content-Disposition", header); EasyExcel.write(response.getOutputStream(), ReportExportVO.class).sheet("上报数据").doWrite(dataList);

前端请求导出时要注意,必须设置responseType: 'blob',否则下载下来的 Excel 打开会显示乱码知识。这个不加基本必踩。

4. 常见问题与排查技巧实录

4.1 前端请求后端跨域,接口通了吗?

前端和后端分离开发时,最频繁的问题就是跨域。我这里推荐最稳妥的方案:后端配一个 CORS 配置类,前端同时配 Vite 代理,两边都做了,生产环境下就不会因为跨域出幺蛾子。

SpringBoot 的 CorsConfig 核心代码:

@Configuration public class CorsConfig { @Bean public CorsFilter corsFilter() { CorsConfiguration config = new CorsConfiguration(); config.addAllowedOriginPattern("*"); config.addAllowedHeader("*"); config.addAllowedMethod("*"); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return new CorsFilter(source); } }

注意:allowedOrigins("*")和allowCredentials(true)不能同时使用,否则启动直接报错。因为浏览器规定这里不能带通配符,必须用allowedOriginPatterns("*")。这是我之前从 Spring Boot 2.2 升级到 2.7 后踩过的雷,升级到 2025 最新版同样适用。

另外,如果你后端用了 Spring Security,跨域配置必须在 Security 过滤链之前生效。CorsFilter 需要注册在http.cors()之前,否则 Security 的过滤器先返回 401,CORS 配置根本没机会生效,前端照样报跨域。

4.2 MyBatis 控制台不打印 SQL,调试很难受怎么办

很多新手拿到源码后,把项目跑起来,结果后台日志看不到 SQL,查不到 mapper 执行了什么语句。其实只需要在application.yml里加一行配置:

mybatis: configuration: log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

加完这句话,控制台就会打印完整的 SQL 和参数。但有一点要注意:这个配置只建议在开发环境开启。生产环境会打印所有 SQL,一方面有信息泄漏风险,另一方面日志文件膨胀得非常快。所以我会把配置拆成application-dev.yml和application-prod.yml,只在 dev 里开。

另外,如果你用的是 MyBatis-Plus,可以分mybatis-plus.configuration.log-impl配置,两者不一样,别搞混了。

4.3 SpringBoot 版本太高导致 javamail 或其它依赖冲突

2025 年最新的 SpringBoot 可能已经到 3.5 了,但如果你从网上下载的这套项目源码还是基于 SpringBoot 2.x,拿最新 JDK 去跑会报一堆ClassNotFoundException或者UnsupportedClassVersionError。这里我建议:不要盲目追求最新版,先看你的 JDK 版本。SpringBoot 3.x 强制要求 JDK 17+,如果本地装了 JDK8,老老实实用 SpringBoot 2.7.x 版本。

我整理过一个版本兼容表,照着配基本没有坑:

组件推荐版本备注
JDK1.8 或 11(SpringBoot 2.x);17(SpringBoot 3.x)官方要求
SpringBoot2.7.18 或 3.2.x长期维护版更稳
MyBatis Starter2.3.x 或 3.0.x注意 SpringBoot 3 需要 jakarta 命名空间
MySQL Connector8.0.33+兼容 MySQL 5.7 和 8.0
Vue3.4+不用 Vue 2
Vite5.x构建工具

这里有个特别生动的类比:项目的依赖关系就像装修房子,水电、地板、家具必须匹配。你拿 4 分管的水管去接 6 分接口,怎么都拧不上。JDK 版本就是水管的直径标准,SpringBoot 版本就是接口尺寸,两者不匹配,后面不管写多少代码都跑不起来。

4.4 MySQL 建库编码导致中文乱码

我收到过好几个同学的求助:页面显示正常,但往数据库里插入中文全变成???。百万种情况里,九成是建库时字符集没指定。必须在建库时显式声明:

CREATE DATABASE epidemic_db DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

在你已经建好库又不想删除重建的情况下,可以执行下面这句补救:

ALTER DATABASE epidemic_db CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;

同时,数据库连接的 URL 里也要加上参数:

jdbc:mysql://localhost:3306/epidemic_db?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai

缺了characterEncoding=utf8的话,即使数据库是 utf8mb4,插入中文也有概率出乱码。

4.5 前端打包后放入 SpringBoot,刷新页面 404 的解决办法

开发完成部署时,如果你把 Vue 打包后的文件放到static目录下,直接访问首页没问题,但一刷新子路由(比如/admin/user/list),就会因为 SpringBoot 默认找不到这个路径而返回 404。

解决办法是加一个路由转发配置,让所有非 API 请求都转发到index.html:

@Controller public class WebController { @RequestMapping(value = {"/", "/{path:^(?!api$).*$}/**"}) public String forward() { return "forward:/index.html"; } }

注意其中的正则排除掉了/api开头的路径,避免把接口请求也转发到前端页面。这是前后端分离部署最经典的一个坑,网上很多文章没讲清楚,我这里单独写出来。

5. 从源码到部署:一套完整的操作记录

5.1 环境准备与初始化

先说跑起来需要哪些东西。我这里按最干净的 Windows 开发环境来举例,Mac 或 Linux 差别不大:

  • JDK 1.8 或 17,我建议直接装 17,因为 SpringBoot 3 是以后主流,虽然这套源码可以兼容 2.x,但 17 能覆盖更多情况。
  • Maven 3.8+,用来管理后端依赖和打包。
  • Node.js 16 以上,用来跑 Vite 前端工程。
  • MySQL 8.0,记得用 root 创建数据库。
  • IDE:后端用 IntelliJ IDEA,前端用 VSCode 或者继续用 IDEA 都行。

这些工具安装很简单,但版本坑多。Node 版本太低 Vite 5 会报错,Maven 版本太低拉不下来某些依赖。建议都装最新的稳定版。

把源码拿到手之后,第一步不要急着跑。先看一下项目的 README,或者直接在application.yml里把数据库账号密码改成你自己的。这个步骤漏了,99% 的报错都集中在连不上数据库上。

5.2 本地启动的详细步骤与验证方法

我的启动顺序从来都是先后端、再前端,因为前端登录时要调后端接口,后端没起来,前端页面全是接口错误。

后端启动:

  1. 在 IDEA 里打开后端项目根目录,等待 Maven 下载依赖。第一次下载会比较慢,建议在 Maven 的settings.xml里配置阿里云镜像,能省一大半时间。
  2. 修改application.yml里的数据库 URL、账号、密码。
  3. 右键运行Application.java,看到Started Application in x.x seconds就成功了。
  4. 打开浏览器访问http://localhost:8080/api/auth/login(GET),如果返回 405 而不是 404,说明接口已经注册成功。

前端启动:

  1. 在 VSCode 或 IDEA 的终端里进入前端项目目录。
  2. 执行npm install,装完依赖后执行npm run dev。
  3. 看到VITE vx.x.x ready in xxx ms且打印出本地地址http://localhost:5173后,用浏览器打开。
  4. 如果页面能正常加载出登录界面,但点击登录提示“获取用户信息失败”,多数是代理没生效,检查vite.config.js里的 proxy 配置。

完整启动之后,用管理员账号登录,能看到仪表盘。随便点几个菜单,看看接口返回数据和表格渲染是否正常。到这里一套源码就算是真正跑通了。

5.3 生产环境部署的两种常见方式

部署这种东西,很多人觉得要买服务器很麻烦,但其实两种方式都还行,我就挑最省事的两条总结下。

第一种是把前端 build 到后端,用 SpringBoot 内嵌 Tomcat 直接启动。流程很简单:

# 前端构建 npm run build # 把 dist 目录下的文件复制到后端 src/main/resources/static/ # 然后打包后端 mvn clean package -DskipTests # 得到 jar 包后启动 java -jar epidemic-system.jar --spring.profiles.active=prod

这种方式适合单机小项目或者毕业设计部署。好处是只需要一个端口、一个进程,不用配置 Nginx。坏处是如果前端资源和后端接口形成同一个 Jar 包,以后改前端得重新打包整个后端,稍显笨拙;因此更讲究一些的项目,还是建议用 Nginx。

第二种是前后端分开部署:后端打 jar 单独跑在 8080 端口,前端 build 后的静态文件放到 Nginx 的 html 目录,Nginx 配置里把/api反向代理到http://localhost:8080。这样重构前端时不用碰后端,生产环境应用的也只是少数几个文件。2025 年了,我强烈建议学会 Nginx 这个部署方式——不管是实习还是正式工作,这个都属于基础中的基础。

Nginx 配置核心部分就这一段:

server { listen 80; server_name your.domain.com; root /var/www/epidemic-web; index index.html; location / { try_files $uri $uri/ /index.html; } location /api/ { proxy_pass http://127.0.0.1:8080/api/; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } }

这里最重要的是第一条try_files $uri $uri/ /index.html;,它解决了前文提到的刷新 404 问题。Nginx 在找不到路径时会把请求回退到index.html,交给 Vue Router 处理,从而保证子路由刷新正常。

5.4 给新手的实操建议与拓展方向

最后说几句掏心窝子的话。如果你是完全没写过全栈项目的新手,我建议从这个项目入手时不要一开始就追求把所有功能都跑通,而是先“死磕”一条链路:登录、上报健康信息、管理端审核。把这一条链路里的前后端请求、数据流、权限校验全部搞懂,比模糊地浏览所有页面有价值得多。

如果已经有基础,想让这个系统从“毕设水平”提升到“简历可写项目”,可以从这几个方向扩展:

  1. 接入消息推送:每天到点提醒用户上报,可以用 Spring 的定时任务 + 微信模板消息或钉钉机器人。这个扩展能让系统看起来更完整,因为“提醒”这是管理类系统的刚需。
  2. 增加多级审批流:异常人员处理改成多级审核,比如班级管理员审核后还要学院管理员确认。这就涉及到工作流设计,非常有含金量。
  3. 用 Redis 做热点缓存:比如把今日上报人数缓存到 Redis,设置 5 分钟过期,降低数据库压力。在简历里写上“缓存优化”这个关键词,面试时就有话题可以聊。

我个人实际折腾过一遍的感受是,这类管理系统的难点从来不在技术选型,而在于把“健康上报”这件事理解透:谁能上报、谁必须上报、上报什么字段、谁可以看、谁可以改、异常怎么流转。把这些业务流程理顺了,代码只是实现你想法的工具而已。

最后再分享一个小技巧:做任何全栈项目时,一定要把前端每次请求的参数和后端接口返回的数据打印到控制台对比着看。很多时候前端调不通,不是因为后端代码错了,而是接口需要的字段名和你从表单里取出来的字段名对不上,比如前端传了userId,后端接收的却是user_id,查不到数据就很自然了。我遇到的头号后端调试痛点基本都在这。

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

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

立即咨询