
做毕业设计选到“HPV疫苗预约系统”这个题目的同学最近几年是肉眼可见地多了。一方面是因为选题本身贴近现实生活疫苗预约、排队、库存这些场景大家都接触过需求容易讲清楚另一方面是这套系统用到的技术栈非常标准Java Spring Boot MySQL MyBatis Plus几乎就是企业级后端开发的入门标配用来应付毕业设计答辩和求职面试里的项目经历都拿得出手。但选题热门也意味着一个问题网上能查到的参考资料多而杂课程设计源码质量参差不齐很多人下载下来跑不起来或者跑起来了但答不上来“为什么这样设计”。所以这篇博文我不想只丢给你一段源码而是把整套系统的设计思路、核心模块、数据库表结构、业务逻辑实现以及我在实际开发和指导过程中踩过的坑全部拆开讲清楚。无论你是有Java基础想自己动手写还是已经有一份参考代码但看不懂这篇文章都能帮你把项目吃透。1. 选题逻辑与技术选型为什么这套系统适合做毕业设计1.1 从现实痛点出发需求分析不空洞HPV疫苗人乳头瘤病毒疫苗预约这件事前几年还是“线下排队、电话咨询、社区登记”为主。后来九价疫苗一针难求预约需求被彻底点燃很多地方开始用小程序、公众号做线上预约。但疫苗数量有限、到货时间不定、接种针次间隔要求严格这几点叠加起来就形成了一个非常适合做软件系统的业务场景。从毕业设计的角度来看这个选题最大的优势是需求边界清晰但功能点足够丰富。用户端要做注册登录、疫苗信息浏览、预约申请、预约记录查询、取消预约管理端要做疫苗品类管理、库存管理、预约审核、用户管理、数据统计。加上预约状态流转、库存扣减这些偏业务逻辑的东西放到论文里能写的东西很多程序里能展示的功能也不少评阅老师不会觉得工作量不够。1.2 Spring Boot Java 的组合为什么是当前的主流答案如果你去翻近两年计算机毕业设计的选题列表Spring Boot几乎成了后端项目的默认选项。原因并不复杂Spring Boot 简化了配置。相比传统的 SSM 框架Spring Spring MVC MyBatisSpring Boot 通过自动配置把大量 XML 配置变成了约定项目启动快、上手门槛低适合毕业设计这种几个月内要出成果的场景。生态成熟资料丰富。遇到问题搜索引擎一搜就有答案这对独立开发的学生来说非常重要。企业认可度高。现在很多中小型公司的后端技术栈就是 Spring Boot做毕业设计的同时相当于提前熟悉了工作环境答辩时老师问“为什么用这个框架”你也有更落地的回答。这套系统我建议采用前后端分离架构前端用 Vue 或 Thymeleaf 都行。如果是为了控制复杂度用 Spring Boot 自带的 Thymeleaf 模板引擎做服务端渲染完全够用如果想让项目看起来更“现代化”前端拆成 Vue 单页应用后端提供 RESTful API这种结构在答辩中更容易加分。后面我讲的接口设计同时适配这两种前端方式。2. 系统功能模块拆解预约平台的核心职责划分2.1 用户端功能预约链条上的每个环节都要有对应入口一套疫苗预约系统用户端的核心操作是“查疫苗、约疫苗、查记录、取消约”这个链条拆细了就是下面这些功能注册与登录。不能只做传统的用户名密码还应该支持手机号验证码登录因为疫苗预约通常和身份信息强绑定。建议在用户表里同时保留 username、phone、password 三个关键字段用手机号作为业务的唯一标识。密码存储必须用 BCrypt 加密答辩时评委经常会问密码安全问题你要能说出 BCrypt 和 MD5 的区别——MD5 是散列算法暴力破解成本低BCrypt 内置盐值并且计算速度可控适合密码存储。疫苗信息展示。前台首页要能按疫苗品类二价、四价、九价展示库存情况、适用年龄、接种程序几针、间隔多久、价格区间。注意库存信息不要直接查数据库里的实时库存数字给前端展示应该提供一个封装好的查询接口把“有货/缺货/预约中”这种状态计算好再返回避免前端拿到原始数字自己算。预约申请。这是最核心的功能。用户选择疫苗品类、接种地点、期望接种日期提交预约申请。这里有个关键判断系统到底做“立即确认”还是“待审核”考虑到疫苗库存需要管理人员线下核实大多数毕设版本都做成管理员审核制即用户提交申请后状态为“待审核”管理员审核通过后状态变为“已预约”用户到点去接种。预约记录查询与取消。用户能查到自己的预约历史每条记录明确展示状态。待审核状态允许取消已审核通过的预约如果要取消建议限制最晚取消时间比如接种日前一天24点前状态记录为“已取消”。2.2 管理端功能数据管理和状态流转是重心管理端的服务对象是平台运营人员功能设计上要围绕“管疫苗、管预约、管用户、看数据”四个方向。疫苗品类管理。管理员可以新增、编辑、上下架疫苗品类。上架时要填写疫苗名称、生产厂家、适用年龄段、接种针次、每针价格、库存总量、库存余量。预约审核管理。管理员查看所有预约申请可以按状态筛选。审核通过时系统自动扣减库存审核驳回时填写驳回原因用户可以收到提示。这是整个系统中业务逻辑最重的一个环节后面我会单独讲实现细节。用户管理。这里不只管用户的注册信息还要能根据用户ID或手机号查询其预约历史。一般还要提供一个简单的权限区分——普通用户和管理员通过角色字段区分避免把两套登录逻辑做成两套独立的表。数据统计。用图表展示每日预约量、各疫苗品类的预约占比、每月新增用户数。答辩时能演示一个简单的统计看板会让人觉得你的项目具备“完整业务闭环”而不仅仅是个CRUD。2.3 数据库表结构五张核心表怎么设计根据上面的功能拆解数据库层面最少需要这么几张表表名核心字段作用userid, username, password, phone, real_name, id_card, role, create_time存储用户与管理员信息vaccine_categoryid, name, manufacturer, applicable_age, doses, interval_days, price, stock_total, stock_left疫苗品类与库存vaccination_siteid, name, address, contact_phone, business_hours接种点信息appointmentid, user_id, vaccine_id, site_id, appointment_date, dose_number, status, reject_reason, create_time, update_time预约记录核心表appointment_recordid, appointment_id, vaccine_id, dose_number, vaccination_time, operator_id接种完成记录需要注意一个常见设计误区不要在 appointment 表里把所有针次混成一条记录。HPV疫苗通常打三针三针之间间隔固定比如四价和九价是0-2-6个月所以预约表里应该每一针是一条独立的 appointment 记录用 dose_number 字段区分是第几针再用一个 group_id 把同一次预约周期的三针关联起来。这样设计的好处是每一针的取消、改期、接种完成都能独立流转不会牵一发动全身。3. 预约流程的状态机设计与库存扣减并发控制3.1 预约状态流转把业务规则画成状态机这部分是答辩时最容易被追问的也是你在论文里最值得花笔墨写的内容。预约单的状态不能简单用一个 status 字段草草了事一定要定义清楚状态流转的规则。一套合理的状态设计是PENDING待审核用户提交预约后的初始状态此时不扣减库存。APPROVED已通过管理员审核通过此时系统扣减对应针次的库存。REJECTED已驳回管理员审核不通过需要填写驳回原因。CANCELLED已取消用户主动取消取消后如果是在 APPROVED 状态需要回补库存。COMPLETED已完成接种完成由管理员在后台标记同时写入接种记录。EXPIRED已过期超过预约日期未接种且未取消系统定时任务自动置为过期并回补库存。状态机的好处是它把散落在各个 Service 方法里的 if/else 集中起来用一张流转表控制。你在代码里可以定义一个枚举 AppointmentStatus再写一个状态校验方法例如private boolean canTransfer(AppointmentStatus from, AppointmentStatus to) { switch (from) { case PENDING: return to APPROVED || to REJECTED || to CANCELLED; case APPROVED: return to COMPLETED || to CANCELLED || to EXPIRED; case REJECTED: case CANCELLED: case COMPLETED: case EXPIRED: return false; default: return false; } }这样一套逻辑放在答辩PPT里画成图老师一眼就能看出你对业务流程有完整的理解而不是只会增删改查。3.2 库存扣减的并发控制防止“超卖”的经典解法疫苗库存扣减是这套系统的技术亮点。如果做的是静态演示库存扣减看起来很简单就是 update ... set stock_left stock_left - 1。但真实场景中多个用户同时点击预约如果不加控制库存可能被扣成负数这就是典型的“超卖”问题。我在指导这个项目时会让学生优先掌握两种解法的基本思路第一种是乐观锁。在 vaccine_category 表里加一个 version 字段更新库存时带上版本号校验Update(UPDATE vaccine_category SET stock_left stock_left - 1, version version 1 WHERE id #{vaccineId} AND stock_left 0 AND version #{version}) int deductStock(Param(vaccineId) Long vaccineId, Param(version) Integer version);执行结果返回值是 1 表示扣减成功是 0 表示库存不足或者版本冲突需要重试或者提示用户预约失败。这种方案实现简单不需要引入额外组件毕设场景完全够用。第二种是加分布式锁。用 Redis 的 SETNX 或 Redisson 的锁对疫苗ID加锁把“查库存—扣库存—创建预约单”这串操作包在锁里保证同一时刻只有一个线程在处理同一疫苗的预约。这种方案更贴近生产环境但需要引入 Redis 依赖项目复杂度会上升。我建议毕设用乐观锁就够了但在论文和答辩时把分布式锁的思路作为优化方向提出来展示你的知识面。评价一个毕设不光要看它实现了什么还要看你有没有思考过更优的解法。如果你不会 Redis还硬要集成进去一旦部署环境出问题反而是给自己挖坑。3.3 定时任务处理过期预约一种容易被忽略的必要功能手工取消、审核、接种完成这些都是用户或管理员触发的操作但“过期未接种”这个状态系统必须自己主动发现并处理。你需要一个定时任务每天或者每小时扫描一次 appointment 表把所有状态为 APPROVED、预约日期早于当前日期、且没有接种记录的数据批量置为 EXPIRED 并回补库存。Spring Boot 里实现定时任务很简单在启动类上加EnableScheduling然后在方法上标注Component public class AppointmentExpireTask { Scheduled(cron 0 0 1 * * ?) public void expireAppointments() { // 1. 查询 expiredTime now 且 status APPROVED 的预约 // 2. 逐条更新状态为 EXPIRED同时回补库存 } }这个功能虽然代码量不大但它是“系统完整性”的重要加分项——说明你想到了业务运行中的异常场景并做了兜底处理。我在实际答辩中看到很多学生根本没想到过期处理评审老师一问“用户预约了但没来怎么办”就开始支支吾吾很可惜。4. 核心代码结构与开发环境搭建要点4.1 项目分层Controller–Service–Mapper 的标准姿势这个项目建议严格按照三层架构来写不要图省事把业务逻辑全堆在 Controller 里。分层不只是为了好看它让你的代码可维护、可测试答辩时老师问你“一个请求从浏览器进来经过哪些层”你也能讲得清清楚楚。推荐的项目包结构com.example.hpv ├── controller # 接口层接收请求、返回结果 │ ├── AuthController.java │ ├── VaccineController.java │ └── AppointmentController.java ├── service # 业务层核心逻辑 │ ├── AppointmentService.java │ └── VaccineStockService.java ├── mapper # 数据访问层MyBatis Plus / MyBatis │ ├── UserMapper.java │ └── AppointmentMapper.java ├── entity # 实体类 ├── dto # 前端交互对象请求/响应体 ├── common # 通用返回结果、异常处理、枚举 └── config # 配置类拦截器、跨域、定时任务在写接口时注意请求参数和响应结果不要直接暴露数据库实体类设计统一的 Result 包装类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(success); result.setData(data); return result; } }这样做的好处是前端拿到的是一个统一格式的 JSON处理起来非常顺滑逻辑上也把“接口协议”和“数据模型”解耦了。4.2 开发环境与核心依赖版本选择很多人跑不起来项目问题往往出在环境版本不匹配。这个项目建议使用以下组合组件推荐版本备注JDK1.8 或 11不要一上来就上 JDK 17/21部分老依赖可能不兼容Spring Boot2.7.x稳定且资料多不要用 3.x 尝鲜MySQL5.7 或 8.08.0 需要注意驱动名和时区配置MyBatis Plus3.5.x简化单表 CRUDMaven3.6依赖管理Lombok最新即可减少冗余代码但注意 IDE 要装插件在application.yml中有几个配置极其容易踩坑。第一个是 MySQL 8.0 的驱动要写成com.mysql.cj.jdbc.Driver老版本的com.mysql.jdbc.Driver会报警告甚至直接报错。第二个是时区设置连接 URL 上建议加serverTimezoneAsia/Shanghai否则服务器时间和数据库时间对不上预约日期会显示错误。第三个是 Jackson 的时间格式化要在配置里固定好否则前后端传日期时会出现各种不明所以的异常spring: datasource: url: jdbc:mysql://localhost:3306/hpv_vaccine?useUnicodetruecharacterEncodingutf8serverTimezoneAsia/ShanghaiuseSSLfalse username: root password: your_password driver-class-name: com.mysql.cj.jdbc.Driver jackson: date-format: yyyy-MM-dd HH:mm:ss time-zone: Asia/Shanghai4.3 接口设计示例预约提交与审核确认我把预约提交的核心接口逻辑列一下这个逻辑串联了库存校验、状态创建和事务管理PostMapping(/appointment) Transactional(rollbackFor Exception.class) public Result? createAppointment(RequestBody AppointmentCreateDTO dto) { // 1. 校验用户是否存在且状态正常 // 2. 校验疫苗是否存在且已上架 // 3. 调用 stockService.deductStock(vaccineId) 尝试扣减库存 boolean deducted stockService.deductStock(dto.getVaccineId()); if (!deducted) { return Result.error(库存不足预约失败); } // 4. 创建预约记录状态为 PENDING // 5. 如果第4步抛异常第3步的回滚会被 Transactional 捕获 return Result.success(appointment); }这里的一个关键点是如果采用“提交预约即扣库存”的策略那么用户取消时一定要回补库存如果采用“审核通过才扣库存”那么待审核状态下库存不变化但需要防止大量待审核预约把疫苗占满。我建议毕设采用“提交预约时扣减库存取消或驳回时回补库存”逻辑最直观。因为管理员审核通常是很快的这种策略可以让用户提交时立即看到是否约满体验更好。你只要在取消、驳回、过期三个节点都记得回补库存就不会出现库存对不上的情况。5. 开发过程中的常见报错与排查经验5.1 前端联调时的跨域问题前后端分离开发时最常碰到的就是跨域。后端运行在 8080前端 Vite 运行在 5173端口不同浏览器默认会拦截跨域请求。解决方案在 Spring Boot 里加一个配置类Configuration public class CorsConfig implements WebMvcConfigurer { Override public void addCorsMappings(CorsRegistry registry) { registry.addMapping(/**) .allowedOriginPatterns(*) .allowedMethods(GET, POST, PUT, DELETE, OPTIONS) .allowCredentials(true) .maxAge(3600); } }注意allowCredentials(true)时不能再用allowedOrigins(*)要改成allowedOriginPatterns(*)否则报错。这个细节很多教程里不会写。5.2 MyBatis Plus 的字段映射坑驼峰与下划线如果数据库字段用的是下划线命名比如create_time实体类字段用驼峰createTime必须在配置中开启下划线转驼峰mybatis-plus: configuration: map-underscore-to-camel-case: trueMyBatis Plus 3.x 默认是开启的但如果你同时混用了原生 MyBatis 的 XML 映射要注意 XML 里的resultMap是否手动指定了字段映射否则查出来的对象某些字段会是 null。这个问题的排查方式很简单在测试类里写一个查询打印整个实体对象一眼就能看出哪个字段丢了。5.3 预约日期必须做后端校验前端表单里让用户选预约日期如果只在前端做校验用户直接传一个昨天甚至去年的日期给后端系统也照样接受这就成了业务规则的严重漏洞。后端在创建预约时必须校验// 预约日期必须是今天之后的日期 if (dto.getAppointmentDate().isBefore(LocalDate.now())) { return Result.error(预约日期不能早于今天); } // 可选限制只能提前7天预约 if (dto.getAppointmentDate().isAfter(LocalDate.now().plusDays(7))) { return Result.error(最多只能提前7天预约); }这是我见到最多学生忽略的一点。评委可能不会手工去测这个漏洞但你如果主动把这段校验放在代码里答辩的时候提一句“我对预约日期做了后端二次校验防止非法请求直接打到接口上”立刻能体现你的安全意识。5.4 验证码登录的实现细节如果做了手机号验证码登录特别注意验证码一定不能明文存在数据库里应该用 Redis 存储并设置过期时间或者存在内存缓存中加过期策略。毕设场景如果不想引入 Redis可以用一个 ConcurrentHashMap 模拟key 为手机号value 为验证码和过期时间的组合对象配合定时清理。这里我更推荐直接上 Redis一是 Redis 本身在简历上是加分项二是代码量并不多stringRedisTemplate.opsForValue().set(sms:code: phone, code, 5, TimeUnit.MINUTES);校验时取出对比匹配后立刻删除防止同一个验证码被重复使用。6. 如何把毕设做出差异化答辩亮点与优化方向建议6.1 在CRUD之上加入一个“业务闭环”设计绝大部分学生的毕设都停留在“接口调通、数据能增删改查”这个层面而优秀的毕设应该展示一个完整的业务闭环。放到 HPV 疫苗预约系统里这个闭环就是用户注册 → 查看疫苗库存 → 提交预约 → 管理员审核 → 审核通过/驳回 → 用户接种 → 管理员标记完成 → 数据统计展示你要让答辩老师看到你设计的不是一个孤立的“增加预约接口”功能而是一整套以预约为核心向外串联了用户管理、库存管理、统计分析的业务体系。完成这个闭环的关键就是前面说的预约状态机加定时任务加库存控制三点缺一不可。6.2 可视化统计的可选实现给管理后台加一个统计页面用 ECharts 展示“近30天预约趋势图”和“各疫苗品类预约占比饼图”。后端不需要引入额外组件写一个统计查询接口按日期分组返回数据GetMapping(/stats/appointment-trend) public Result? appointmentTrend(RequestParam Integer days) { // 按 appointment_date 分组统计返回 [{date: 2024-12-01, count: 23}, ...] }这个功能实现简单但视觉效果非常好能让答辩PPT一下子生动起来。我见过很多学生在这个页面上花了不到一天却换来了很不错的评价。6.3 更进一步的优化方向如果学有余力如果你的基础不错想在毕业设计中展示更强的能力可以在以下方向中选一个做深引入 Redis 缓存疫苗库存信息降低数据库压力同时用一个预热任务在项目启动时把库存加载到缓存中。消息队列异步化通知用户预约成功或审核通过后通过 RabbitMQ 发送消息异步执行短信或邮件通知。加入预约名额限制比如每个接种点每天最多放号50个预约时校验当天已达上限。这个功能虽然逻辑不复杂但它在现实业务中非常重要可以体现你对领域业务的理解。单元测试至少为库存扣减和预约状态流转写几个 JUnit 测试用例。绝大多数毕设是没有测试代码的在项目里看到测试方法会给老师留下非常严谨的印象。6.4 做这套系统时我个人最推荐的一条路径最后说说我自己带学生做类似项目时最推荐的一个节奏。先不要急着写代码用一到两天的时间把需求分析文档和数据库表结构设计出来画好状态机图和用例图然后从后端开始写先打通用户注册登录再写疫苗品类的管理接口最后集中精力写预约模块因为那是整个系统的灵魂。预约模块写完之后再做管理端的审核功能补上定时任务最后再做前端页面和统计图表。这个顺序的好处是每一步都可以独立验证不会出现“写到最后发现前面思路全错”的尴尬局面。如果你已经有一份现成的参考项目也不要直接拿去做些换皮操作就交差——至少要亲手把预约状态流转和库存扣减这两个核心方法重新写一遍或者用自己的理解重构一下关键数据表字段。因为在答辩现场老师最常问的就是“库存怎么防止超卖”“预约状态是怎么流转的”“如果用户取消预约会发生什么”你亲手敲过这些代码回答时才会心里有底而不是万一被问到细节就卡壳。做这套系统最大的收获不是拿一个分数而是通过一个完整的项目把所有学过的知识点串起来。从前端页面到后端接口从数据库设计到并发控制从定时任务到异常处理每个大学里学过的概念都能在这个项目里找到对应的位置。能把这个项目从头到尾吃透你的 Java 后端能力就已经远远超过普通应届生水平了——这也是它值得你花一两个月认真对待的原因。