1. 项目概览与模块设计1.1 这个系统到底解决了什么问题先说个背景。疫苗预约这类系统并不是普通的CRUD它有几个很棘手的痛点一是高并发某个城市开放一批九价疫苗号源瞬间可能涌入几万人二是强一致性疫苗库存绝对不能超卖哪怕多扣一个号都是医疗事故级的问题三是流程状态复杂从疫苗入库、批次发布、用户预约、按时接种到留观登记每个环节状态都要可追溯。这套企业级疫苗发布和接种预约系统就是对着这些痛点来设计的。技术栈是SpringBootVueMyBatisMySQL后端负责疫苗批次管理、号源发放、预约订单处理和库存扣减前端提供用户预约界面和管理员操作台。整体架构很清晰适合用做毕业设计、企业内训项目或者作为中小型医疗机构的预约系统底座来二次开发。我自己在实际部署和二次开发这套系统的过程中把项目从数据库表结构到前后端联调完整过了一遍踩了不少坑。这篇博文不打算做那种代码贴上去就完事的讲解而是把我认为真正值得关注的设计思路、并发控制方案、以及部署过程中容易翻车的细节一条一条拆开说清楚。1.2 功能模块梳理从功能上看这套系统大致可以拆成六个模块用户认证模块基于Spring Security JWT 实现登录、注册、权限校验。普通用户、疫苗管理员、系统管理员三种角色。疫苗批次管理模块管理员录入疫苗批次信息疫苗名称、生产企业、批次号、有效期、库存量支持上下架操作。号源发布模块系统根据疫苗批次库存自动生成可预约号源支持按天、按时间段生成。在线预约模块用户浏览可预约的疫苗批次选择合适的日期时段提交预约申请系统实时扣减号源。接种管理模块用户到现场后工作人员核销预约码登记接种信息记录留观状态。数据统计模块按疫苗品类、时间段统计预约人数、接种完成率、爽约率等。1.3 数据流向与角色权限我用一种比较通俗的方式来描述各角色的操作路径普通用户注册登录 - 查看疫苗列表 - 选择批次 - 选择日期时段 - 提交预约 - 查看预约详情 - 到现场核销。疫苗管理员维护疫苗批次信息 - 发布号源 - 查看预约名单 - 核销预约码 - 登记接种记录。系统管理员管理用户账号 - 查看系统运行数据 - 配置基础参数。这里值得注意的一点是预约并非创建订单就直接锁定库存系统中用一个库存预占的概念来处理。用户在提交预约的瞬间系统先锁定一个号源用户在预约有效期内未到现场才算释放。我后面会专门讲这个机制实现的细节因为在本地复现项目时很多问题都是出在这一块。2. 技术选型与架构思路2.1 为什么是SpringBootVueMyBatis这套组合先说结论这套技术栈不是当前互联网大厂最潮流的方案——现在微服务、分布式事务、Go语言那一套确实很火——但它很可能是中小型团队做医疗类管理系统时最稳妥的选择。后端用SpringBoot的理由不用多讲生态成熟、上手快、内置Tomcat、自动配置强大。更重要的是国内企业级项目Java的存量太大了招人容易维护成本也低。项目里用到的Spring Security JWT组合基本上是Java后端的标准登录方案没有引入太偏门的东西。前端用Vue而不是React主要原因是Vue的学习曲线相对平缓模板语法对后端开发者很友好。这套系统前端用的是Vue 2.6.x版本配上Element UI。虽然Vue 3已经很成熟了但这个项目选型时考虑到Element UI的组件生态最全、文档最丰富直接照抄组件示例就能把管理后台搭起来对于追求快速交付的场景更实际。MyBatis相比JPA/Hibernate最大的优势是SQL可控性。预约系统里有大量带条件的动态查询查疫苗库存、查预约记录、查统计报表MyBatis的XML映射文件写起来非常直观而且那些复杂的报表SQL可以直接在XML里调试DBA接手也容易看懂。MySQL可以说是这套系统里争议最小的组件稳定、免费、资料多没有任何理由不用它。只要表索引设计合理、SQL不写烂支撑中小城市级别的疫苗预约流量完全没问题。2.2 前后端分离架构的利弊权衡这套项目是标准的前后端分离架构后端独立部署在8080端口前端通过Vue CLI启动在8081端口前端开发时通过代理转发请求到后端接口。前后端分离带来的最大好处是职责边界清晰。后端完全不用关心页面长什么样只管提供RESTful接口前端只管页面交互不用理解后端业务逻辑。这样分给不同的人并行开发非常高效也不容易互相阻塞。但前后端分离也有它让人头疼的地方我在实操中最大的体会是联调和调试的成本增加了。原来单体应用里改个后端接口刷新页面就能看见效果前后端分离后接口数据格式变了前端没跟上就会白屏或者报错一大片。这个项目里用了Swagger具体版本是springfox来生成接口文档有效缓解了这个问题。后端写完接口后用Swagger页面自测一遍前端照着文档对字段名联调效率明显上了一个台阶。我也强烈建议所有做前后端分离项目的团队接口文档工具必须安排上别靠口头传话对字段。2.3 MyBatis在实际项目中的配置与使用经验项目里MyBatis的配置有几个细节值得拿出来说说。第一个是开启驼峰映射。数据库字段一般用下划线命名create_timeJava实体类用驼峰命名createTime如果不开启驼峰映射查询出来的结果集的列名就没办法自动匹配到实体属性上。在application.yml中必须要加这一项配置mybatis: configuration: map-underscore-to-camel-case: true mapper-locations: classpath:mapper/*.xml type-aliases-package: com.example.vaccine.entity第二个是分页插件的引入。项目里用的是PageHelper这是我个人比较推荐的MyBatis分页方案用法很简单在查询前调用PageHelper.startPage(pageNum, pageSize)即可后面紧跟的查询语句会被自动拦截拼接上LIMIT语句。不过PageHelper有个非常经典的坑——内存分页问题。如果你在startPage之后执行了多条查询语句最后一条才会被分页而它实际上会把数据全部查出来再在内存里做分页处理数据量大的时候非常致命。正确用法是确保startPage后面紧跟的就是需要分页的那一条SQL中间不要夹任何其他数据库操作。第三个是MyBatis的缓存机制。项目里默认使用的是MyBatis自带的二级缓存但实际上我建议在业务开发中把二级缓存关掉尤其是像疫苗库存这种对数据一致性要求极高的场景。MyBatis默认的二级缓存粒度太粗容易出现脏读问题而且分布式环境下缓存同步也是个麻烦事。用Redis没问题但别用MyBatis自带的缓存。3. 数据库设计与核心表结构3.1 用户与角色相关表设计这套项目的数据库设计是比较标准的一套RBAC基于角色的访问控制模型。核心表包括sys_user、sys_role、sys_user_role三张。sys_user表主要字段有id、username、passwordBCrypt加密后的密文、real_name、id_card、phone、status、create_time。这里要说明一点身份证号字段在真实的医疗系统中是必要的接种需要实名但在演示环境里建议手工录入测试数据时不要用真实身份证用符合格式规则的假数据就好。sys_role表就很简单了id、role_name、role_code、description。角色编码在代码里做权限判断时会用到比如ROLE_USER、ROLE_ADMIN、ROLE_VACCINE_ADMIN。sys_user_role就是关联表字段只有user_id和role_id用来做多对多关联。3.2 疫苗批次与号源发布表设计这块是整个系统的核心业务表。我先列关键的三张表vaccine_batch疫苗批次表、vaccine_stock疫苗库存/号源表、vaccine_appointment预约订单表。vaccine_batch表的核心字段id主键vaccine_name疫苗名称比如九价人乳头瘤病毒疫苗manufacturer生产企业batch_no疫苗批次号production_date生产日期expire_date有效期至total_doses总剂次status状态1为上架可预约0为下架vaccine_stock表比较重要它记录的是某个批次在某一天的号源情况id主键batch_id关联vaccine_batch表stock_date号源日期slot_time时段比如09:00-10:00total_count该时段总号源数booked_count已预约数remaining_count剩余可预约数为什么要单独拆一张stock表而不是直接在batch表上加一个库存字段原因是同一个疫苗批次会在多个日期、多个时段放号如果直接在batch表上记录库存一个批次对应多天预约的记录根本无法维护。拆成stock表后一个批次对应多行stock记录每一行代表某一天某个时段的库存余量逻辑非常清晰。vaccine_appointment预约订单表是整个系统里最重要的表我下一节专门展开讲。3.3 并发场景下的库存控制设计预约系统的核心难点在于如何防止超卖。所谓超卖就是两个人同时看到库存还剩1个号同时提交预约结果系统给两个人都预约成功了。这种场景在实际运行中几乎必然会遇到尤其是热门疫苗放号的那一瞬间。这套项目采用的是数据库乐观锁方案。在vaccine_stock表中增加了一个version字段预约扣减库存时执行的SQL是这样写的UPDATE vaccine_stock SET booked_count booked_count 1, remaining_count remaining_count - 1, version version 1 WHERE id #{stockId} AND remaining_count 0 AND version #{oldVersion}这个SQL的核心在于UPDATE语句本身自带行锁。哪怕一万个用户同时发起预约请求数据库层面也会把针对同一行stock记录的UPDATE操作串行化执行。当remaining_count大于0时更新成功影响行数为1说明预约成功如果影响行数为0说明号源已经被抢完了。这里有一个容易忽略的细节使用乐观锁时后续的更新操作是否要强制校验version其实在这个场景中WHERE条件里的version#{oldVersion}是可选的。因为我们已经用了remaining_count 0这个条件来保证库存不为负加version只是为了在极端情况下防止ABA问题。我个人的建议是保留version校验因为将来如果预约资格支持了修改操作没有版本号很容易出问题。另外一个关键点是预约扣减库存和创建预约订单必须在同一个事务里执行。否则可能出现库存扣减成功但订单创建失败的情况用户明明抢到了号结果系统里查不到订单。项目里是通过在Service层方法上加Transactional注解来保证事务一致性的这个方法在后端实现部分我会再讲。3.4 索引设计建议从实际运行效率角度考虑项目建设时就在关键查询路径上设计了索引vaccine_appointment表的user_id字段加普通索引用于查询我预约的列表vaccine_appointment表的batch_id appointment_date字段加联合索引用于统计某批次每天的预约量vaccine_stock表的batch_id stock_date字段加唯一索引防止同一个批次在同一天重复投放号源sys_user表的username字段加唯一索引登录查询走索引这里我想多提一句别过度建索引。索引不是越多越好每个索引都会拖慢写入速度占用磁盘空间。像sys_user_role这种关联表数据量不大主键索引就够了不需要额外建任何索引。4. 后端核心功能实现4.1 疫苗发布流程与状态机设计疫苗发布不是一个简单的INSERT操作它是一个有状态的业务流转过程。这套系统把疫苗发布设计成了三个核心阶段草稿态管理员录入疫苗批次信息保存但不对外可见数据尚未校验完全。上架态批次检测合格管理员执行发布操作系统根据库存量自动生成未来N天的号源stock记录用户端能看到可预约的疫苗。下架态批次过期、库存清零或发现质量问题管理员下架该批次用户端不可见但已产生的预约记录不受影响。这里我想重点讲一下上架操作。发布批次时系统不是简单地把batch表的status改成1而是要做两件事第一件校验该批次的过期时间、库存数量是否合法第二件自动生成vaccine_stock表中的号源记录。生成号源时涉及一个参数设定可预约天数。项目里默认是发布当天起7天内可预约这意味着一个批次上架后系统会一次性为该批次生成7天乘以每天多个时段的多条stock记录。如果某天某个时段的号源没被约满那这批号源到期后就自动作废不会累积到下一天这样设计符合医疗资源管理的实际情况。状态流转这块一定要设计好。比如批次已经上架了管理员想修改库存数量系统必须先走下架操作修改完成后重新发布而不能直接在上架状态下改库存。我之前在做二次开发时偷懒没加这个限制结果就出现了用户正在预约的批次被管理员改库存导致预约页显示的剩余数量和实际库存对不上非常尴尬。4.2 预约接口的并发控制实现预约接口是这套系统里技术含量最高、也最容易出问题的地方。我先把这个流程完整拆解一遍用户从前端提交预约请求后端接收到的核心参数是stockId预约哪个时段和batchId预约哪个批次。接下来Service层依次执行校验用户是否登录、是否具备预约资格比如同一批次是否重复预约。查询vaccine_stock记录若stock记录不存在返回号源不存在。执行我前面写的UPDATE乐观锁SQL尝试扣减库存。如果更新行数为0抛出库存不足异常。库存扣减成功后创建vaccine_appointment预约记录生成唯一的预约码项目里用UUID加上随机数字生成。事务提交返回预约详情。这里有一个非常容易被忽略的细节MySQL默认的事务隔离级别是REPEATABLE READ可重复读。在这个级别下如果使用SELECT查询剩余库存然后再用update去更新中间就会因为快照读的问题导致判断失真。所以项目里采取的做法是不在事务里先SELECT判断再UPDATE而是直接执行UPDATE用UPDATE影响行数来判断是否成功。这是一种利用数据库原子操作的天然优势来避免并发问题的方式。还有一个细节值得提一下就是连接池的参数配置。项目里用了Druid连接池默认的maxActive是8这个数字在本地开发和测试环境够用但到生产环境一定要根据实际并发量调整。我当时在压测时发现接口性能上不去查了半天才发现是连接池被耗尽线程都在等待数据库连接。后来把maxActive调到了50minIdle保持5情况大幅改善。4.3 事务边界控制与分布式事务考量写到这里必须重点讲一讲事务边界。因为项目里预约和扣库存这两个操作必须原子化在一开始写的时候我把事务注解加错了位置导致一个经典问题——事务不生效。Transactional注解失效的场景比较多常见的包括注解加在private方法上Spring的CGLIB代理无法拦截到事务不生效。同一个类中A方法调用同类B方法B的Transactional注解不生效。异常被try-catch捕获后没有重新抛出事务感知不到异常不会回滚。这个项目里把事务注解加在了Service实现类的public方法上并且保证方法内部不做try-catch吞异常处理而是直接抛出运行时异常让Spring代理去处理这样才能确保扣减库存失败时预约订单能同步回滚。关于分布式事务我想多说一点。这种单体架构的项目用本地事务即可没必要上Seata之类的分布式事务框架。但如果后面要把预约系统拆分为独立的订单服务和库存服务就需要考虑消息队列加最终一致性的方案了。比如用RocketMQ或RabbitMQ发送预约成功消息库存服务消费消息扣减库存通过消息重试机制来保证最终一致性。不过在单体阶段用本地事务是最合适的选择不要过度设计。4.4 定时任务与通知模块项目里还设计了一个定时任务模块用来处理两类场景。第一类场景是每天凌晨系统会检查前一天的预约记录对未到场的用户做爽约标记并自动释放号源。这个定时任务用Spring自带的Scheduled注解即可实现。配置上注意让定时任务执行时间错开数据库备份时间凌晨2点到4点之间是比较合理的窗口。第二类场景是预约日前一天给用户推送接种提醒。推送渠道可以是短信也可以是小程序订阅消息。项目里封装了一个MessageService接口默认实现是打印日志模拟发送没有接入真实的短信服务商。对比来说接入阿里云短信或腾讯云短信的成本很低一个短信模板加一个SDK调用十几分钟就能接好真正开发时建议接上。还需要注意的一点是定时任务要加分布式锁防止多个实例部署时同一任务被重复执行。虽然这个项目是单体部署但如果后面用Nginx做负载均衡部署多节点Scheduled任务就会在每个节点上都执行一遍。项目里预留了一个基于数据库的唯一任务记录表来实现简单的分布式锁虽然简单但确实能起到效果。4.5 全局异常处理与参数校验这一块虽然不起眼但却是决定项目工程质量的关键。这套系统里定义了一个统一的Result对象所有接口返回的数据格式都是一致的code、message、data三个字段。前端拿到返回结果后统一判断code是否为200而不是每个接口单独处理不同的数据结构。全局异常处理用的是RestControllerAdvice ExceptionHandler的组合。这种方式的优势在于业务层随手抛出自定义业务异常框架统一捕获并转换成规范的JSON返回给前端。例如库存不足时抛出StockNotEnoughException前端就能优雅地弹出手速慢了号源已抢完的提示而不是看到500错误页面。参数校验方面项目引入了spring-boot-starter-validation在实体类上使用NotBlank、NotNull等注解配合Controller方法参数前的Validated注解完成参数校验。写了一段时间代码后你会发现这种声明式的校验比在业务代码里手动if判断要干净得多维护起来也清晰。5. 前端核心功能实现5.1 前端工程化配置与目录结构前端部分是标准的Vue CLI工程核心功能都集中在src目录下。我简单列一下目录结构src/api封装axios请求库src/routerVue Router路由配置src/storeVuex状态管理src/views页面组件src/components公共组件src/utils工具函数环境配置方面有一个关键点开发环境的跨域问题。前端跑在8081端口后端跑在8080端口浏览器直接发请求会跨域所以项目里在vue.config.js中配置了devServer代理module.exports { devServer: { port: 8081, proxy: { /api: { target: http://localhost:8080, changeOrigin: true } } } }这样前端请求/api开头的接口时开发服务器会把请求转发到后端8080端口不涉及跨域问题。但是这里有个机制上的注意点生产环境下前端打包后是静态文件如果部署在Nginx里也需要在Nginx配置一段类似的proxy_pass转发规则否则前后端接口是没法联通的。5.2 用户端预约页面的交互设计用户端预约流程是这套系统的门面交互设计直接决定了用户会不会用、用得顺不顺手。前端页面做了几个关键的交互细节预约入口页展示可预约的疫苗批次列表每张卡片包含疫苗名称、生产企业、剩余库存、有效期等信息。用户点击预约进入日期时段选择页。日期选择页用日历组件展示未来7天的可预约状态有号的日期显示可点约满的日期置灰不可点。点击某个日期后下方展示该日期下的时段列表每个时段下方展示剩余号源数。如果某个时段只剩个位数号源页面上会用醒目的颜色进行提示这算是一个人性化的设计能有效引导用户选择库存充足的时段。提交预约时需要二次确认弹窗展示预约的疫苗批次、接种时间、接种地点等信息用户确认后才真正发起预约请求。这个二次确认的设计至关重要。我之前见过不少系统用户误点一下就预约成功了取消操作又很麻烦体验非常差。加一个确认弹窗的时间成本极低但用户体验提升非常明显。预约成功后页面跳转到我的预约列表顶部会用醒目的卡片展示预约码方便用户到现场后出示给工作人员扫码。5.3 管理后台的统计看板管理后台的核心页面是数据统计看板主要负责向管理员展示今天有多少人来打疫苗以及疫苗库存消耗得怎么样这些信息。看板顶部是四个统计卡片今日预约人数、累计接种人数、可用疫苗批次数量、平均爽约率。统计卡片下面是一个按日期的柱状趋势图展示最近7天每日预约量和接种量。再往下是一张二维表格按疫苗名称和日期分组展示各批次疫苗每天的可预约总量、已预约量、接种完成量。这些图表用Element UI自带的统计组件加ECharts图表库实现ECharts的配置项非常丰富柱状图和饼图做数据可视化展示效果很好。管理后台的表格都会加上分页和条件筛选。分页工具用了我前面说的PageHelper前端配合el-pagination组件把当前页码和每页条数传给后端后端用PageHelper生成分页结果非常丝滑。一些需要注意的点管理后台操作建议配上操作日志记录比如谁在哪个时间发布了哪个批次、把哪个批次下架了。操作日志对排查问题很有价值一旦有人误操作改错了库存查到操作记录才能定位到责任人。项目里实现了一个简单的操作日志AOP切面在管理端的方法上加上OperationLog注解即可自动记录。5.4 前端路由与权限控制路由权限控制是前端比较容易踩坑的一块。项目里采用的方式是路由分为公共路由和权限路由公共路由包括登录页、疫苗列表页权限路由则包括管理后台、预约管理、接种管理。用户登录后后端会把该用户的角色编码返回给前端前端在Vue Router的beforeEach路由守卫中做判断。router.beforeEach((to, from, next) { const token localStorage.getItem(token) if (to.path /login) { next() return } if (!token) { next(/login) return } if (to.meta.roles !to.meta.roles.includes(store.state.user.role)) { next(/403) return } next() })为什么不让后端做页面跳转控制因为后端只能控制接口权限控制不了用户在浏览器里看到的页面。前端路由守卫可以防止用户通过手动输入URL跳转到管理后台页面但真正的数据安全还是靠后端接口的权限校验兜底。记住一句话前端权限控制只是为了用户体验后端接口权限才是安全基石。6. 项目部署与本地运行6.1 环境准备清单要把这套系统在本地跑起来需要准备以下环境JDK 1.8及以上推荐JDK 1.8Maven 3.6及以上MySQL 5.7或8.0Node.js 12及以上开发工具推荐IDEA VSCodeMySQL装完之后需要执行项目提供的SQL脚本把数据库和表结构初始化好。注意SQL脚本里包含了一个演示用管理员账户和一个测试用户登录密码都是经过BCrypt加密存储的初始密码一般是123456首次登录后建议立即修改。6.2 后端启动步骤与踩坑记录后端启动相对简单步骤是导入源码到IDEA - 修改application.yml中的数据库连接账号密码 - 运行Application主类即可。启动前需要注意两个配置项。第一个是数据库连接配置确保数据库名称、用户名、密码完全正确。第二个是Redis配置。这里我要特别强调一下项目中用Redis做JWT Token的缓存存储Redis版本没有特殊要求但服务器要能正常访问到Redis端口。如果本机没有安装Redis启动时后端会抛异常这个依赖绕不开。如果启动时发现端口被占用可以在application.yml里修改server.port配置。常见的8080端口被占用情况排查方式是用netstat -ano命令查看端口占用进程确认后杀掉或换端口。6.3 前端启动步骤前端启动同样不复杂先cd到前端项目目录执行npm install npm run servenpm install的时候通常会遇到网络问题。国内网络环境下直接用npm registry下载依赖速度慢还经常超时建议先配置淘宝镜像npm config set registry https://registry.npmmirror.com依赖安装成功后执行npm run serve启动开发服务器。Vue CLI会出一个本地访问地址默认是http://localhost:8081。打开浏览器输入这个地址就能看到登录页面了。用管理员账号登录后可以完整地走一遍发布疫苗批次 - 生成号源 - 用普通用户账号预约的完整流程。前端启动的时候还要检查一个关键点vue.config.js的代理配置是否指向了正确的后端地址。如果后端端口改了这边代理目标也要同步改。6.4 演示数据初始化建议初始化演示数据时有几点建议。不要用系统自带的初始化脚本里的简单数据就完事了一定要构造一批能覆盖业务场景的数据。比如创建一个包含多个批次、多个日期的库存数据既有约满的时段也有余量充足的时段这样才能看请预约页面的完整状态展示。另外建议在本地压测一下预约接口。写一个小脚本模拟并发请求发个几百个请求试试你会看到部分请求成功、部分请求返回库存不足这个现象就证明了乐观锁防超卖机制确实在正常工作。我实测下来200个并发请求针对10个号源进行预约只有10个成功190个被拒效果非常可靠。7. 常见问题与排查实录7.1 问题排查速查表在部署和二次开发这套系统的过程中我整理了以下高频问题及排查思路问题现象可能原因解决方案前端登录请求返回403代理配置target指向了错误的后端端口检查vue.config.js中的proxy.target后端启动报MySQL连接失败数据库账号密码错误或MySQL服务未启动核对application.yml配置确认MySQL服务已开启预约成功后查不到订单事务未生效或事务被吞异常检查Service方法是否有Transactional注解确认异常没有被try-catch吞掉库存100个预约出去了200个扣库存和创建订单不在同一事务中检查方法粒度是否覆盖了这两步操作定时任务重复执行多个实例部署未配置分布式锁引入基于数据库的分布式锁前端页面白屏代码有编译错误或路由配置问题打开浏览器控制台查看报错信息定位问题组件登录后跳转回登录页前端路由守卫拦截token校验失败检查localStorage的token是否存在核对token是否已过期7.2 一个印象深刻的Bug库存扣减与订单创建的数据一致性问题我在第一次启动项目做联调时遇到一个很有意思的问题用Postman连续发送预约请求成功返回的有40个但查看预约订单表发现只有39条记录库存却已经扣了40。这说明有的请求库存扣减成功了但订单创建失败而且事务没有回滚。排查过程很曲折。我先在Service方法上加了Transactional注解看似没问题了但继续压测还是偶发。后来我仔细看了事务配置发现项目里配置了Spring的事务管理器但Service方法的异常被内层代码给捕获掉了。代码里有一个地方在创建预约记录时如果数据库发生唯一索引冲突异常会被一个catch块捕获后返回一个默认值异常没有抛出去Spring自然感知不到事务需要回滚。找到原因后解决方式也很简单把catch块改成捕获异常后主动抛出运行时异常或者只捕获特定的可处理异常。这个场景也提醒了我一个重要的经验Transactional注解它的回滚机制依赖于异常向上传播代码里尽量不要吞异常否则事务保护就形同虚设。7.3 关于JWT Token过期与用户状态变更项目中还遇到一个问题用户因为违规被管理员禁用后已经发出的JWT Token仍然可以正常访问接口。这是因为JWT是无状态的后端无法在Token过期前主动失效它。解决方案是在Redis中存储Token的黑名单或者在用户鉴权Filter中每次请求都检查用户状态是否正常。项目里最终采取的做法是请求进来先校验Token签名再查询用户状态如果用户被禁用则直接返回401。这种方式虽然多了一次数据库查询但安全性更高我也建议在实际项目中保留这个逻辑。7.4 数据库连接池配置Druid的监控功能最后分享一个实用配置。项目引入的Druid连接池自带一个监控页面强烈建议把它打开用来观察数据库连接池的使用情况、SQL执行耗时和慢SQL统计。只需要在application.yml中配置spring: datasource: druid: stat-view-servlet: enabled: true login-username: admin login-password: admin123启动项目后访问http://localhost:8080/druid就能看到监控面板。我实际使用中发现最有用的是慢查询这个功能——如果一个接口响应特别慢去Druid监控页看一眼就能定位到是哪个SQL慢省去了很多排查时间。现在很多教程都不提Druid监控这个功能但我觉得这是Druid相比HikariCP最值得使用的理由。HikariCP性能确实优秀但Druid监控面板带来的调试便利性在开发阶段的价值是不可替代的。8. 真实项目落地的一些个人体会这款系统整体看下来给我的感觉是典型的麻雀虽小五脏俱全。学生或者刚接触企业级项目的开发者用它来理解SpringBoot Vue的完整开发链路会非常顺畅从零搭建环境到前后端联调那套流程走一遍基本就能把企业级Web开发的门路摸清了。对于做医疗系统相关的团队在这套系统基础上扩展在线支付、多接种点管理、消息推送这些功能工程成本也不会太高。其中我认为最值得反复揣摩的还是那一套并发控制的设计。做设计的时候先从定义清晰的数据库表入手到开发时用乐观锁UPDATE来实现库存扣减再到业务层用事务来保证一致性最后到了部署阶段在配置上考虑连接池和监控面板每一个环节都有它的道理它们凑在一起才是企业级这三个字真正的底气。很多人以为企业级无非就是代码写得规范、功能做得完善其实更重要的是对极端场景与数据一致性的理解。我自己在反复部署和实践这套系统之后最大的体会是不要迷信那些听起来很高级的中间件先把最核心的数据库事务和并发控制吃透比什么组件都管用。如果你也准备拿这套系统做二次开发我建议你先把数据库表设计理解透再把预约扣库存那几行SQL背下来最后再考虑往上加功能。地基打牢了楼才不会塌。