接手过不少学生项目和内部管理系统,看到“Java Web 疫情防控管理系统”这个标题,第一反应是亲切——SpringBoot2、Vue3、MyBatis-Plus、MySQL8.0,标准得不能再标准的技术栈组合。这类项目在毕设、课设、技能培训中出现的频率非常高,甚至有些外包单子也在要类似的“信息登记+健康上报+数据看板”管理系统。标题里那个【含文档】,在求职和答辩场景下往往是加分项,这点很多光秃秃挂源码的项目比不了。
这篇博文我就围绕这个项目的完整形态,把技术选型、数据库设计、前后端核心实现、部署过程中的坑,从头到尾捋一遍。不管你是拿它做毕设参考,还是想从中抽一套通用管理系统的骨架,都能找到可以“抄作业”的部分。
1. 项目概述与技术选型思路
1.1 这套系统到底解决什么问题
先看业务本质。疫情防控管理系统的核心,在于把“人”和“状态”两条线管起来。线下场景里,社区、学校、园区、企业每天都有人在出入口登记体温、填报健康信息、记录外来访客,这些数据如果靠Excel和纸质表格堆,查询、统计、追溯会非常痛苦。一个管理系统要解决的,就是让这些信息线上化、结构化、可检索,同时给管理者一个直观的统计看板。
拆开来看,常见的功能需求无非这么几类:人员档案管理(常住人口、学生、职工)、每日健康信息上报(体温、症状、行程情况)、出入登记与访客记录、异常状态预警、数据统计与导出。这套系统说到底是“人员信息+健康事件”的场景化整合,搞清楚这个定位,数据库设计和模块划分就不会跑偏。
这也解释了为什么这类项目适合做毕设和求职项目。业务不复杂但完整,有前端交互、有后端逻辑、有数据库设计、有权限需求,刚好覆盖一个Web开发核心知识点。你要是能把它讲清楚、改出亮点,面试官基本能判断你的CRUD不是粘贴来的。
1.2 技术栈选型背后的取舍
再聊聊技术栈。这套组合在2024年、2025年依然非常主流,但每一个选型背后都有可说的理由。
后端用SpringBoot2而不是SpringBoot3,很重要的原因是生态兼容性。SpringBoot2.x经过多年迭代,网上资料、踩坑帖子、企业存量项目数量都极为庞大,MyBatis-Plus、各种第三方starter对2.x的适配也最稳。SpringBoot3虽然性能和体量上更先进,但伴随的是Jakarta EE命名空间迁移、JDK17基线,部分老库包在适配期出现过各种兼容问题。对于教学、毕业设计、快速交付项目,稳定压倒一切,选2.7.x是最省心的。
前端Vue3是当前前端框架的绝对主流。相比Vue2,组合式API(Composition API)让逻辑复用更干净,配合Vite开发服务器,热更新速度明显快于webpack,开发体验提升了一个档次。Element Plus作为Vue3生态里最成熟的UI组件库,表格、表单、弹窗、分页组件齐全,后台管理页面开发效率极高。如果你用React的Ant Design也无不可,但Vue3+Element Plus的匹配度在这个场景里确实更高,学习成本也更低。
MyBatis-Plus解决了MyBatis的一个痛点:单表CRUD不用写XML和SQL。内置的BaseMapper已经提供insert、deleteById、selectPage等方法,加上条件构造器QueryWrapper和LambdaQueryWrapper,复杂查询也不需要手写大量XML。配合分页插件,后端分页查询的代码量能减少一半以上。这个选型特别适合中小型管理系统,业务表多但单表逻辑居多,生产力和可维护性平衡得很好。
MySQL8.0相比5.7带来的升级也是实打实的。默认字符集utf8mb4,中文和emoji存储无压力;窗口函数、CTE公共表表达式让统计类SQL写法更优雅;性能方面对索引和优化器有明显改进。加上8.0是当前新装数据库的主流版本,项目直接用8.0能免去以后迁移的麻烦。
2. 核心业务模块与数据库设计
2.1 业务模块怎么拆
模块划分决定了项目的骨架。常见做法是拆成五个核心模块:用户管理(含角色权限)、健康上报、出入登记、访客管理、统计看板。
用户管理负责登录、人员信息维护、账号启停用。权限上建议拆成三层:管理员、健康管理人员、普通用户。管理员管人员账号和系统配置,健康管理人员负责查看上报数据和异常处理,普通用户每天填报自己的健康信息。不需要引入Spring Security这种重框架,用拦截器或者处理器拦截校验角色即可,这类项目的事务边界比较清晰,轻量方案反而更直观。
健康上报是每天高频操作,字段包括体温、有无咳嗽乏力、有无中高风险区域旅居史、当前健康码状态等,同时记录填报时间和所属人员。出入登记针对进出办公区、校门场景,记录出入时间、门禁点位、体温。访客管理管外部人员的预约和登记,通常要在访客信息里关联受访人。统计看板则用图表展示今日上报人数、异常人数、出入人次等,这部分适合聚合SQL和前端图表组件配合实现。
2.2 核心表结构和字段设计
表结构设计直接决定后面写代码的心情。以用户表和健康上报表为例,给出常见的建表参考。用户表的核心字段如下:
CREATE TABLE sys_user ( id BIGINT PRIMARY KEY AUTO_INCREMENT COMMENT '主键ID', username VARCHAR(64) NOT NULL UNIQUE COMMENT '登录账号', password VARCHAR(128) NOT NULL COMMENT '密码(BCrypt加密)', real_name VARCHAR(64) COMMENT '真实姓名', phone VARCHAR(20) COMMENT '手机号', user_type TINYINT DEFAULT 3 COMMENT '1-管理员 2-健康管理人员 3-普通用户', status TINYINT DEFAULT 1 COMMENT '1-启用 0-禁用', deleted TINYINT DEFAULT 0 COMMENT '逻辑删除标记', create_time DATETIME COMMENT '创建时间', update_time DATETIME COMMENT '更新时间' ) ENGINE=InnoDB COMMENT '系统用户表';健康上报表:
CREATE TABLE health_report ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL COMMENT '上报人ID', report_date DATE NOT NULL COMMENT '上报日期', temperature DECIMAL(4,1) COMMENT '体温(如36.5)', is_cough TINYINT DEFAULT 0 COMMENT '0-无咳嗽 1-有咳嗽', is_fatigue TINYINT DEFAULT 0 COMMENT '0-无乏力 1-有乏力', travel_history VARCHAR(255) COMMENT '近期行程说明', health_code_status TINYINT DEFAULT 1 COMMENT '1-绿码 2-黄码 3-红码', remark VARCHAR(255) COMMENT '备注', deleted TINYINT DEFAULT 0, create_time DATETIME, update_time DATETIME, KEY idx_user_date (user_id, report_date) ) ENGINE=InnoDB COMMENT '每日健康上报表';字段设计的时候有几个容易忽略的细节。
user_type和status都用TINYINT存枚举,不要用VARCHAR存“管理员”“启用”这种中文字符串,一是存储空间大,二是容易写错,三是排序比较都不方便。前端展示时做一次转换即可,后端还可以用字典表维护,但小项目直接用常量类或者枚举类就行。
deleted字段是配合MyBatis-Plus逻辑删除用的,字段名和全局配置保持一致,查询时MP会自动追加deleted=0条件,防止误查已删除数据。但要注意唯一索引和逻辑删除的冲突:如果username上有唯一索引,逻辑删除后再次插入同用户名会报错。简单处理是唯一索引改成普通索引,在业务代码里做存在性校验。
create_time和update_time建议用MyBatis-Plus的自动填充功能统一赋值,不在业务代码里手写每一处setCreateTime。表里这两个字段统一叫create_time/update_time,代码里对应createTime/updateTime,配置好MetaObjectHandler之后,插入和更新时自动填值,省事且不容易漏。
健康上报表里加联合索引idx_user_date,是因为最常见的查询就是“查某个人某天/某段时间的上报记录”,如果没有这个索引,数据量上来后全表扫描会很痛苦。出入登记表、访客表也是同理,外键关联查询字段建议都建索引。
3. 后端核心实现:SpringBoot2 + MyBatis-Plus
3.1 后端工程结构和分层设计
后端工程结构是这类项目最容易踩坑的地方。有人把所有类都扔在几个包下面,包名混乱,最后自己都找不到代码。建议按功能模块分包,而不是按技术分层分包。结构参考如下:
com.example.epidemic ├── common # 通用类:状态码、异常、工具类 │ ├── Result.java │ ├── ResultCode.java │ └── BusinessException.java ├── config # 配置类:MP分页插件、跨域配置、拦截器注册 ├── controller # 控制层 │ ├── AuthController.java │ ├── HealthReportController.java │ └── ... ├── entity # 数据库实体 │ ├── SysUser.java │ └── HealthReport.java ├── mapper # MyBatis-Plus的Mapper接口 ├── service # 业务逻辑层 │ └── impl ├── dto # 接收前端参数的传输对象 ├── vo # 返回给前端的视图对象 └── handler # 全局异常处理器、自动填充处理器分包的原则是清晰可查、依赖方向一致。controller只做参数接收和结果返回,业务逻辑放service层,数据访问放mapper层,entity对应数据库表,dto/vo负责在不同边界传输数据。很多初学者喜欢在controller里直接写一堆业务代码,当时看着快,后面维护和改需求时会非常痛苦。至少保证controller层代码不超过10行,把逻辑交出去,这个习惯值得养成。
3.2 认证与权限的实现细节
认证方案里,JWT比Session更适合前后端分离。Session方案需要依赖Cookie传递JSESSIONID,存在跨域携带问题,而在前后端分离架构下,后端服务可能和前端页面不在同一台机器,域名端口不一致的情况很常见,Cookie策略处理起来麻烦。JWT把用户身份信息签名后发给前端,前端每次请求在请求头Authorization里带上token,后端无状态验签即可。
实现上可以简洁但完整:
// 登录接口核心逻辑 public Result login(LoginDTO dto) { SysUser user = userService.getOne( new LambdaQueryWrapper<SysUser>() .eq(SysUser::getUsername, dto.getUsername())); if (user == null) { throw new BusinessException("用户不存在"); } if (!BCrypt.checkpw(dto.getPassword(), user.getPassword())) { throw new BusinessException("密码错误"); } if (user.getStatus() == 0) { throw new BusinessException("账号已被禁用"); } String token = JwtUtil.createToken(user.getId(), user.getUserType()); return Result.success(new LoginVO(token, user.getRealName(), user.getUserType())); }密码不要用MD5了,MD5加不加盐都容易被彩虹表攻击。项目里有spring-security-crypto依赖就能用BCrypt,passwordEncoder.encode()和matches()两个方法就够。这是目前性价比最高的密码哈希方案。
JWT校验放在拦截器里。拦截器获取请求头的Authorization,解析token,校验有效性,然后把用户信息放进ThreadLocal或请求上下文,方便controller和service取用。放行路径记住两类:登录接口和静态资源;其他接口统一校验。如果项目里有文件上传下载、健康填报这种非登录页面访问的场景,放行规则要仔细核对,避免漏放行敏感接口。
权限控制这一层不需要做得很重,管理员专属接口在拦截器里或者注解上判断userType即可。核心是别让普通用户能调用管理员的统计接口,用拦截图个方便。
3.3 MyBatis-Plus 的几个实用玩法
MyBatis-Plus在这个项目里的价值远超简单CRUD,几个高级功能用好了,代码量明显下降。
逻辑删除,在yml里配置好全局逻辑删除字段和值,实体上标注@TableLogic,之后调用removeById就不是真实DELETE,而是UPDATE deleted=1,查询自动过滤。这个功能和数据恢复需求配合起来很稳,但要注意前面提到的唯一索引问题。
自动填充,实现MetaObjectHandler接口,对insert和update分别填充创建时间和更新时间,实体字段加@TableField(fill = FieldFill.INSERT)标注。这种做法一劳永逸,新增表也不容易忘。
@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()); } }分页插件是查询类接口的标配。在配置类里注册MybatisPlusInterceptor,加入PaginationInnerInterceptor,然后service层就能直接调用mapper.selectPage(page, wrapper)。前端传页码和每页条数,后端返回总记录数和当前页数据,流程非常固定。
条件构造器是写动态查询的利器。健康上报查询可能需要组合“日期范围、人员姓名、状态”等多个条件,LambdaQueryWrapper比QueryWrapper好在类型安全,字段名编译期就能检查,不会出现手写字符串拼错的情况。
LambdaQueryWrapper<HealthReport> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(HealthReport::getUserId, dto.getUserId()) .ge(dto.getStartDate() != null, HealthReport::getReportDate, dto.getStartDate()) .le(dto.getEndDate() != null, HealthReport::getReportDate, dto.getEndDate()) .orderByDesc(HealthReport::getReportDate);这里需要特别注意的是,MyBatis-Plus的eq方法如果直接传null值条件会拼成“字段 = null”导致查不到数据。所以条件型参数一定要用带boolean判断的ge/le重载方法,先判断参数不为空再加条件。这种细节是“能跑”和“没bug”之间的区别。
关于MyBatis-Plus批量操作的坑,也提醒一句。MP提供的saveBatch底层默认是逐条INSERT,SQL拼接性能并不高,如果一次性插入上万条数据,建议用自定义SQL的foreach批量插入,或者分批提交。日常管理系统的批量导入场景一般几百条,saveBatch够用,但别盲目相信“批量就快”。
4. 前端核心实现:Vue3 + Element Plus
4.1 前端工程结构与技术点梳理
前端这块我用Vite构建Vue3项目,在npm init vue@latest创建基础工程后,安装了vue-router、pinia、axios和element-plus。目录结构整理为:
src ├── api # 接口请求模块 │ ├── auth.js │ ├── health.js │ └── user.js ├── assets # 静态资源 ├── components # 公共组件 ├── layout # 后台布局(侧边栏+顶部栏) ├── router # 路由配置 │ └── index.js ├── store # Pinia状态管理 │ └── user.js ├── utils # 工具类:axios封装、token存取 ├── views # 页面组件 │ ├── login │ ├── dashboard │ ├── user │ ├── health │ └── visitor ├── App.vue └── main.jsapi目录单独管理接口请求是很推荐的实践。页面组件里不直接写axios地址,而是引入api模块的函数,统一维护后端接口路径。后端接口改了路径,只需要改api目录对应的文件,不会满项目找。
// src/api/health.js import request from '@/utils/request' export function getHealthReportPage(data) { return request({ url: '/api/health/page', method: 'post', data }) } export function saveHealthReport(data) { return request({ url: '/api/health/save', method: 'post', data }) }Element Plus的表格组件el-table和表单组件el-form是这个项目的主力。列表页几乎都是同一个模式:搜索区、新增按钮、表格、分页。把这套模式做成一个通用模板文件,后面新增模块直接复制修改,开发效率能提升一大截。
4.2 请求封装与路由守卫
axios封装是所有前后端分离项目必须用心处理的地方。统一处理baseURL、超时时间、token注入和响应拦截是核心。
// src/utils/request.js import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' const request = axios.create({ baseURL: '/api', timeout: 15000 }) 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 }, error => { if (error.response && error.response.status === 401) { localStorage.removeItem('token') ElMessage.error('登录状态已过期,请重新登录') router.push('/login') } else { ElMessage.error(error.message || '网络异常') } return Promise.reject(error) } ) export default request后端返回统一Result结构(code、message、data),前端拦截器里只透传code===200的响应,非200的用Element Plus的ElMessage统一弹出错误信息。页面里的业务方法就不用每个都写try-catch和错误提示了,代码干净非常多。
路由守卫负责未登录拦截。把需要登录才能访问的路由做统一判断,如果在白名单之外且没有token,直接跳登录页。这个逻辑放在router.beforeEach里:
router.beforeEach((to, from, next) => { const token = localStorage.getItem('token') if (to.path === '/login' && token) { next('/') } else if (to.path !== '/login' && !token) { next('/login') } else { next() } })权限控制如果要做细一点,可以在路由配置的meta里标注角色,按角色动态过滤菜单路由。不过管理端项目一般最多三级菜单,数据量不大,配合后端接口验证,前端控制主要作用是优化体验,不能只靠前端做权限。
开发环境跨域建议优先用Vite的proxy配置,而不是后端开CORS。配置在vite.config.js里:
server: { port: 3000, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } }这样前端代码里请求的/api就自动代理到后端8080端口,浏览器层面完全没有跨域,开发调试顺畅很多。
4.3 后台管理页面的快速搭建
页面层面的核心是复用。管理系统页面的90%都是“表单+表格+弹窗”,用Element Plus搭建时有几点经验。
表格列用el-table-column配置,日期列格式化用formatter函数,状态列直接渲染el-tag标签(比如绿码黄码红码用不同颜色区分),操作列放编辑和删除按钮。删除操作要有二次确认,用ElMessageBox.confirm,比window.confirm好看,也不容易误点。
弹窗表单建议做一个独立子组件,父组件通过ref调用或者v-model控制弹窗显隐。表单校验用el-form的rules规则,比如手机号正则、必填项必审。这里有个关键点:新增和编辑共用一个弹窗组件时,打开编辑前要记得回显数据,打开新增时要把表单重置,否则上次残留的数据还在表单里。
登录页虽然简单,但也是印象分的一部分。账号密码输入、回车提交、登录loading状态处理,这些基础体验别忽略。很多毕设项目被问的第一个问题就是“登录功能怎么实现的”,答不好很容易减分。
前端状态管理用Pinia而不是Vuex。Pinia API更简洁,没有mutations那一层,直接改state,TypeScript支持也更好。本项目规模不大,store里主要存用户信息和登录状态,项目变大后再按模块拆分store也不迟。
5. 部署上线与MySQL8.0使用实践
5.1 MySQL8.0 安装与连接的几个坑
MySQL8.0在后端连接时最容易踩的坑有三个。
第一是JDBC驱动版本。MySQL8.0要求驱动类名是com.mysql.cj.jdbc.Driver,依赖里要用mysql-connector-java 8.0.x以上版本。如果项目还要兼容5.7,用新的驱动也能连5.7,所以直接换8.0驱动没问题。
<dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.33</version> </dependency>第二是时区问题。连接串里必须带serverTimezone参数,不然启动时大概率报“The server time zone value is unrecognized”。推荐统一用Asia/Shanghai:
url: jdbc:mysql://localhost:3306/epidemic?useUnicode=true&characterEncoding=utf8&serverTimezone=Asia/Shanghai&useSSL=false第三是认证插件问题。MySQL8.0默认的caching_sha2_password认证方式在一些旧客户端和中间件上会认证失败。虽然新驱动已经支持,但如果遇到连接报错,可以在MySQL里创建用户后强制指定mysql_native_password,当然更推荐升级驱动而不是降级认证,从安全性角度来说新插件更靠谱。
如果你用Docker安装MySQL8.0,数据卷挂载是必踩的坑。很多人跑docker run不加-v参数,容器删了数据全没。建议命令至少带数据卷和时区参数:
docker run -d \ --name mysql8 \ -p 3306:3306 \ -e MYSQL_ROOT_PASSWORD=root123456 \ -e MYSQL_DATABASE=epidemic \ -v /data/mysql:/var/lib/mysql \ -v /etc/localtime:/etc/localtime:ro \ mysql:8.0MYSQL_DATABASE环境变量会在容器首次启动时自动创建同名数据库并用utf8mb4编码,对于项目初始化非常方便。
5.2 前后端分离项目部署的完整流程
部署前后端分离项目,最简单也最常见的方案是:后端以jar包形式跑在服务器上,前端打包成静态资源交给Nginx托管,Nginx把/api下的请求反向代理到后端服务。
后端打包记得先跑测试再package,或者跳过测试直接package。SpringBoot2打包出来是可直接执行的fat jar:
mvn clean package -DskipTests java -jar epidemic-server-1.0.0.jar --spring.profiles.active=prod如果后端配置了上下文路径,比如server.servlet.context-path=/api,那Nginx的反向代理要对应调整。建议项目不配全局context-path,直接用接口里带/api前缀的方式统一,部署时更灵活。
前端构建:
npm run build构建完成后dist目录就是静态资源。Nginx配置片段参考:
server { listen 80; server_name your-domain.com; root /var/www/epidemic-front; 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这行很关键,Vue Router如果用的history模式,刷新非首页路由时找不到对应物理文件,必须回退到index.html。如果不想处理这个,也可以用hash模式,但url里会带#,不够好看。
生产环境记得关闭SpringBoot的默认错误页和调试日志级别,相关的敏感报错信息不应直接暴露给前端。日志只输出到终端也可以,但正式项目建议配置logback按天滚动输出,排查问题时日志是唯一线索。
6. 常见问题与排查技巧实录
6.1 高频报错与解决方案速查
这类SpringBoot+Vue项目在开发过程中的高频问题,我整理成一个速查表,大部分都是“报错-原因-解决”三件套。
| 报错现象 | 常见原因 | 解决方案 |
|---|---|---|
| 前端请求接口404 | 代理路径或后端context-path不匹配 | 检查vite proxy target、后端接口@RequestMapping前缀、Nginx location代理路径 |
| 后端启动报时区错误 | JDBC连接串缺serverTimezone参数 | 连接串加serverTimezone=Asia/Shanghai |
| LocalDateTime序列化显示为数组或字符串格式不对 | Jackson默认序列化LocalDateTime | yml配置Jackson日期格式或添加JSR310模块 |
| 查询列表接口返回的数据被逻辑删除的也查出来了 | 实体/Del全局配置未生效 | 确认yaml配置logic-delete-field且实体有@TableLogic |
| 前端页面刷新后404 | Vue Router history模式未配置try_files | Nginx配置try_files $uri $uri/ /index.html; |
| 保存数据时创建时间为NULL | MetaObjectHandler未实现或实体缺fill标注 | 实现接口并在字段上标注@TableField(fill) |
| 登录接口返回CORS错误 | 前后端跨域未处理 | 开发用Vite proxy,生产用Nginx同源代理 |
| MyBatis-Plus分页数据不生效 | 未注册分页插件 | 配置类注入MybatisPlusInterceptor并加PaginationInnerInterceptor |
LocalDateTime序列化问题特别值得多说一句。SpringBoot2默认用Jackson序列化LocalDateTime时,容易输出成[2025, 1, 12, 15, 30, 45]这种数组格式,前端拿到的不是标准字符串。两个解决方式,第一是在application.yml里统一配置:
spring: jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: GMT+8但这种方式对LocalDateTime不总是生效。更可靠的是在日期字段上加注解,或者在配置里注册JavaTimeModule并设置LocalDateTimeSerializer。我个人习惯用后者,一劳永逸不会再出幺蛾子。
6.2 我踩过的几个比较隐蔽的坑
表格速查覆盖的是高频问题,还有几个坑埋得比较深,遇到了能卡大半天。写下来供各位避雷。
第一个是MENTION.png图片损坏这种倒是小事,真正的隐蔽问题是逻辑删除和唯一约束的冲突。用户表username字段如果设了唯一索引,一个用户被逻辑删除(deleted=1)后,别人再用同样的用户名注册,数据库层面唯一索引会直接报错。解决办法要么不建唯一索引靠代码判断,要么把唯一索引改为包含deleted字段的联合唯一索引。这个我建议用联合唯一索引方案,数据冗余代价最小。
第二个是MyBatis-Plus主键策略的坑。如果实体主键用的是默认的ASSIGN_ID,生成的雪花ID是19位数字,传到前端后JavaScript的Number类型精度不够,会导致最后几位丢失,编辑时ID对不上后端数据。解决方式有三种:配置主键自增(AUTO)、用字符串类型放ID、或者后端统一把ID序列化为字符串。管理端项目一般还是主键自增最省事,如果已有雪花ID数据再考虑String化方案。
第三个是element-plus的日期选择器和后端日期参数格式。el-date-picker默认给的是字符串或者Date对象,后端如果用LocalDateTime接,需要前端在提交前做个格式化。要么前端用value-format="yyyy-MM-dd HH:mm:ss"指定返回格式,要么后端配合@DateTimeFormat注解。建议统一在前端处理,后端保持LocalDateTime接收。
关于docker容器重启导致MySQL数据丢失的问题,在本地开发环境也常出现。容器删掉再起,如果没挂载数据卷,表全没了,然后一堆人开始纠结“为什么我昨天建的表今天没了”。这个问题文章前面已提过,再强调一次:数据卷挂载是Docker使用MySQL的必配项,没有之一。
7. 从学习角度看这个项目怎么用
代码跑起来只是第一步,怎么把这个项目内化成自己的知识体系才是收获的关键。我的建议是三步走。
第一步是把代码完整读懂,不是看个大概,而是每个文件都扫一遍。从启动类出发,沿请求链路追一遍登录流程、上报流程、查询流程,搞清楚一个接口从URL到数据库返回结果的完整链路。
第二步是带着问题改代码。比如把权限从“三种角色固定”改成“可配置角色+菜单权限”,把统计看板从简单的count聚合改成用MySQL窗口函数做环比。改的过程会逼着你查资料、阅读源码,这才是最有效的学习。
第三步是补充项目文档。标题里【含文档】是加分项,但最好自己再写一份部署文档和使用说明。把数据库初始化脚本、环境配置步骤、接口说明整理好,对答辩、面试展示、后续交接都非常有用。
我个人在实际操作中的体会是,这类项目真正的价值不在于功能多新奇,而在于它是一张完整的地图,把SpringBoot、Vue3、MyBatis-Plus、MySQL这些知识点串成一条线。跟完一遍,你对前后端协作、环境部署、接口设计、数据模型的理解,绝对比单刷一百道面试题来得扎实。
如果想做成一个更有区分度的作品,还可以在这套骨架上加一些东西。比如导出Excel报表、用ECharts做更细致的数据可视化、引入Redis缓存热点数据、给健康上报加定时提醒任务。每一个扩展点都对应一类核心技术,选一个方向深入下去,这个项目就不再是练习品,而是能写进简历的实战作品了。