1. 项目整体拆解为什么是这套技术栈先说结论这个Java Web疫情防控管理系统本质上是一套典型的前后端分离的管理信息系统覆盖了疫情场景下的核心业务闭环——人员健康信息上报、异常预警、通行核验、物资管理、公告发布。它不是一个玩具项目而是把 SpringBoot2 Vue3 MyBatis-Plus MySQL8.0 这四样东西串成了一个可直接运行的完整工程。对于正在做课程设计、毕业设计或者想系统学习企业级开发链路的人来说这套代码的价值在于你用一套代码就能看懂前端交互、后端接口、数据库设计、权限控制这整条线是怎么连起来的。先聊技术选型。SpringBoot2 目前依然是国内中小型项目和教学场景的主力稳定、资料多、坑少。Vue3 是前端框架的当前主流方向组合式 API 写起来比 Options API 更接近工程化思维。MyBatis-Plus 解决了传统 MyBatis 大量手写 SQL 的痛点单表 CRUD 几乎不用写 XML内置分页插件和逻辑删除非常适合这种以管理后台为主的项目。MySQL8.0 则是当下最常用的开源关系型数据库窗口函数、公共表表达式这些特性在报表统计场景很好用。这套组合的搭配逻辑很清晰SpringBoot2 负责把后端服务拆成标准的三层结构MyBatis-Plus 负责把数据访问层的重复劳动降到最低Vue3 负责把管理界面的交互做得干净利落MySQL8.0 负责把业务数据稳稳落盘。每个组件都不是最花哨的但组合在一起非常稳这正是生产环境最看重的东西。从业务场景来看这个系统的核心价值在于信息流转。防疫管理最大的痛点是数据分散员工/学生的健康信息在群里接龙物资库存靠手工台账重点人员信息在 Excel 表格里。这套系统要解决的就是把散落的数据统一收口到同一个平台里让管理员能看到全局让用户能自助填报让数据能自动触发异常预警。如果你正准备基于这套系统二次开发或者想把它改造成其他管理场景比如企业后勤报修、社区网格化管理这套代码的骨架可以直接复用。因为它的本质就是多角色登录 数据填报 审批流转 统计展示防疫只是业务外壳底下是通用的权限管理逻辑和 CRUD 模板。2. 系统架构与数据库设计思路2.1 前后端分离架构请求是怎么流转的整个系统的运行流程可以简单概括为浏览器里的 Vue3 页面 - axios 发起 HTTP 请求 - SpringBoot 的 Controller 接收 - Service 处理业务 - Mapper 操作数据库 - 结果逐层返回 - 前端渲染页面。前后端通过 JSON 格式交互后端只负责提供接口和数据校验前端只负责展示和交互逻辑互不干扰。这里有个关键前提跨域问题。开发环境下前端跑在 localhost:5173后端跑在 localhost:8080端口不同必然产生跨域。常见方案是后端配置 CORS 过滤器或者前端 Vite 配置 proxy 代理。用 Vite 的 proxy 更推荐因为它只影响开发环境生产环境用 Nginx 反代时不用改代码。后端的三层结构不是花架子而是职责隔离的硬约束Controller 层只做参数接收、调用 Service、返回统一结果对象不写业务逻辑。Service 层处理业务规则比如上报体温超过 37.3℃ 时自动生成异常记录、调用发放通行码等。Mapper 层通过 MyBatis-Plus 的 BaseMapper 继承通用方法比如 selectPage、selectList、updateById复杂统计才写自定义 SQL。2.2 核心表设计建表是地基设计错了后面全难受疫情防控管理系统的数据库设计至少要覆盖五类核心数据用户、健康上报记录、通行码、物资、公告。我直接说实际建表时最需要注意的字段设计用户表sys_user必备字段id、username、password、real_name、role_type角色类型、phone、id_card、department部门/学院、status账号状态。密码必须加密存储推荐使用 BCrypt不要用 MD5。MD5 是散列算法不是加密算法彩虹表分分钟跑出来。健康上报表health_report这是整个系统的核心表。字段包括id、user_id、report_date上报日期、temperature体温、health_status健康状况枚举、is_risk_area是否到过风险地区、is_contact是否接触疑似病例、remark备注。关键设计点是表达方式——用 user_id report_date 做唯一约束保证一个人一天只能上报一条记录避免重复数据。通行码表pass_code字段包括id、user_id、code_type绿码/黄码/红码、generate_time、expire_time、reason。通行码不是固定不变的而是根据最新的健康上报记录和行程信息动态计算出来的。你可以选择每天定时任务批量生成或者每次访问页面时实时计算后者的实现更简单也不容易出逻辑漏洞。物资表material和物资出入库记录表material_log物资管理采用的是库存总表 流水明细的模式。material 表只存当前库存总量material_log 表记录每一条入库、出库操作。这样做的好处是——库存永远可以从流水表里重新推算出来就算某条库存数据被误改了查流水也能恢复对账。出入库操作必须放在事务里扣减库存时用 UPDATE ... SET stock stock - #{num} WHERE stock #{num} 这样的原子操作避免并发超卖。公告表notice字段参考id、title、content、publish_time、publisher_id。这个表最简单但要注意时间字段用 datetime 而不是 timestamp因为 timestamp 有 2038 年问题。2.3 权限模型用角色字段还是 RBAC 四表五表很多课程设计项目的权限设计就一句话user 表里有个 role 字段管理员判断一下 role1 就放行。这个方案在小项目里能用但扩展性很差。如果你想让系统更像真实企业项目建议至少做到基于角色的访问控制RBAC用户表、角色表、菜单表、用户角色关联表、角色菜单关联表。不过这也要看实际需求。如果系统就两类角色——管理员和普通用户那用 role_type 字段完全够用还能省掉大量关联查询。如果要把角色拆成超级管理员、部门管理员、普通用户、志愿者等多类型那就得上 RBAC 模型。我的建议是先想清楚业务有多少角色不要把权限设计做得过度复杂否则维护成本远大于收益。这个项目里值得借鉴的权限控制点是接口层面的拦截校验。后端需要一个拦截器或者 AOP 切面对所有非登录接口做 token 校验对管理员专属接口校验角色。不要只靠前端隐藏按钮来实现权限——前端隐藏只是用户体验后端校验才是安全底线。别人拿 Postman 直接调你的管理接口绕过页面照样能改数据。3. 后端核心实现SpringBoot2 与 MyBatis-Plus 的实战组合3.1 统一返回结果与全局异常处理一个成熟的后端工程第一步一定不是写业务代码而是搭好统一返回结构。我见过太多项目每个接口返回的 JSON 格式都不一样前端解析的时候痛不欲生。这个项目里你应该定义 Result 类包含 code、message、data 三个字段所有接口统一返回这个格式。Data public class ResultT { private Integer code; private String message; private T data; public static T ResultT success(T data) { ResultT result new Result(); result.setCode(200); result.setMessage(操作成功); result.setData(data); return result; } public static T ResultT error(String message) { ResultT result new Result(); result.setCode(500); result.setMessage(message); return result; } }全局异常处理用 RestControllerAdvice 注解统一兜底把业务异常、参数校验异常、未知异常分别包装成对应的 Result 返回。这样前端可以统一在 axios 拦截器里处理错误弹窗不用每个请求都写 try-catch。3.2 MyBatis-Plus 的宝藏功能CRUD 接口、分页插件、逻辑删除、自动填充MyBatis-Plus 最爽的地方是继承 BaseMapper 后单表 CRUD 全免写 SQL。你只需要一个实体类和 Mapper 接口public interface HealthReportMapper extends BaseMapperHealthReport { // 自定义复杂查询可以写在 XML 里或者用 Select 注解 }这行代码意味着 selectById、selectList、insert、updateById、deleteById 这些方法全都有了。查询条件用 LambdaQueryWrapper 构造类型安全字段名写错了编译期就报错LambdaQueryWrapperHealthReport wrapper new LambdaQueryWrapper(); wrapper.eq(HealthReport::getUserId, userId) .eq(HealthReport::getReportDate, today) .orderByDesc(HealthReport::getCreateTime); HealthReport report healthReportMapper.selectOne(wrapper);分页插件配置要注意MyBatis-Plus 3.4 之后分页插件构造方式变了要用 PaginationInnerInterceptorConfiguration public class MybatisPlusConfig { Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL)); return interceptor; } }逻辑删除是一个很容易被忽视但非常实用的功能。在实体类的 deleted 字段上加上 TableLogic 注解MyBatis-Plus 就会把 deleteById 自动转成 update 语句数据不会物理删除而是标记为已删除。这在做人员健康记录这类需要留痕的业务场景非常关键。逻辑删除字段在实体上定义后所有自动拼接的 SQL 都会带上AND deleted0这个条件。自动填充是另一个省事的点。实体类里定义 createTime、updateTime 字段加上 TableField(fill FieldFill.INSERT) 和 TableField(fill FieldFill.INSERT_UPDATE)再写一个 MetaObjectHandler 实现类插入和更新时就会自动填充时间字段Component public class MyMetaObjectHandler implements MetaObjectHandler { Override public void insertFill(MetaObject metaObject) { this.strictInsertFill(metaObject, createTime, LocalDateTime.class, LocalDateTime.now()); this.strictInsertFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } Override public void updateFill(MetaObject metaObject) { this.strictUpdateFill(metaObject, updateTime, LocalDateTime.class, LocalDateTime.now()); } }3.3 登录认证与 JWT 的设计细节登录是整个系统的入口也是最容易被忽视安全问题的地方。推荐实现方式是 JWTJSON Web Token流程是用户提交用户名密码 - 校验 BCrypt 密文 - 生成 token 返回前端 - 前端后续请求在请求头带 Authorization - 后端拦截器解析 token 获取用户信息。JWT 生成用 jjwt 库密钥要放在配置文件里不要硬编码在代码中。token 要设置过期时间一般开发环境设 2 小时生产环境可以结合 refresh token 机制做续期。拦截器里解析 token 后把用户信息放入 ThreadLocal 或者 RequestContext方便后续业务代码直接获取当前登录用户。实际写的时候有几个细节容易踩坑。第一SpringBoot 配置拦截器时要注意放行登录接口和静态资源不然前端页面都加载不出来。第二JWT 解析抛异常要区分是 token 过期还是 token 非法分别返回不同的提示信息。第三如果项目里有多个角色生成 token 时就把角色信息放进去避免每次请求都查一次数据库。3.4 大屏统计与报表接口分组查询的 SQL 写法这类管理系统一般会带一个数据看板页面展示今日上报人数、异常人数、物资库存预警等统计数据。这些统计指标对应的 SQL 基本是 GROUP BY COUNT 时间范围过滤的组合。比如查最近 7 天每天的上报人数SELECT DATE(report_date) AS report_day, COUNT(*) AS cnt FROM health_report WHERE report_date DATE_SUB(CURDATE(), INTERVAL 6 DAY) GROUP BY DATE(report_date) ORDER BY report_day;用 MyBatis-Plus 的 QueryWrapper 也能实现部分分组统计但涉及日期函数处理时直接写 Select 注解或 XML 里的自定义 SQL 更直观。实际项目中建议把复杂统计 SQL 单独放在一个 StatisticsMapper 里保持代码结构清晰。报表接口要额外注意日期处理的边界问题。前端传的日期范围通常是字符串后端要正确解析成 LocalDateTime 或 Date并注意结束时间要取到当天的 23:59:59否则当天记录统计不进去。这个坑我印象太深刻了排查半天最后发现是时间边界差了一天。4. 前端核心实现Vue3 组合式 API 与工程化实践4.1 Vue3 项目搭建与目录结构规划用 Vite 创建 Vue3 项目是现在的主流做法命令就一行npm create vitelatest epidemic-admin -- --template vue比 Webpack 时代的配置轻量太多开发服务器启动速度基本秒开。进入项目后建议 src 目录按模块划分api接口请求封装、router路由配置、stores全局状态、views页面视图、components公共组件、utils工具函数。项目里用到的核心依赖大概这几类vue-router路由、pinia状态管理、axiosHTTP 请求、element-plusUI 组件库、echarts图表统计。Element Plus 是目前 Vue3 生态里最成熟的 UI 库表格、表单、弹窗这些管理后台常用组件开箱即用样式也统一。4.2 请求封装与拦截器别让每个页面都重复写 axiosAxios 封装是前端工程质量的分水岭。随便在组件里写 axios.get 的项目后续改 baseURL 要改几百处调试接口时也没有统一日志。标准做法是建一个 request.js 工具模块统一配置 baseURL、超时时间、请求拦截器、响应拦截器import axios from axios import { ElMessage } from element-plus import router from /router const request axios.create({ baseURL: /api, timeout: 10000 }) // 请求拦截器自动携带 token request.interceptors.request.use(config { const token localStorage.getItem(token) if (token) { config.headers.Authorization Bearer ${token} } return config }) // 响应拦截器统一处理错误码 request.interceptors.response.use( response { const res response.data if (res.code ! 200) { ElMessage.error(res.message) return Promise.reject(new Error(res.message)) } return res.data }, error { if (error.response error.response.status 401) { ElMessage.error(登录状态已过期请重新登录) localStorage.removeItem(token) router.push(/login) } else { ElMessage.error(网络请求失败) } return Promise.reject(error) } ) export default request这里一个很重要的设计是把后端返回的 res.data 直接透传出去这样页面里调用 api 时拿到的就直接是业务数据不需要每次写 res.data.data 这样的嵌套代码。调用方式也统一成模块化导出// api/report.js import request from /utils/request export function submitHealthReport(data) { return request.post(/health-report/submit, data) } export function getReportPage(params) { return request.get(/health-report/page, { params }) }4.3 路由守卫与权限控制前端防君子不防小人前端路由守卫解决的痛点是用户没登录直接访问 /admin 页面或者登录后访问没有权限的菜单。Vue3 中写在 router 的 beforeEach 钩子里router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path ! /login !token) { next(/login) } else { next() } })动态路由是根据用户角色动态注册菜单的一个进阶方案实现稍复杂。角色为管理员时前端把管理相关路由 addRoute 进去普通用户则不注册这些路由。好处是菜单列表天然跟着路由走权限配置在路由表里一目了然。但要注意动态路由刷新后状态会丢失需要把路由恢复逻辑写到全局守卫里或者存一份到 localStorage。4.4 Element Plus 表格与表单实战搜索、分页、弹窗三件套管理后台的页面逻辑基本都是同一个套路搜索条件区 表格数据区 分页区 新增/编辑弹窗。以健康上报记录管理页为例核心模板结构大概是el-card el-form inline el-input v-modelsearchForm.keyword placeholder姓名/手机号 / el-date-picker v-modelsearchForm.dateRange typedaterange / el-button typeprimary clickhandleSearch查询/el-button /el-form el-table :datatableData v-loadingloading el-table-column proprealName label姓名 / el-table-column proptemperature label体温 / el-table-column prophealthStatus label健康状态 / el-table-column label操作 template #default{ row } el-button link typeprimary clickhandleDetail(row)查看/el-button el-button link typedanger clickhandleDelete(row)删除/el-button /template /el-table-column /el-table el-pagination v-model:current-pagequeryParams.pageNum v-model:page-sizequeryParams.pageSize :totaltotal changeloadTableData / /el-card组合式 API 把相关逻辑放一起比 Options API 的 data/methods/computed 分开写更聚焦。一个列表页的逻辑主要是 searchForm、queryParams、tableData、total、loading 这些响应式变量加上 loadTableData、handleSearch、handleDelete 几个函数。注意分页的 current-page 和 page-size 要绑定到 queryParams 上切换页码时把分页参数传到后端。4.5 echarts 可视化页面从接口数据到图表渲染链路数据看板通常用 echarts 渲染柱状图、折线图、饼图。实现链路很简单页面挂载后调用统计接口拿数据 - 把数据组装成 echarts 需要的 option 格式 - echarts.init 实例化 - setOption 渲染。import * as echarts from echarts const initChart (data) { const chart echarts.init(document.getElementById(reportTrend)) chart.setOption({ xAxis: { type: category, data: data.dates }, yAxis: { type: value }, series: [{ type: line, data: data.counts, smooth: true }] }) window.addEventListener(resize, () chart.resize()) }这里要提醒一个实际场景的细节echarts 图表容器需要有明确的宽高。如果父容器是 flex 布局或者百分比高度图表可能渲染不出来打开控制台报Cant get DOM width or height。调试时给容器设个固定高度是最快的验证方式。另一个细节是图表在 tab 切换或者弹窗里显示时容器可能还没有完成布局需要在 nextTick 后初始化或者调用 chart.resize() 刷新。5. 环境搭建与部署MySQL8.0 和项目启动的实战流程5.1 MySQL8.0 安装与配置Windows 和 Docker 两条路线MySQL8.0 安装是很多新手卡住的第一关。Windows 上推荐用 ZIP 压缩包方式安装比安装向导更可控。简单说流程解压 - 新建 my.ini 配置文件 - 初始化数据目录 - 安装服务 - 启动服务 - 设置 root 密码。my.ini 至少需要配置端口和字符集[mysqld] port3306 character-set-serverutf8mb4 default-storage-engineInnoDB max_connections200初始化时以管理员身份运行 CMD进到解压目录的 bin 下执行 mysqld --initialize-insecure这一步会生成一个空密码的 root 用户。然后 mysqld --install 安装为 Windows 服务net start mysql 启动服务。最后用 mysql -u root -p 登录执行 ALTER USER rootlocalhost IDENTIFIED BY 你的密码;。如果本机装了 Docker用容器跑 MySQL8.0 更省心docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORDroot123456 \ -e TZAsia/Shanghai \ -v /opt/mysql-data:/var/lib/mysql \ mysql:8.0注意 -v 挂载数据目录这样容器删了数据还在。用 Docker 方式的另一个好处是免去了本机装服务的各种兼容性问题用完 docker stop 一关就干净了。5.2 项目配置与启动从 clone 到跑起来全流程拿到项目源码后整个启动流程要过四关第一关数据库准备。新建数据库后执行项目附带的 SQL 脚本初始化表结构和测试数据。注意检查 SQL 脚本里的字符集建议建库用 utf8mb4不然存 Emoji 或者生僻字会报错。第二关后端配置检查。打开 application.yml确认数据源配置重点检查时区。MySQL8.0 的 JDBC URL 必须带 serverTimezoneAsia/Shanghai否则报连接超时或者时区错误。完整示例spring: datasource: url: jdbc:mysql://localhost:3306/epidemic_db?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalseallowPublicKeyRetrievaltrue username: root password: root123456 driver-class-name: com.mysql.cj.jdbc.DriverallowPublicKeyRetrievaltrue 这个参数是 MySQL8.0 连接时的常见坑不加它就报Public Key Retrieval is not allowed。第三关前端依赖安装。进入前端目录执行 npm install。如果你在安装 element-plus 或 echarts 时遇到 peer dependency 冲突加上 --legacy-peer-deps 一般能过。npm install 慢的话可以临时切到国内镜像源。第四关前后端启动。后端直接启动 SpringBoot 的 main 方法。前端 npm run dev 跑 Vite 开发服务器。如果前端配置了代理Vite 的 vite.config.js 里会有这样一段server: { proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } }这样前端页面请求 /api/xxx 会自动转发到后端 8080 端口前后端开发模式下没有任何跨域问题。5.3 生产环境部署用 Maven 打包和 Nginx 托管开发完要部署上线思路是后端打成 jar 包前端构建出静态文件用 Nginx 托管静态文件并反代后端接口。后端打包mvn clean package -DskipTests生成的 jar 包在 target 目录用 java -jar 启动。如果服务器内存小可以加 -Xms256m -Xmx512m 限制 JVM 内存。前端构建npm run build构建产物在 dist 目录把 dist 目录上传到服务器Nginx 配置静态资源目录并设置接口反向代理server { listen 80; server_name your-domain.com; root /opt/epidemic/dist; index index.html; location / { try_files $uri $uri/ /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; } }try_files 那行是 SPA 路由必需的重写规则不然前端 vue-router 用 history 模式时刷新某个子页面会报 404。6. 常见问题与排查技巧实录做这类项目新手最容易卡住的不是业务代码而是环境配置和几个隐蔽的细节。我把实际操作中高频踩坑的问题整理成了一张速查表症状可能原因解决方案后端启动报 Access denied for user数据库账号密码错误或授权不足用 root 账号执行 GRANT ALL PRIVILEGES ON epidemic_db.* TO root%;连接数据库报 Public Key Retrieval is not allowedJDBC URL 缺少 allowPublicKeyRetrievaltrue在连接参数中加上 allowPublicKeyRetrievaltrue前端页面请求接口报 404Nginx 或 Vite 代理没配对路径检查代理配置里 /api 前缀是否与后端 Controller 的 RequestMapping 匹配时间字段相差 8 小时JDBC URL 没设置 serverTimezone统一设置为 serverTimezoneAsia/ShanghaiMyBatis-Plus 分页不生效分页插件没注册确认配置类里加了 PaginationInnerInterceptorVite 启动报端口被占用默认端口 5173 被其他程序占用在 vite.config.js 里设置 server.port 为其他值npm install 卡住或报错网络问题或依赖冲突切换镜像源加 --legacy-peer-deps 选项前端表格数据有但页面不显示后端返回数据结构与前端取值路径不一致检查 Result 包装类结构确认 res.data 是不是真正的列表数据JWT 鉴权拦截导致登录接口也报 401拦截器没放行登录接口在 WebMvcConfig 中排除 /login 和 /captcha 等公开接口逻辑删除后数据查不出来查询条件被自动拼接了 deleted0确认实体类逻辑删除字段映射正确别把逻辑删除字段误加在普通查询条件里再补充几个需要重点注意的实战细节注意分页参数别传错。MyBatis-Plus 分页默认接收 current 和 size但很多前端组件用的是 pageNum 和 pageSize。如果不做参数映射分页查询要么不生效要么结果对不上。建议在 Controller 里显式接收 pageNum 和 pageSize再传给 Service 层的 Page 对象。注意 MySQL8.0 的 ONLY_FULL_GROUP_BY 模式。默认开启的严格模式下GROUP BY 查询容易报which isnt in GROUP BY错误。写统计 SQL 时SELECT 的字段要么在 GROUP BY 里要么用聚合函数包裹。不建议直接改 sql_mode 关掉这个约束规范写 SQL 才是正道。注意前端删除操作要二次确认。Element Plus 的 ElMessageBox.confirm 弹窗不要省掉。这种管理系统的数据都是重要的健康记录手滑点错删除按钮就直接少一条记录虽然逻辑删除还能恢复但用户心里会慌你也得花时间解释。注意文件上传的路径问题。如果系统里有导入 Excel 或者上传头像的功能上传文件保存的位置不要写在项目目录下。项目重新部署时文件会丢。正确做法是配置一个外部磁盘路径比如 /opt/epidemic/upload用配置项注入到代码里。7. 项目二开方向与个人经验总结最后说说这个项目在我看来的后续扩展空间。疫情管理系统虽然是一个特定历史阶段的产品形态但它的技术骨架完全可以复用到其他管理场景。如果你拿这套代码改二开按优先级推荐几个方向第一换成企业员工健康打卡系统把健康上报变成考勤打卡或者每日温度登记核心逻辑几乎不需要改。第二扩展成社区网格化管理系统加入楼栋管理、住户信息、访客登记这些模块。第三改成通用的表单收集 审核审批平台让管理员自定义表单字段业务自由度更高。还有一点技术层面的建议如果你的生产环境并发量上来了优先优化数据库层的查询效率。先看慢查询日志给高频查询字段建索引比如 health_report 表的 (user_id, report_date) 联合索引。其次引入 Redis 做缓存把健康上报这种高频写入场景的数据先写缓存再异步落库。但这两个优化都别一上来就做先保证功能稳定、数据准确再去谈性能。说句实在话这种管理系统项目的技术天花板不算高但它帮你把从零到一搭建完整工程的流程完整走了一遍。你会碰到跨域、依赖冲突、数据库连接、分页失效、时区偏移、路由守卫这些在企业开发里每天都在发生的问题而且因为项目足够小你能看到问题的全貌排查起来不会像老系统那样深不见底。这个经验比项目本身更值钱。如果这篇内容对你有帮助建议直接打开源码把每个模块过一遍先看数据库脚本理清表关系再看后端 Controller 和 Service 的调用链然后照着前端页面把接口对应起来。三遍走下来SpringBoot2 Vue3 MyBatis-Plus 这条开发脉络你就彻底通了。后面换任何业务场景无非是换表结构、换页面表单、换统计 SQL骨架和套路是通用的。