ARTICLE DETAIL

资讯详情

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

SpringBoot+Vue+MySQL疫苗预约系统开发全流程解析

SpringBoot+Vue+MySQL疫苗预约系统开发全流程解析 去年带学生做毕设我接触最多的就是各种“预约类系统”。今天挑一个出现频率特别高的题目来聊聊——疫苗发布和接种预约系统技术栈就是经典的SpringBoot Vue MySQL。这个选题的好处在于业务链路完整、数据关系清晰、前后端交互点足够多作为毕业设计既能体现工作量又不会超出一个人可以独立完成的范围。这篇文章我会把整个项目从需求拆解、数据库设计、后端并发处理、前端交互到最后的部署论文完整梳理一遍打算做类似管理系统的同学可以直接拿去参考。1. 第一件事不是写代码而是把业务需求拆解清楚很多同学拿到这个题目第一反应是“疫苗、预约、系统”然后就开始建表写接口结果做到一半发现管理端和用户端边界不清预约状态乱成一团。我建议先从真实使用场景入手把这个系统到底给谁用、解决什么问题写明白。1.1 业务场景从线下登记到线上预约要完成哪些事疫苗管理的线下流程通常是这样接种点提前发布疫苗到货信息居民到现场排队登记、领取号源、接种、留观。这个过程的问题很明显——信息不透明居民不知道有没有货、要不要排队接种点手工登记容易出错后续也很难统计接种率。换成线上系统之后核心业务就变成了几条发布端管理员维护疫苗品种、库存数量、适用人群、禁忌说明发布接种公告查看和管理所有预约记录。预约端居民用户注册登录、查看疫苗公告和库存、选择日期和时间段提交预约、查看自己的预约历史、必要时取消预约。数据关联一次预约必须绑定一个用户、一个疫苗批次、一个接种点和一段时间段这些关系最后都要落到数据库表结构上。这个系统的边界其实就是信息发布 预约登记 记录管理不需要做支付不需要做复杂的排班调度更不需要对接医院内部的业务系统。把边界框住后面设计才不会跑偏。1.2 角色拆分两类登录端和四条功能主线系统分两个角色普通用户和后台管理员。用户端是面向接种者的功能偏向查询和预约管理端是面向运营人员的功能偏向数据和内容维护。我画过一张功能清单大致是这样模块用户端管理端账号注册、登录、密码重置管理员登录、个人信息维护公告查看疫苗公告列表、详情发布、编辑、删除公告疫苗查看疫苗信息、库存状态新增疫苗、维护库存、上下架预约提交预约、取消预约、查看记录查看全部预约、按状态筛选、导出统计数据无按周/月统计接种量生成图表功能主线就是这四条账号认证、公告发布与展示、疫苗信息管理、预约闭环。预约闭环是最核心的一条线它涉及“提交预约—审核/确认—完成接种—取消/爽约”的状态流转数据库设计和接口设计都要围绕它来展开。提示毕设答辩时老师很容易问“你的系统对比线下流程到底优化了哪个环节”。提前想清楚这个问题回答的时候底气足很多。2. 技术选型不是越新越好而是越稳越好现在网上技术栈更新很快但毕业设计追求的应该是自己完全能驾驭、生态成熟、资料好找的组合。SpringBoot Vue MySQL这套组合恰好满足后端不用写一堆笨重的配置前端组件库丰富MySQL作为关系型数据库对预约这类强一致性场景天然合适。2.1 后端为什么是SpringBoot而不是SSM这个题目如果是五年前做可能还会用SSMSpring SpringMVC MyBatis手写XML配置。现在再用SSM光是配置就够折腾而且和Vue联调时CORS跨域、JSON序列化这些问题都要手工处理。SpringBoot最大的优势是自动化配置 内置Tomcat 一站式起步依赖一个main方法就能跑起来绝大部分配置用application.yml就能解决。后端项目里我用的核心依赖大致是这样Spring Boot 2.7.x稳定资料最多和JDK 8/11都能配合。MyBatis-Plus比原生MyBatis省事分页插件、自动填充、逻辑删除都内置好了。Spring Security或Sa-Token负责登录认证和接口权限控制。Lombok减少实体类的getter/setter代码让代码更清爽。Hutool工具库生成验证码、时间工具、随机数都有现成方法。很多同学纠结要不要用最新版Spring Boot 3.x我建议毕业设计优先用2.7.x。3.x要求JDK 17而且部分starter和依赖的兼容性问题需要额外排查没必要在版本上给自己挖坑。2.2 前端选Vue列表、表单、路由这一套是真的顺手Vue适合这种管理系统的原因很直观页面本质上就是“表格 表单 详情”的排列组合Vue的数据双向绑定让表单交互变得非常简单组件化开发把用户端和管理端的公共部分比如日期选择器、状态标签、分页组件拆得很干净。前端这边我的标配是Vue 2 Element UI经典搭配毕业设计资料最多遇到问题搜索基本都有答案。Vue 3 Element Plus如果导师对版本有要求用这个组合也可以核心写法和Vue 2差异不大。Vue Router负责路由跳转和登录守卫。Axios统一处理HTTP请求、携带Token、拦截错误状态码。个人建议如果你的Vue基础一般优先选Vue 2 Element UI网上模板和教程量级完全不是一个档次如果已经从Vue 3开始学了就用Vue 3 Element Plus注意组件引入方式的变化就行。2.3 数据库MySQL和缓存Redis的边界问题有些同学会在设计里加Redis理由是缓存公告、缓存预约计数。我的看法是毕业设计可以不用RedisMySQL完全扛得住这个量级。疫苗预约的实际并发量并不高数据库层面做好事务和锁性能就足够了。硬加Redis反而会引入缓存一致性、过期策略这些额外问题答辩时老师一追问问深了就容易被卡住。如果实在想体现技术含量可以把Redis作为加分项放在论文的“系统优化”里说明它在高并发场景下可以做什么但主体实现仍然用MySQL。这样既稳妥又有亮点。2.4 ORM和权限框架怎么配合ORM我推荐MyBatis-Plus理由只有一个字——省。单表查询不需要写SQLselectById、selectPage直接调用多表查询用TableField、TableName注解维护映射关系。预约记录和疫苗信息、用户信息关联查询时手写一条自定义SQL配合Page分页即可。权限控制不推荐自己写Session判断。用Spring Security太重可以退一步用Sa-Token或者JWT 拦截器。JWT的方案最简单登录成功后生成Token前端请求头携带后端写一个拦截器校验有效性和角色完全没有Spring Security那套繁琐的过滤器链配置。3. 数据库设计是最花时间的环节状态机尤其重要这个系统的数据库设计虽然表不多但关系和质量决定后面所有代码的复杂度。我自己做的时候光预约表的状态字段就改了三次版本。3.1 核心表结构和字段设计我最终落地的表如下用户表(user)字段类型说明idbigint主键自增usernamevarchar(50)登录账号唯一索引passwordvarchar(100)BCrypt加密后的密码real_namevarchar(20)真实姓名id_cardvarchar(18)身份证号疫苗登记需要phonevarchar(11)手机号ageint年龄部分疫苗有年龄限制gendertinyint性别addressvarchar(255)居住地址create_timedatetime注册时间疫苗信息表(vaccine)字段类型说明idbigint主键vaccine_namevarchar(50)疫苗名称manufacturervarchar(50)生产企业vaccine_typevarchar(20)疫苗类型stockint可预约库存applicable_groupvarchar(255)适用人群说明contraindicationsvarchar(500)禁忌说明dosage_infovarchar(255)接种剂次说明statustinyint0下架 1上架create_timedatetime录入时间接种点表(site)id、site_name、address、open_time、close_time。公告表(notice)id、title、content、status、create_time。预约表(appointment) id、user_id、vaccine_id、site_id、appoint_date、time_slot、status、cancel_reason、vaccinated_time、create_time、update_time。提示疫苗的stock字段可以直接当成“剩余号源数”使用。每次预约成功减一取消预约加一。简单直接适合毕设。3.2 预约状态流转决定接口逻辑的地基预约状态是这种系统的灵魂。我的设计是四个状态0 待接种预约成功后进入1 已完成管理员标记为已接种2 已取消用户主动取消或管理员取消3 已爽约超过预约日期未接种自动进入状态流转的逻辑要配合接口思路才能写对。用户提交预约后创建一条待接种记录取消操作只能作用于待接种状态管理员完成接种后状态改为已完成同时把疫苗库存减一系统启动时扫描待接种且appoint_date小于当前日期的记录统一更新为已爽约。这个状态机看起来简单但写代码时非常容易漏掉状态校验。比如用户反复点击取消按钮如果没有状态判断就会把已经取消的记录再取消一次库存加两次、减一次数据就乱了。3.3 库存扣减的两种方案对比第一种是疫苗表stock字段直接扣减。最简单查询当前疫苗剩余量就是一条SQL的事。风险是并发下可能出现超卖用户查到的库存是5同一时间100个人预约最后实际数量对不上。第二种是预约成功后再扣库存也就是预约记录提交时只做校验等管理员确认“完成接种”后才减少stock。好处是预约阶段不会扣错数坏处是库存显示的就不是“可预约量”而是“剩余总库存”中间会有一批预约成功但没完成的记录占着量。我最终用的是折中方案stock表示剩余可选号源提交预约事务内执行update vaccine set stock stock - 1 where id ? and stock 0受影响行数为0说明已经约满。这样展示出来的数字就是真实可预约量管理员完成接种时不再重复扣减只更新状态。4. 后端核心逻辑预约接口的并发防线和权限链路后端接口如果按功能模块拆大概有20个左右但最值得细讲的就一个提交预约接口。这个接口涉及身份校验、参数校验、业务校验、数据一致性四个层次写好了整个项目的水平就体现出来了。4.1 提交预约的完整校验链路我推荐按下面顺序写校验逻辑顺序错了会出现各种奇怪问题身份认证通过Token解析出当前用户ID校验用户是否存在且未被禁用。参数基础校验疫苗ID、接种点ID、预约日期、时间段不能为空日期不能是过去时间。业务状态校验疫苗必须处于上架状态用户不能同时存在一条待接种的同疫苗预约记录防止重复预约。库存校验与扣减执行update ... where stock 0失败返回“当前疫苗库存不足”。创建预约记录插入一条状态为“待接种”的预约记录。事务包裹步骤4和5必须在同一个Transactional中要么都成功要么都回滚。很多同学容易漏掉的是第3步的重复预约判断。如果不做用户可以无限提交同一种疫苗的预约后台管理根本没法看。解决办法很简单在预约表上加联合唯一索引ALTER TABLE appointment ADD UNIQUE KEY uk_user_vaccine_date (user_id, vaccine_id, appoint_date);数据库层面做了兜底即使代码漏判也不会产生重复数据。4.2 并发防超卖乐观锁 受影响行数判断就够了疫苗预约这种场景不是秒杀并发量有限不需要引入分布式锁。我用的是数据库条件更新 受影响行数判断的模式Transactional public boolean submitAppointment(AppointmentRequest req) { // 1. 业务校验略 // 2. 条件更新库存足够才扣 int affected vaccineMapper.deductStock(req.getVaccineId()); if (affected 0) { throw new BizException(疫苗库存不足); } // 3. 创建预约记录 appointmentMapper.insert(appointment); return true; }对应的SQLUPDATE vaccine SET stock stock - 1 WHERE id #{vaccineId} AND stock 0;这个方案的核心是stock 0条件 MySQL行锁。同一时刻两个请求同时执行后执行的会等待前一个事务提交然后因为库存减掉了而更新0行直接判定预约失败。这个机制在InnoDB引擎下是可靠的。注意deductStock执行完不能立刻return false要抛异常让事务回滚否则库存日志会对不上。4.3 用户端与管理端接口的规划清单后端接口规划我建议按模块列出来这样论文的接口设计和前端联调都有依据用户端POST /api/user/register注册POST /api/user/login登录GET /api/vaccine/list疫苗列表GET /api/vaccine/{id}疫苗详情GET /api/notice/list公告列表POST /api/appointment/submit提交预约GET /api/appointment/my我的预约PUT /api/appointment/cancel/{id}取消预约管理端POST /api/admin/login管理员登录GET /api/admin/vaccine/page疫苗分页POST /api/admin/vaccine/save新增/编辑疫苗DELETE /api/admin/vaccine/{id}删除疫苗GET /api/admin/appointment/page预约分页查询PUT /api/admin/appointment/complete/{id}标记完成GET /api/admin/statistics/weekly每周接种统计管理端的DELETE不建议物理删除用MyBatis-Plus的逻辑删除注解TableLogic界面上“删除”背后其实是deleted1这样历史预约记录仍然可以查询。4.4 统一返回体和全局异常处理要早做后端返回给前端的格式不统一前端联调会非常痛苦。建议用统一结构{ code: 200, message: 操作成功, data: { } }定义一个ResultT泛型类所有Controller都返回它。配合RestControllerAdvice做全局异常处理业务异常返回400带具体提示参数异常返回500带校验信息这样前端只要认准一种结构就完事了。经验毕设答辩演示时输入错误数据会暴露出后端给的是500还是400。提前统一异常处理演示过程会顺很多。5. 前端实现细节从首页公告到预约表单的交互闭环前端页面数量不算少但结构非常清晰。用户端一套页面管理端一套页面通过路由区分。我把做的时候觉得最值得说的几个点拎出来。5.1 前端工程结构和路由权限设计Vue项目用vue-cli创建后主要目录结构是src/ ├── api/ # 接口请求封装 │ ├── user.js │ ├── vaccine.js │ ├── appointment.js │ └── notice.js ├── router/ # 路由配置 ├── views/ # 页面组件 │ ├── user/ # 用户端页面 │ └── admin/ # 管理端页面 ├── components/ # 公共组件 ├── utils/request.js # axios实例封装 └── App.vue路由配置时我会加两个元信息字段{ path: /admin, component: AdminLayout, meta: { role: admin }, // 需要管理员角色 children: [...] }配合全局前置守卫router.beforeEach((to, from, next) { const token localStorage.getItem(token); if (to.meta.role) { const role localStorage.getItem(role); if (token role admin) next(); else next(/login); } else { if (to.meta.requiresAuth !token) next(/login); else next(); } });这样用户直接输入/admin地址也进不去安全性和答辩印象分都能提升。5.2 用户端关键页面疫苗列表和预约表单是最重要的疫苗列表用的el-tableel-pagination加载接口拿分页数据。每行放一个“预约”按钮点击弹出el-dialog。这里交互细节很多我自己写的时候踩了几个坑疫苗库存为0或者下架状态按钮要disabled并显示“已约满”。预约日期用el-date-picker设置:disabled-date只允许选择未来7天内日期避免用户选历史日期。时间段用el-radio-group上午/下午两个选项也可以加“具体时间点”比如09:00-10:00这种更细的维度。弹窗打开时展示疫苗的禁忌说明减少用户误预约。提交预约后不要立刻跳转弹一个成功提示把预约编号展示出来再让用户选择“查看我的预约”或“继续浏览”。这一步对体验影响很大很多毕设就是把按钮一点了事用户完全不知道预约成功没有。5.3 axios拦截器和Token过期处理前端所有请求都走同一个axios实例。我会在request.js里面做三件事service.interceptors.request.use(config { const token localStorage.getItem(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.clear(); router.push(/login); return Promise.reject(res); } return Promise.reject(res); }, error { Message.error(error.response.data.message || 请求异常); return Promise.reject(error); } );401统一跳转登录页这个细节非常关键。Token 30分钟过期后如果每个接口都各自弹错体验会稀碎统一在拦截器里处理用户无感知重新登录就行。5.4 管理端页面表格筛选、弹窗编辑和统计图表管理端的疫苗管理和预约管理本质是两个“大表格”核心是筛选条件。预约管理我做了三列筛选状态下拉框、日期范围、用户姓名模糊查询。配合MyBatis-Plus的QueryWrapper动态拼条件代码也不复杂。疫苗新增/编辑用el-dialog嵌套el-form字段多的时候我建议把表单拆成“基本信息”和“接种说明”两个分组视觉上清爽很多。表单校验规则用Element UI自带的rules重点字段加required疫苗库存必须是非负整数。统计图表我用的ECharts后端返回每周接种量数组前端用柱状图渲染。表格图表同时展示论文的截图素材也就有了答辩时展示效果也好看。6. 从能跑代码到顺利毕业部署、论文和答辩的准备6.1 部署前必须改的三个配置项本地能跑不等于部署时能跑我在帮学生准备部署文档的时候最常见的三个问题排序如下数据库连接配置。application.yml里的JDBC地址、账号密码要改成目标环境的。MySQL 8.0以上的驱动URL里要加时区参数url: jdbc:mysql://localhost:3306/vaccine_db?useSSLfalseserverTimezoneAsia/ShanghaicharacterEncodingutf8前端打包路径。前端npm run build之后生成dist目录里面是静态文件。两种部署方式一是把dist文件直接扔到SpringBoot的resources/static下一起打成jar包最省事二是用Nginx托管前端Backend单独跑需要配置跨域。毕设演示优先用第一种。CORS跨域配置。如果前后端分开跑开发模式Spring Boot要开跨域Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOrigins(http://localhost:8080) .allowedMethods(*) .allowedHeaders(*); } }6.2 论文写作里最容易写空的功能测试模块论文里最容易被答辩老师挑刺的就是“系统测试”这一章。很多同学写“经测试系统运行良好”一句话带过老师一看就觉得没做工作。我建议至少写清三层功能测试用例表每个核心模块至少3条用例包含用例编号、操作步骤、预期结果、实际结果、是否通过。登录模块示例正确账号登录成功、错误密码提示、未登录访问受保护接口被拦截。预约模块重点测试库存不足时预约失败、重复预约被阻止、取消后库存回补、并发下库存不超卖。用例表不要编数据写完代码后花一个小时从页面和接口两个层面跑一遍把真实结果录进去。老师问细节时才答得上来。6.3 答辩高频问题与作答思路疫苗预约系统这个题老师大概率会问三类问题“库存怎么防止超卖”回答思路预约接口在事务内执行条件更新SQLstock 0才扣减库存利用数据库行锁保证同一疫苗的扣减串行化再加联合唯一索引防止同一用户重复预约。“用户的Token有效期是多久过期了怎么办”回答思路JWT设置30分钟有效期前端axios响应拦截器收到401后清除本地登录态并跳转登录页用户重新登录后可继续操作。“如果预约人数突然暴增你的系统怎么应对”回答思路当前MySQL条件更新的方案在低并发下完全可靠如果并发量提升可以考虑引入Redis预扣减库存、消息队列异步落库这是后续优化方向。这个问题答到“知道怎么优化”就足够了不用真写出来。个人体会这套系统做下来我最大的感受是毕设的价值不在于技术多新而在于把一条完整的业务链路做通、做稳。从数据库设计到接口规划从前端交互到部署文档每一个环节之间都有因果关系把这种关系理清楚答辩和论文都是一马平川。最后分享一个小技巧预约强调的“截止时间”——后台可以写一个定时任务每天凌晨扫描一次所有“待接种”且预约日期已过的记录统一改成“已爽约”。这个小功能代码量不大但在老师和评委眼里属于“考虑到了实际业务流程”的加分点写论文的时候也非常好讲。如果你也正在做类似的管理系统按我上面这套思路去拆解自己的题目把核心状态机画对把并发的坑堵住把联调和部署跑通这个项目基本就稳了。
返回列表