ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

微信小程序疫苗预约系统实战:号源并发控制与状态机设计

微信小程序疫苗预约系统实战:号源并发控制与状态机设计 简介这是一套面向Java后端与微信小程序全栈开发者的疫苗预约接种系统实战源码适用于课程设计、毕业设计及医疗类小程序项目参考。系统采用Spring Boot Vue 微信小程序技术栈覆盖管理员与接种者双角色全流程业务包括接种点管理、预约计划调度、疫苗信息维护、支付模拟、二维码签到及多维度历史查询等核心功能。压缩包共644个文件含86个Java后端逻辑文件、115个Vue前端组件、119个HTML页面、86个XML配置文件及75个GIF动效资源整体3.56MB结构清晰、模块解耦便于二次开发与教学演示。已有1054人学习下载配套application.properties数据库配置说明与MySQL初始化SQL脚本开箱即用内容预览显示包含Layui、Layer等主流前端UI库及完整登录/管理/预约模块样式体系具备真实生产环境适配能力。1. 疫苗预约项目的需求解剖这系统不是简单做个表单1.1 疫苗预约和普通挂号的本质区别做疫苗预约接种系统之前我一度以为这跟做学校预约、体检预约没什么区别无非就是用户选一个时间、提交表单、后台记一条数据。真正开始梳理需求才发现疫苗预约比普通预约复杂得多核心区别在于两件事稀缺号源的并发分配以及多针次疫苗的接种进度跟踪。普通挂号医生每天看几十个号你抢不到可以换一个医生资源是冗余的。疫苗不一样很多疫苗比如九价HPV、流感疫苗到货数量有限每个接种点放出的号源是固定的抢完就没了。而且像HPV九价这种疫苗要打三针第二针、第三针的时间间隔有严格限制系统必须能记录用户已经打到第几针避免提前或延后接种。更别说儿童疫苗里还有一类苗和二类苗的区别有些疫苗需要跟上次接种间隔28天以上这些规则如果全靠人工判断接种点的工作量会大得吓人。所以这套基于微信小程序的疫苗预约接种系统表面上解决的问题是用户在小程序里选时间、提交预约实际要解决的是三个更深层的问题在不同人群同时抢同一个号源时怎么保证不超卖、不重复。怎么让用户和接种点都能清晰地看到一条接种时间线尤其是多针次疫苗。怎么减轻接种点工作人员的人工核对压力把核销、确认、记录变成一套标准流程。搞清楚这三点才知道功能清单该怎么划。如果你只是照着网上的CRUD模板写最后做出来的东西大概率只能在演示视频里跑上了真实环境就是事故现场。1.2 业务状态机一次接种要经历哪些状态预约系统的难点一定不在增删改查而在状态流转。我设计状态机的时候专门画了一张流转图理思路这里用文字描述一次预约从用户提交开始需要经过以下状态待接种已预约用户在小程序里提交预约成功此时号源已锁定占用一个名额。已取消用户在接种时间之前主动取消号源释放回池子其他用户可以继续预约该时段。已完成已接种用户到达接种点工作人员核销预约码确认接种完成。此时这个号源才算真正消耗掉。爽约/未核销预约时间到了但用户没来而且没有提前取消预约失效。每个用户如果多次爽约可以考虑限制后续预约权限这个后面细说。已过期预约时间已经过去但用户既没有取消、也没有接种系统自动从待接种置为已过期。这里有个容易踩坑的点状态字段到底用几个数字表示。看到很多项目喜欢把状态定义成0/1/2这种纯数字然后在前端用switch-case去映射文案。前期确实省事但后面加需求比如已过期和爽约其实是两种语义的时候就非常痛苦。我建议一开始就用字符串枚举比如PENDING、CANCELLED、VACCINATED、NO_SHOW、EXPIRED。虽然数据库稍微多占一点空间但代码可读性和可维护性完全是两个级别。1.3 功能模块的边界划分整个系统我拆成了三个端用户端微信小程序微信授权登录、疫苗列表、按接种点查看号源、选择接种人、提交预约、预约记录列表、取消预约、接种档案查询。接种点端管理后台网页号源配置放号数量、时间、预约列表查询、核销确认、接种记录补录、简单的数据统计。系统端管理后台高权限管理员管理、接种点管理、疫苗管理、针对全量预约数据的统计。小程序端不需要做得太臃肿核心就是把找疫苗-选时间-提交预约-查看记录这条链路走通。管理后台反而是工作量的大头因为接种点工作人员的真实操作场景非常杂他们需要在电脑上快速搜索一个用户的预约记录需要扫码核销需要手动处理一些异常情况比如用户到现场了但没预约成功。我当时给管理后台定的原则是小程序的每个用户操作后台都能看到对应的数据后台的每次核销操作都会推动预约状态向前流转。这条原则保证了两个端的数据不会各说各话。2. 技术选型与数据库设计前后端怎么搭更稳2.1 为什么选原生微信小程序 SpringBoot 这对组合技术选型没有绝对的对错只有合不合适。这套系统我选择了原生微信小程序 Spring Boot MySQL Redis MyBatis-Plus。下面说说我为什么这么选。前端没用uni-app是因为项目本身就是给微信生态用的没有多端诉求用原生框架最直接调试起来也最方便。虽然网上经常看到uniapp做微信小程序在手机上预览没问题、但在微信开发者工具里白屏这类问题原生小程序虽然也要处理兼容性但至少少了一层框架的转译开销定位问题相对直接。后端用Spring Boot是因为团队对Java最熟而且Spring Boot对于这种典型的管理系统非常稳妥生态完善。MyBatis-Plus用来简化单表操作但复杂查询还是手写SQL这里不建议完全依赖MP的Wrapper去做多表关联维护起来会想哭。Redis在整个项目里承担两件事第一是分布式锁用来解决号源并发扣减的问题第二是缓存号源列表和疫苗列表降低数据库压力这个场景QPS虽然不算高但接口响应速度确实能提升不少。如果你一个人开发、不想折腾Java环境那用Node.jsExpress或者PHP也能做。我见过不少用PHP做的预约系统跑得也很稳。关键还是你的业务逻辑设计技术栈只是载体。2.2 数据库表结构设计核心表并不复杂但字段足够多数据库设计是这类系统最值得花时间的地方。我拆出下面这几张核心表用户表user字段类型说明idbigint主键openidvarchar(64)微信openid唯一nicknamevarchar(50)昵称phonevarchar(20)手机号id_cardvarchar(18)身份证号实名档案用created_atdatetime注册时间疫苗表vaccine字段类型说明idbigint主键namevarchar(50)疫苗名称manufacturervarchar(50)生产厂家dose_countint需要接种的针次1/2/3interval_daysint针次间隔天数简化的校验规则descriptiontext适用人群、注意事项statustinyint上下架状态接种点表site字段类型说明idbigint主键namevarchar(100)接种点名称addressvarchar(200)地址phonevarchar(20)联系电话work_timevarchar(100)服务时间描述号源时段表appointment_slot字段类型说明idbigint主键site_idbigint接种点IDvaccine_idbigint疫苗IDslot_datedate预约日期start_timevarchar(10)开始时间如 09:00end_timevarchar(10)结束时间如 09:30total_countint总号源数booked_countint已预约数versionint乐观锁版本号预约表appointment字段类型说明idbigint主键order_novarchar(32)预约单号唯一user_idbigint用户IDpatient_namevarchar(50)接种人姓名patient_id_cardvarchar(18)接种人身份证patient_phonevarchar(20)接种人电话vaccine_idbigint疫苗IDsite_idbigint接种点IDslot_idbigint号源时段IDdose_noint第几针statusvarchar(20)PENDING/CANCELLED/VACCINATED/NO_SHOW/EXPIREDcancel_reasonvarchar(200)取消原因vaccinated_atdatetime实际接种时间created_atdatetime创建时间这套表设计谈不上惊艳但每一张表都有它存在的理由。有几个关键点值得多说一句。第一预约表里有patient_name、patient_id_card这几个冗余字段而不是只存user_id然后去关联用户表。因为成人预约经常是给孩子或者父母预约接种人和小程序登录用户不是同一个人。如果你想在预约表里只存一个 预约人ID就得再加一张家属表复杂度会上升不少。这里直接用冗余字段反而简单。第二号源时段表加了version字段做乐观锁这是并发控制的基石。具体用法在后面代码部分展开。第三预约单号order_no一定要独立出来不要用自增ID给用户看。一是避免暴露系统业务量二是方便后续对接短信、对账等场景。2.3 号源数据是怎么生成的号源配置是整个系统我最满意的一个设计。管理后台里工作人员只需要选择疫苗、接种点、日期范围、每天放多少号系统就会自动在appointment_slot表里批量生成号源记录。这里有一个细节如果放号日期是固定的未来N天滑动窗口就不需要一次性生成半年的数据。比如管理员设定提前7天放号系统每天凌晨定时任务跑一次检查未来第7天的号源是否存在不存在就生成当天的号源。这样数据库里永远是最近7天的号源数据不会出现用户看到一个月后的号源但管理员根本来不及调整配置的情况。这个设计在代码里就是一条批量插入语句用MyBatis的foreach循环插入即可。需要关注的不是插入逻辑而是定时任务的时间点要设在凌晨低峰期避免影响白天的查询性能。3. 小程序端核心功能实现预约流程的完整链路小程序端我重点讲登录、疫苗列表、号源选择、提交预约、订阅消息这五个环节。3.1 登录态处理wx.login 到 openid 再到 token微信小程序不能直接拿用户的openid必须通过wx.login()拿到临时code然后由后端调用微信接口换取。这个流程很多人第一次接触容易绕晕我把它拆成三步第一步小程序端调wx.login()拿到 code。 第二步把 code 传给后端接口/api/auth/login。 第三步后端拿着 code 去请求微信的code2Session接口换取 openid 和 session_key然后返回一个自定义的登录态 token 给小程序。这个 token 我用的是JWT里面只放userId和openid有效期设为7天。小程序前端把 token 存到wx.setStorageSync(token, token)之后每次请求都在 header 里带上。// utils/request.js const BASE_URL https://api.example.com const request (url, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: BASE_URL url, method, data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) }, success(res) { if (res.statusCode 200) { resolve(res.data) } else if (res.statusCode 401) { // token过期重新登录 wx.removeStorageSync(token) wx.navigateTo({ url: /pages/login/login }) } else { wx.showToast({ title: res.data.message || 请求失败, icon: none }) reject(res.data) } }, fail(err) { reject(err) } }) }) } module.exports { request }登录页面里注意wx.getUserProfile改版后的授权逻辑现在不能直接弹窗拿用户头像昵称了我建议不要卡在这一步先让用户能登录能预约头像昵称可以后续在个人中心里引导用户补充。初始昵称直接用微信用户兜底。3.2 疫苗列表与号源展示疫苗列表页面就是一个典型的列表页后端接口返回疫苗名称、厂家、价格如果有、剩余号源总数。这里我刻意做了一个聚合查询避免前端拿到疫苗列表后再逐条去请求号源数量。// VaccineController.java GetMapping(/vaccine/list) public Result listVaccines() { ListVaccineVO list vaccineService.listWithSlotCount(); return Result.success(list); }对应的SQL大致是SELECT v.id, v.name, v.manufacturer, v.dose_count, v.description, COALESCE(SUM(s.total_count - s.booked_count), 0) AS remaining_count FROM vaccine v LEFT JOIN appointment_slot s ON s.vaccine_id v.id AND s.slot_date CURDATE() AND s.booked_count s.total_count WHERE v.status 1 GROUP BY v.id这个查询把还有没有号一次性算出来前端拿到疫苗列表就能直接展示剩余N个号体验很好。点击某个疫苗进去用户先选择接种点再选择日期。日期我建议用小程序原生的picker modedate但要注意start和end参数的动态设置。选好日期后再调号源列表接口把当天的所有可约时段展示出来。// pages/slot/slot.js const getSlots async () { const res await request(/api/slot/list?vaccineId${vaccineId}siteId${siteId}date${date}) setData({ slots: res.data }) }每个时段卡片上显示09:00-09:30剩余8个号如果booked_count total_count这个时段的卡片置灰不可点。这里用了表单radio-group或者自由定制的单选框样式——注意微信小程序的 radio 组件默认样式丑而且不同机型上尺寸有差异建议直接用 view 配合选中态样式来做单选项。这也是热搜上微信小程序单选框高频出现的原因很多人卡在自定义样式上。3.3 提交预约的并发控制不超卖的保障这是整个项目技术含量最高的一块。用户选好时段、填写接种人信息后点击提交预约后端要做的是扣减号源、创建预约记录必须在一个事务里完成而且不能出现超卖。我选了乐观锁方案适用于这种绝大多数情况不会冲突、但极端情况下必须保证正确的场景。// AppointmentServiceImpl.java Transactional(rollbackFor Exception.class) public void createAppointment(CreateAppointmentRequest req) { // 1. 校验用户是否已有同疫苗未完成预约 Integer pending appointmentMapper.countPending(req.getUserId(), req.getVaccineId()); if (pending ! null pending 0) { throw new BizException(您已有待接种的预约请勿重复预约); } // 2. 扣减号源乐观锁控制并发 int updated slotMapper.decreaseStock(req.getSlotId()); if (updated 0) { throw new BizException(手慢了该时段号源已被约满); } // 3. 创建预约记录 Appointment appointment new Appointment(); appointment.setOrderNo(OrderNoGenerator.generate()); // ... 设置其他字段 appointmentMapper.insert(appointment); }关键是第2步的SQLUPDATE appointment_slot SET booked_count booked_count 1, version version 1 WHERE id #{slotId} AND booked_count total_count AND version #{version}这个SQL的意思是只有当前booked_count小于total_count时才允许把booked_count加1。数据库层面的行锁天然保证了并发安全两个用户同时提交时只有一个用户能成功执行这条UPDATE另一个的updated返回0然后抛异常提示已约满。有人会问为什么不直接用Redis的INCR做预扣Redis方案在高并发下性能确实更好但需要额外处理Redis和数据库的一致性问题比如用户预约成功后Redis扣了但创建预约记录时数据库报错Redis的数字又得回滚这在业务上非常容易出Bug。对于预约系统这种并发量几百人抢几十个号的场景数据库乐观锁完全够用而且逻辑简单、容易排查。不要为了炫技引入不必要的复杂度这是做业务系统最重要的原则。还有一个小细节用户重复预约的校验。我做了两层一层是上面代码里的countPending数据库查询另一层是给appointment表加了一个联合唯一索引user_id,vaccine_id,status不过MySQL的联合唯一索引对status这种会变的值不太好使所以实际还是要靠事务内的查询插入来保证。真正高并发的场景可能还需要Redis做前置拦截但在这个量级下数据库查询就能扛住。3.4 订阅消息通知让用户知道约没约上预约成功后我给用户发送一条微信订阅消息告知预约成功、接种时间、接种地点。这一步需要在用户点击提交预约之前主动调wx.requestSubscribeMessage请求授权。// pages/confirm/confirm.js const subscribeMessage async () { const tmplIds [订阅消息模板ID] return new Promise((resolve) { wx.requestSubscribeMessage({ tmplIds, success(res) { resolve(res[tmplIds[0]] accept) }, fail() { resolve(false) } }) }) }一个容易被忽略的点订阅消息分为一次性订阅和长期订阅普通开发者申请到的模板基本都是一次性订阅——用户授权一次你只能发送一条消息。所以不要在用户点击提交之前就把授权次数消耗掉了。我这里是用户点了提交预约后先弹授权用户同意后调登录、提交预约接口预约成功后再发订阅消息。后端发送订阅消息的逻辑不复杂难点在于access_token的获取和缓存。微信的access_token有效期是2小时频繁获取会被限流一定要把access_token存到Redis里设置过期时间7200秒快过期再重新获取。我见过不少项目没做缓存请求稍微一多就报45009接口调用超限这种错属于完全可避免的。4. 管理后台的号源与核销设计工作量的大头4.1 放号配置与每日定时任务管理后台我用的是Vue3 Element Plus和SpringBoot后端通过 RESTful 接口对接。页面上维护放号规则以某一种疫苗在某个接种点为单位配置放号周期、每日号源总数、时段拆分规则比如上午9点到11点每个小时为一个时段每个时段放10个号。前端的表单提交后后端生成号源数据的逻辑大致是// SlotJob.java Scheduled(cron 0 0 2 * * ?) // 每天凌晨2点执行 public void generateSlots() { ListSlotRule rules slotRuleMapper.selectAll(); for (SlotRule rule : rules) { LocalDate targetDate LocalDate.now().plusDays(rule.getAdvanceDays()); int count slotMapper.countByDateAndRule(rule.getId(), targetDate); if (count 0) { slotService.generateForDate(rule, targetDate); } } }这里generateForDate内部会根据规则里的时段列表批量插入多条appointment_slot记录。要注意批量插入不要循环单条插入一定要用MyBatis的foreach拼成一条INSERT执行性能差好几倍。另外放号规则里通常还要配一个放号时间的概念比如每天上午10点放第7天的号。这个时间如果在凌晨定时任务里实现比较别扭因为用户能看到号源的时间不一定是数据生成的时间。我的做法是凌晨2点只生成不可见的号源数据然后提供一个openSlots操作由管理后台的定时任务在指定时间比如10:00把号源的状态从0改为1。前端查询时只返回status1的号源。这样既灵活又可控。4.2 核销流程扫码和手动核销两条路用户到了接种点工作人员登录管理后台首页默认显示当天的全部预约列表。核销有两种方式第一种是手动查询工作人员输入用户的预约单号或手机号查出预约记录确认状态是PENDING点击确认接种状态变为VACCINATED同时记录vaccinated_at。第二种是扫码核销用户小程序端我的预约页面会展示一个预约二维码我用的是小程序码二维码里带order_no工作人员在后台用扫码枪扫一下前端拿到单号后调后端核销接口。这里扫码枪本质就是一个USB输入设备扫到的内容会以键盘输入的方式自动填入输入框所以我只需要监听输入框的keyup事件回车后自动触发搜索并不需要什么高级硬件对接。核销接口有一个细节核销和状态变更要校验当前状态。// AppointmentController.java PostMapping(/api/appointment/vaccinate) public Result vaccinate(RequestBody VaccinateRequest req) { int updated appointmentMapper.updateStatus( req.getOrderNo(), PENDING, VACCINATED, LocalDateTime.now()); if (updated 0) { throw new BizException(该预约已处理或状态异常请刷新后重试); } return Result.success(); }UPDATE ... WHERE order_no ? AND status PENDING这种写法能防止工作人员连续点两次按钮导致状态被重复更新。虽然不是高并发但我们要养成这种防御性编程习惯。实际核销过程中会遇到各种意外比如用户明明没预约成功但拿着一张取消状态的记录来现场这时候后台要能查到取消原因工作人员可以正常沟通处理。4.3 统计报表不用太花哨但要能看出问题管理后台的统计模块我做得很克制只做了四个指标今日预约数、今日核销数、今日取消数、各疫苗剩余号源数。查询逻辑完全是COUNTGROUP BY没有用任何复杂的大数据组件。为什么做这么简单因为实际上接种点最关心的三个问题是今天约了多少人来了多少人哪些疫苗快没号了。你给一个接种点看折线图、地图分布他们反而无所适从。做业务系统报表的复杂程度一定要跟着真实使用场景走。有一点很多人会忽略统计数据里要区分爽约和已过期。爽约意味着用户预约了但没来这说明有资源被浪费了已过期可能是因为号源过期时间设置太短。如果发现某个时段爽约率特别高就可以提醒管理员调整放号策略比如把号源时间从半小时改为15分钟减少单时段资源堆积。5. 上线前后踩过的坑小程序审核与真机调试5.1 小程序类目审核没资质怎么办做医疗相关的小程序微信审核非常严格。疫苗预约这个领域你选择医疗-医疗服务类目平台会要求提供医疗机构执业许可证等资质。如果你只是个人开发者或者手里没有医疗机构资质这条路基本走不通。我实际踩下来的经验是如果你的系统只是做预约工具、不涉及在线问诊、不涉及处方药销售可以尝试选择工具-预约/报名类目。类目不同审核要求差异很大但前提是你的预约功能里不能出现疫苗接种这类强医疗词汇。这需要你在系统名称、页面文案上做一些调整比如统一叫预约服务健康服务。这里提醒一句千万不要为了过审去伪造资质小程序审核会定期复核一旦被发现轻则下架、重则封号。如果确实是真实的医疗机构项目就直接用机构资质去申请一步到位。还有一个相关的坑如果预约流程里涉及在线支付比如二类苗付费小程序平台对虚拟支付有严格限制。疫苗是实物服务问题不大但如果你涉及到的是在线问诊支付那就要小心了。我的建议是疫苗预约系统尽量不要在小程序内做线上支付让用户到现场缴费省掉一堆审核和资金合规的麻烦。5.2 真机正常但开发者工具白屏别慌按顺序排查开发过程中我遇到过一个小程序在手机上预览没问题、但在微信开发者工具里打开白屏的情况。排查一圈下来原因竟然是最简单的开发者工具的基础库版本太旧不支持我在代码里用到的某个新API。更新基础库版本后一切正常。这类问题排优先顺序的建议是先看开发者工具的Console报错再看是否用了新API再看组件是否在页面json文件里注册最后看app.json的页面路径是否正确。白屏问题里十有八九是JavaScript报错打断了渲染而报错信息往往被开发者工具藏在了Console面板里一眼没看见就慌了。另外模拟器和真机的样式差异非常普遍。我经常遇到模拟器上好好的真机上某个margin多出来几个像素或者flex布局产生换行。这个没有捷径只能在真机预览里过一遍核心页面。特别是微信小程序顶部导航栏的高度问题不同机型状态栏高度不同如果你做了自定义导航栏一定要用wx.getSystemInfoSync()获取statusBarHeight再根据胶囊按钮位置计算导航栏高度固定写死90px在这个年代已经不现实了。5.3 HTTPS、域名白名单和抓包调试小程序的wx.request强制要求HTTPS而且域名必须在小程序后台配置为白名单。开发阶段可以在开发者工具里勾选不校验合法域名但上线前一定要把正式域名的SSL证书配好否则真机一上线就是页面白屏、请求全部失败。关于调试很多人会问小程序怎么抓包。我的做法是开发者工具里勾选打开调试然后通过代理工具比如Charles或Reqable设置HTTPS代理手机和电脑连同一个WiFi手机WiFi代理指向电脑IP再安装代理的根证书就能看到小程序的所有请求。注意微信iOS端的证书信任逻辑和Android不完全一样iOS需要到设置里手动信任证书这一步经常有人漏掉导致抓不到包。抓包的场景非常关键尤其是上线后发现某个用户预约失败你需要在后台看他的完整请求链路是登录接口返回了401还是号源扣减失败还是参数校验没过。有了抓包工具这些问题几分钟就能定位否则只能靠用户截图效率极低。6. 这套系统的通用化思路与实际效果6.1 从疫苗预约到通用预约改哪些地方就能复用项目做完之后我回头审视了一遍发现这套系统里大概70%的代码是完全通用的只需要把疫苗这种业务概念抽象成资源就能复用到很多预约场景。你只需要改这几个地方第一把vaccine表换成语义更通用的resource表字段从疫苗名称、厂家、针次改成资源名称、描述、单位。第二把dose_no第几针的逻辑去掉。如果预约的是一次性资源比如场地预约、体检预约这个字段就冗余了如果是多阶段资源比如驾考科目二约了还要考科目三可以把它泛化成step_no。第三把接种人信息改成动态表单。疫苗预约场景的接种人姓名、身份证、电话是固定的但通用预约里不同资源要收集的信息不一样。可以用JSON字段存表单数据或者用一张扩展字段表让管理员在后台自定义需要收集哪些字段。剩下的登录、号源管理、并发控制、核销、统计、订阅消息通知全部不用动。如果你接下来还想做预约相关的项目直接从这套骨架上改是最快的方式。6.2 性能数据和实际使用反馈系统上线后我用压测工具简单跑了一下号源提交接口模拟30个用户同时抢一个只有10个号源时段的场景最终成功预约数为10剩余20个请求全部返回号源已约满。数据库没有任何死锁接口P99响应时间在200ms以内对于这种业务场景来说完全够用。实际运营中最让我意外的不是并发而是用户取消预约的频次。上线第一周当天预约的取消率竟然超过了15%。这直接导致的问题是有些时段白天看着已经约满晚上用户一取消第二天早上又放出几个号但此时已经没有用户去刷了号源就白白浪费掉。针对这种情况我加了一个很简单的小功能取消预约的号源不会立即释放而是进入一个待释放状态在每天凌晨定时任务里统一释放。这样第二天用户打开小程序看到的是重新整理过的号源分布而不是乱七八糟的零星空位。实测下来号源利用率提升了大概10%左右。6.3 几个我认为最值得记住的经验回头整理这个项目有几个经验如果让我重做一遍依然会坚持第一状态字段用字符串枚举不要用数字。项目维护到后期status0到底是已取消还是待确认你根本记不住每次都要翻代码注释。字符串一眼就能看明白。第二并发控制用数据库乐观锁就好别一上来就上消息队列和分布式事务。预约系统的并发量级远没有达到需要那套重型方案的级别而且那套方案会引入大量不可控的中间件故障点。等你真的需要处理每秒上万次请求时再演进架构没有迟。第三给用户看的通知一定要准。订阅消息看似只是一个小功能但预约成功没通知和预约成功通知内容写错了时间是完全不同的两个问题。后者会让用户白跑一趟投诉率飙升。发送逻辑里一定要用预约记录里的真实字段不要用前端传过来的时间避免数据不一致。第四一定要在开发阶段就把日志打好。我项目里每个预约状态变更都写了Slf4j日志包含orderNo、旧状态、新状态、操作人、时间。上线后排查问题这些日志就是救命稻草。没有日志的情况下去排查并发问题等于盲人摸象。这套系统从需求梳理到上线前后花了大概三周如果说最核心的收获不是代码写了多少行而是把预约这件事的底层逻辑真正想清楚了号源是稀缺资源、状态是流转的、用户和工作人员看到的永远应该是同一份数据。你如果也在做类似的项目建议先把业务状态机画明白再动手写代码。状态清楚了页面和接口只是体力活。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进