简介这套基于微信小程序的智能化医院体检全流程管理系统是一份面向医疗信息化方向开发者与学习者的完整项目源码覆盖在线预约体检套餐、实时查看排队进度、电子报告查询、健康档案管理、医生在线咨询、体检注意事项提醒、健康数据分析追踪及体检流程数字化等核心业务模块适用于课程设计、毕业设计或同类产品研发参考。资源共1201个文件、约15.31MB以vue前端页面、java后端服务、js业务逻辑、wxss/wxml小程序界面为主辅以png/svg图标素材、json/xml配置、sql数据库脚本及bat部署脚本前后端链路较为完整。包内附有1-install.bat、2-run.bat、3-build.bat部署脚本及后台管理视图、小程序页面文件便于理解分层架构并快速二次开发。已有189人下载学习借助源码可省去基础框架搭建时间直接聚焦预约、排队、报告、咨询等业务逻辑的定制与调优。1. 体检全流程管理系统排队三小时、报告找不到的旧场景该被数字化了“基于微信小程序的智能化医院体检全流程管理系统”本质上就是把体检中心从“预约套餐、现场签到、排队叫号、报告领取、档案留存、复诊咨询”的线下链路全部搬进微信小程序。体检中心最典型的痛点不是缺医生而是流程是个黑匣子用户不知道前面还有多少人、报告出了没人通知、去年和今年的指标对不上。这套系统的价值不是做一堆页面而是把散落的线下状态串成一条透明的数据流。适合做这个方向的人有两类一是医院信息科、体检中心运营者想用一套低成本方案替代纸质流程二是正在做毕业设计、需要把功能讲清楚并真正能跑通的小程序开发者。先说一个反直觉的结论这个项目八成的工作量在写状态机和数据表页面反而简单。2. 技术选型与工程结构先定数据模型再写页面不然一定返工拿到这个需求第一件事不是搭页面而是定技术栈。这类体检管理系统最成熟的组合是微信小程序做用户端Spring Boot MySQL Redis 做后端另加一个 Vue3 的 Web 管理后台给护士和医生用。这个组合在中小型院内系统和毕业设计里都非常常见案例多、排查资料多、招人也好招。要不要上微服务完全不需要。一个体检中心同时在线用户通常只有几百人单体应用足以支撑拆成微服务只会让本机部署和运维变成灾难。2.1 原生微信小程序还是 uniapp先看有没有多端诉求很多团队一上来就用 uniapp理由是“以后还能发 App”。对体检这种场景这属于典型的过度设计。用户到体检中心的第一动作是微信扫一扫进小程序不会为了体检单独装一个 App。原生微信小程序在登录、支付、订阅消息、openDocument 这几个核心 API 上支持最直接踩坑资料也最好找。uniapp 虽然能把同一套代码编译到 Android、iOS、鸿蒙等多端但真机上要调微信私有能力时经常得写条件编译代码里满是平台判断反而变成一个新的黑匣子。我的选择标准很简单如果明确只做微信生态就原生如果团队目标是同一套代码覆盖多端再考虑 uniapp。管理后台同理只有护士和医生用直接用 Vue3 加一个成熟中后台模板比单独搞一套大前端平台快得多。这里不要被“跨端”这个概念带着走先把单一场景做透多端以后再抽公共层也不迟。2.2 前端目录怎么分按流程节点不按功能堆用户对体检系统的心理模型是线性的先选套餐再预约到院签到排队做检查拿报告再看档案。小程序页面就应该按这个流程分目录而不是按“功能模块”堆一堆平级页面。目录职责pages/index首页今日体检入口、套餐分类、公告pages/package体检套餐列表与详情pages/order预约下单选日期时段、阅读注意事项pages/queue实时排队进度pages/report电子报告列表与详情pages/archive健康档案历次指标趋势pages/consult医生在线咨询components/notice-card注意事项卡片组件utils/request.js统一请求封装注入 token 与错误处理页面跳转天然就是流程跳转首页进套餐套餐页下单订单页生成排队号排队页结束后跳报告报告归档进档案。这样用户在页面里的行为路径和线下流程完全一致开发时也好定位问题。这里有一点值得强调务必把 wx.request 封装成一个统一的 Promise 方法不要在每个页面里直接调 wx.request。几十个页面的项目如果请求散落各处后面加一个 token 或改一个错误提示要动几十个文件那是真血泪经验。// utils/request.js const request (url, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: ${baseUrl}${url}, method, data, header: { token: wx.getStorageSync(token) || }, success(res) { if (res.statusCode 401) { wx.navigateTo({ url: /pages/login/index }) reject(res) return } if (res.data.code ! 0) { wx.showToast({ title: res.data.msg, icon: none }) reject(res.data) return } resolve(res.data.data) }, fail: reject }) }) }这段封装把三件事收敛到了一处所有请求自动带 header token遇到 401 统一跳登录页后端业务错误统一弹 toast。baseUrl 建议单独放在 config.js 里不要写死在每个页面。这样做的好处是后面前端要接“体检报告下载”或“排队轮询”时只需要复用这个 request 方法不需要关心 token 和状态码处理。2.3 先建这三张表再谈其他功能很多新手上手先写页面页面写完了发现数据没地方存回头再改表结构返工量极大。反过来先把核心表定下来页面只是数据的投影。这个系统最关键的表是套餐表、订单表、报告指标表。报告指标表放到第 4 章讲这里先看前两张。CREATE TABLE health_package ( id BIGINT PRIMARY KEY AUTO_INCREMENT, package_name VARCHAR(64) NOT NULL, price DECIMAL(10,2) NOT NULL, items_json TEXT COMMENT 套餐包含项目如 血常规、肝功能, notice_rules JSON COMMENT 注意事项规则如 require_fasting1, stock INT NOT NULL DEFAULT 0 COMMENT 每日可约库存, status TINYINT DEFAULT 1, created_at DATETIME ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE health_order ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_no VARCHAR(32) UNIQUE, user_id BIGINT NOT NULL, package_id BIGINT NOT NULL, period_start DATETIME, period_end DATETIME, status TINYINT COMMENT 0待支付 1已确认 2已到院 3体检中 4已完成 5已取消, queue_no INT, version INT DEFAULT 0, created_at DATETIME ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;这里有几个设计决策要解释。items_json 用 JSON 存套餐明细优点是省掉一张中间表增删项目灵活缺点是如果要统计“哪个体检项目卖得最多”就得拆“套餐明细表”。对这个规模的管理系统JSON 够用。status 用 TINYINT 数字做状态机不要用字符串“PAID”“ARRIVED”之类的数字在前后端判断和索引上都更省事。version 字段是乐观锁在下面排队状态更新时能防止并发覆盖。order_no 的唯一索引是防止重复下单的第一道防线应用层哪怕出 bug数据库也能兜底。部署形态上我建议单体部署一台云服务器装 MySQL、Redis、后端 jar 包小程序端不存任何敏感数据token 通过 wx.setStorageSync 保存在本地。用户体检报告属于敏感数据后端接口一定要做用户维度校验不能让 A 用户通过改 reportId 看到 B 用户的报告这个后面还会再强调。3. 在线预约与实时排队把“现场取号”改成“状态流转”预约和排队是这个系统里业务规则最复杂的模块因为它们不只是增删改查而是一个严格的状态机。下面按用户实际操作顺序拆开讲。3.1 预约状态机五个状态只有三条合法路径订单状态从 0 到 4 分别是待支付、已确认、已到院、体检中、已完成。再加上一个 5 已取消。合法流转路径只有几条用户在套餐详情页下单后生成状态 0支付成功变成 1到院签到变成 2医生开始检查变成 3项目全部完成变成 4。用户在状态 0 或 1 时可以取消订单到状态 5。不允许从 2 直接跳到 4一个没做检查的人不可能“已完成”。这个状态机的推进点对应线下动作支付成功是系统自动推进签到由护士操作体检中由医生在叫号系统点击“开始检查”已完成由护士在报告上传后统一置为完成。状态推进的代码必须收敛在 service 层不要在每个 controller 里直接 update status否则一旦出现“跳过状态”或“状态回退”排查起来非常痛苦。后端做状态变更时最稳的写法是带前置条件的 UPDATE而不是先 SELECT 再 UPDATEUPDATE health_order SET status #{targetStatus}, version version 1 WHERE id #{orderId} AND status #{currentStatus} AND version #{version};这条 SQL 的意思是只有在订单当前状态确实等于我预期的 currentStatus、且版本号没被人改过时才允许更新。受影响行数为 0 就说明并发冲突要么别人已经推进了状态要么当前状态本身非法。把这条规则套到“护士签到”场景里就是SET status 2 WHERE id ? AND status 1重复签到第二次必然失败。用乐观锁代替业务代码里的 if 判断是这个小系统里性价比最高的并发安全方案。3.2 下单接口同一用户同一天只能有一单进行中用户选好体检套餐、选好日期时段后点击提交订单。这个接口要处理两个问题重复下单和库存并发。先看核心逻辑Service public class OrderService { Transactional public Order createOrder(Long userId, Long packageId, LocalDateTime periodStart) { // 幂等校验同一用户同一天存在未完成订单直接拒绝 Long exists orderMapper.countOngoing(userId, periodStart.toLocalDate()); if (exists 0) { throw new BusinessException(当天已有体检预约请勿重复下单); } // 库存扣减条件更新防止超卖 int updated packageMapper.deductStock(packageId); if (updated 0) { throw new BusinessException(该时段套餐库存不足); } Order order new Order(); order.setOrderNo(generateOrderNo()); order.setUserId(userId); order.setPackageId(packageId); order.setStatus(0); order.setPeriodStart(periodStart); order.setPeriodEnd(periodStart.plusHours(4)); orderMapper.insert(order); return order; } }Transactional保证幂等校验、库存扣减、订单落库在同一个事务里中间任何一步失败都整体回滚。deductStock 对应的 SQL 是UPDATE health_package SET stock stock - 1 WHERE id ? AND stock 0这句是关键数据库在更新时判断库存大于 0才能扣减。并发场景下两个用户同时下单只有一个用户能扣到库存另一个拿到 updated0 直接报“库存不足”。如果先 SELECT 再 UPDATE两个请求都读到 stock1就会双双扣成负数这是库存超卖最常见的翻车原因。orderNo 的生成规则我习惯用yyyyMMddHHmmss 4 位随机数长度适中、可读性好。体检日期只允许约未来 7 天内的号源前端限制一次后端还要再校验一次。下单接口返回订单号后前端跳转支付页支付成功回调里再把订单状态从 0 推进到 1。3.3 实时排队进度10 秒轮询就够了别硬上 WebSocket排队进度是这个系统最容易设计过度的模块。很多开发者一看到“实时”两个字就上 WebSocket结果在医院内网环境里长连接经常被反向代理的空闲超时断开重连逻辑写了一大堆体验反而更差。体检排队的实时性要求其实不高用户能知道“前面还有 8 个人”就够了10 秒刷新一次完全满足。小程序端轮询的完整写法如下const { api } require(../../utils/request) Page({ data: { queueNo: null, waitingCount: 0, status: }, onLoad(options) { this.setData({ queueNo: options.queueNo }) this.startPolling() }, startPolling() { this.timer setInterval(() { api.get(/api/queue/snapshot, { queueNo: this.data.queueNo }) .then(res { this.setData({ waitingCount: res.waitingCount, status: res.status }) if (res.status CHECKING || res.status DONE) { this.stopPolling() } }) .catch(() { // 轮询失败保留上一次数据不清空排队号 }) }, 10000) }, stopPolling() { clearInterval(this.timer) this.timer null }, onUnload() { this.stopPolling() } })有两个细节值得注意。第一轮询间隔 10 秒是性价比比较高的值。200 个在线用户同时轮询QPS 也只有 20普通后端毫无压力间隔太短则服务端压力成倍上涨间隔太长用户会觉得卡。第二onUnload 里必须清定时器。小程序页面切走不销毁如果定时器不清用户离开排队页后请求还在后台跑页面多了会卡顿这是新手最容易漏的坑。接口失败时保留上一次的数据只显示“连接中”不要把排队号清空否则用户看到白屏会反复退出重进。后端排队快照接口返回的数据结构可以设计成{ code: 0, data: { queueNo: 23, currentIndex: 15, waitingCount: 8, status: WAITING } }currentIndex 是当前正在体检的排队号waitingCount 由后端计算好后返回前端不要自己用 queueNo 减 currentIndex因为涉及“过号”“重新签到”等特殊情况前端算不准。status 标记当前用户处于 WAITING、CHECKING 还是 DONE前端根据状态决定继续轮询还是停止。3.4 体检注意事项提醒规则驱动不写死页面注意事项提醒如果做成静态页面不同套餐、不同人群的差异根本管不过来。需要空腹的项目、需要憋尿的项目、慢性病人需要停药的项目规则完全不同。我的做法是把注意事项规则放在套餐表的 notice_rules 字段里{ require_fasting: [肝胆胰脾B超, 空腹血糖], require_water: [泌尿系统B超], stop_drug: [阿司匹林需停药7天] }小程序端在订单详情页加载后先把该订单下的体检项目和套餐规则匹配再按“未检项目”过滤出当前需要提醒的事项const unCheckedItems order.items.filter(item !item.checked) const notices [] unCheckedItems.forEach(item { if (noticeRules.require_fasting.includes(item.name)) { notices.push({ type: fasting, text: 空腹项目 item.name }) } if (noticeRules.require_water.includes(item.name)) { notices.push({ type: water, text: 憋尿项目 item.name }) } })这样设计的好处是套餐内容调整时只需要改后台套餐数据前端不用发版。用户阅读注意事项后需要勾选“已阅读”未勾选不允许点击“开始体检”按钮这个交互细节能帮体检中心挡掉不少客诉。页面上如果用了自定义导航栏记得在 onLoad 里动态读取胶囊按钮位置来算顶部高度不同机型差异很大写死高度会在大屏手机上露怯。4. 电子报告查询、健康档案与数据分析追踪数据结构决定分析能不能做报告和档案是体检系统里最有长期价值的部分但很多项目把报告存成一张 PDF 就结束了后面想做“健康数据分析追踪”时完全没有数据可用。这章的核心观点是PDF 是给人看的结构化指标是给系统算的两者都要留。4.1 电子报告查询PDF 交给文件系统指标必须结构化正规流程是体检中心的 LIS 检验系统生成 PDF 报告后通过接口推送到后端后端把 PDF 存到对象存储或本地磁盘同时把报告里的关键指标抽出来落到 report_item 表。数据库里不应该存 PDF 的二进制内容只存 URL否则数据库体积会飞速膨胀备份和迁移都会很痛苦。CREATE TABLE report_file ( id BIGINT PRIMARY KEY AUTO_INCREMENT, order_id BIGINT NOT NULL, file_name VARCHAR(128), file_url VARCHAR(512), file_type TINYINT COMMENT 1PDF 2图片, created_at DATETIME ) ENGINEInnoDB DEFAULT CHARSETutf8mb4; CREATE TABLE report_item ( id BIGINT PRIMARY KEY AUTO_INCREMENT, user_id BIGINT NOT NULL, order_id BIGINT NOT NULL, item_code VARCHAR(32) COMMENT 指标编码如 GLU, item_name VARCHAR(32), item_value VARCHAR(16), unit VARCHAR(16) COMMENT 标准单位如 mmol/L, ref_range VARCHAR(32), abnormal TINYINT DEFAULT 0 COMMENT 0正常 1偏高 2偏低, check_date DATETIME ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;report_item 表是整个“健康数据分析追踪”的地基。它的查询维度有三个order_id 关联到具体一次体检user_id 用于做跨年份档案查询item_code 用于做单指标趋势分析。abnormal 字段可以在报告解析时由后端根据 ref_range 自动算出来也可以由医生复核后修正前端展示时直接根据这个字段标红加箭头。小程序端打开报告 PDF 的标准链路是 wx.downloadFile 下载临时文件再 wx.openDocument 打开previewReport(e) { const fileUrl e.currentTarget.dataset.url wx.downloadFile({ url: fileUrl, success(res) { wx.openDocument({ filePath: res.tempFilePath, fileType: pdf, showMenu: true, success: () console.log(报告打开成功) }) } }) }这里有一个特别容易踩的配置坑wx.downloadFile 的合法域名和小程序 request 合法域名是分开配置的。很多人只配了 request 域名导致接口请求正常、PDF 永远下载失败后面第 5 章还会细说。报告生成后还要主动通知用户。常见做法是用微信订阅消息报告上传完成后调 wx.requestSubscribeMessage 引导用户订阅“报告已出”通知后端在报告落库时通过模板消息推送。这个功能虽然小却能显著减少用户反复刷新报告页的频率也是“全流程”体验里很有感知的一环。4.2 健康档案与趋势分析一条 SQL 把三年指标拉平健康档案页面如果只是把历次报告按时间罗列用户看两次就不想看了。真正有价值的是趋势血糖这几年是平稳还是升高体重指数是上涨还是回落。实现趋势的前提是 item_code 编码统一单位统一。SELECT check_date, item_value, unit, ref_range, abnormal FROM report_item WHERE user_id #{userId} AND item_code #{itemCode} ORDER BY check_date DESC LIMIT 8;这条 SQL 按“人 指标编码”取出最近 8 次体检记录后端直接返回给小程序前端用 ec-canvas 或 canvas 画折线图即可。对应的后端接口可以很简单GetMapping(/api/archive/trend) public ListTrendVO trend(Long userId, String itemCode) { ListReportItem items reportItemMapper.selectTrend(userId, itemCode, 8); return items.stream().map(item - new TrendVO(item.getCheckDate(), item.getItemValue(), item.getUnit()) ).collect(Collectors.toList()); }这里单位统一是最大的隐性坑。血糖有的实验室报 mmol/L有的历史报告是 mg/dL两个单位数字差了约 18 倍不换算就画在同一张图里折线会直接变成“过山车”。我的做法是维护一张指标字典表只认一个标准单位入库前做归一化。历史数据如果单位未知宁可不在趋势图里展示也不能拿原始值硬画。趋势图下方可以加一栏“最近一次体检结果摘要”把异常指标列表单独列出来让用户一眼看到需要关注的项。4.3 医生在线咨询让医生打开会话就能看到这份报告在线咨询如果做成了纯聊天室价值会大打折扣。用户从报告页发起咨询时应该把 report_id 或 order_id 带到会话上下文里医生端打开会话就能直接看到这份报告不用再问“你上次报告呢”。会话消息表可以这样设计CREATE TABLE consult_message ( id BIGINT PRIMARY KEY AUTO_INCREMENT, consult_id BIGINT NOT NULL, sender_type TINYINT COMMENT 1用户 2医生 3系统, msg_type TINYINT COMMENT 1文本 2图片 3报告卡片, content TEXT, report_id BIGINT, created_at DATETIME ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;消息类型里单独设一个“报告卡片”本质上消息内容只存 report_id不存报告快照。医生端点击报告卡片后实时请求报告详情接口拿最新数据。这样做的好处是同一份报告在会话、档案、报告列表三个入口看到的永远是最新版本不会因为会话里存了一份旧 PDF 而产生歧义。人工在线咨询需要医生端配合通常做法是管理后台里内置一个简单的 Web IM 工作台。消息推送可以用微信的“客服消息”能力用户在小程序里发消息后后端通过客服消息接口推给医生的管理后台医生回复时再经过后端调客服消息接口发给用户。整个链路不需要自建 WebSocket实现成本低很多。5. 避坑排查指南5个让体检小程序翻车的真实问题与修复这一章写的是这套系统上线后最容易遇到的 5 个问题每一条都是现象、原因、处理三步讲清楚。排查的时候我的习惯是先看后端日志和接口返回码再在开发者工具 Network 面板里看真实请求响应最后才判断是前端逻辑问题还是后端数据问题。不要一上来就怀疑框架大多数问题都是基础配置和状态一致性导致的。5.1 排队进度和护士操作台不一致现象护士在管理后台把用户状态改成“体检中”用户小程序端还显示“前面还有 12 人”等很久都不刷新。原因管理后台直接执行了裸 UPDATE 更新订单状态没有走状态机入口导致乐观锁没有生效同时用户端轮询返回后页面把本地排队号和接口返回数据合并旧数据没被完全覆盖。处理所有状态变更统一走 updateStatus(currentStatus, targetStatus) 接口SQL 里带前置状态条件。用户端轮询到数据后无条件用服务端返回的 status 和 waitingCount 覆盖本地值。另外在排队页加一个手动刷新按钮用户等太久可以主动拉取减少投诉。排查时可以看后端日志里状态更新 SQL 的受影响行数如果经常是 0说明并发冲突频繁优先查管理后台是否绕过了状态机。5.2 开发环境一切正常一上体验版全部请求失败现象开发者工具里所有接口都能通手机扫码预览后页面白屏控制台报 url not in domain list。原因微信小程序的 wx.request 和 wx.downloadFile 只允许请求后台配置的合法域名。本地开发时开发者工具默认勾选了“不校验合法域名”所以 http://localhost 和 http://192.168.x.x 都能用一旦进入体验版和正式版这个勾选失效所有不在白名单里的域名请求全部被拦截。处理在微信公众平台后台把后端接口域名加入 request 合法域名把报告文件域名加入 downloadFile 合法域名。上线环境必须 HTTPS。开发时也要尽量用真实域名调试别依赖“跳过校验”否则上线时才发现还要临时换域名改配置。注意这个坑很隐蔽因为接口报错往往被框架拦截后只在 console 里出现一行很容易当成后端挂了。5.3 iOS 打不开 PDF 报告Android 正常现象同一份 PDF 报告Android 手机可以正常打开iPhone 上白屏或提示“无法打开文档”。原因iOS 对 URL 中的中文文件名和特殊字符非常敏感后端返回的 file_url 没有做 URL 编码中文文件名直接拼进 URL 导致下载失败。另一个隐藏原因是对象存储或静态文件服务器没有开启 Range 请求支持wx.openDocument 在文件稍大时依赖分段读取服务器不支持就会失败。处理后端生成 file_url 时统一对文件名部分做 URLEncoder.encode不要把原始文件名直接拼进去。静态服务器要确认支持 Range 请求头。排查方法很直接把后端日志里真实的文件 URL 复制到 iPhone 的 Safari 里直接访问能打开就是小程序端问题打不开就是服务器或 URL 本身的问题。5.4 数据库时间混乱“预计等待时间”算成负数现象排队页显示“预计等待 -18 分钟”或者用户预约了上午时段到中午状态还是“待确认”。原因前后端时间规范不统一。有的字段是 DATETIME有的字段是毫秒时间戳前端拿到字符串后再自行截取转换跨天预约时只拼了“上午/下午”没拼日期计算全部错位。MySQL 的 TIMESTAMP 和 DATETIME 混用还会带来时区问题。处理全项目统一数据库用 DATETIME后端接口统一返回格式化字符串等待时长全部由后端计算前端只负责展示。下单接口里 period_start 必须存完整的“日期 时段起始时间”不能只存一个“AM”枚举。排查时可以先查数据库里 period_start 的实际值如果日期部分是对的再看前端有没有自己拼日期逻辑前端代码里出现 new Date(period).getTime() 这类跨层计算时就要警惕。5.5 健康趋势图画成“过山车”查下来是单位混用现象用户血糖趋势忽高忽低去年显示 5.6今年显示 101折线图吓人一跳。原因不同体检机构、不同化验设备返回的单位不统一血糖值有的用 mmol/L有的用 mg/dL。导入历史数据时没有做单位归一化直接存在同一个字段里画在了同一张图上。处理指标字典表只认一个标准单位入库前把非标准单位换算成标准值再存如果无法确定历史数据的单位宁可不在趋势图展示。前端展示时再把标准值格式化为用户习惯的单位。这个问题的坑不在代码而在数据导入环节做历史数据迁移时一定要留一个字段记录原始值和原始单位否则后续想纠错都没有后悔药。6. 把演示系统做成能长期用的体检系统灰度验证与两个工程习惯功能开发完并不代表系统能上线。我建议先做灰度验证挑一个体检科室作为试点每天只让前 20 个体检用户走小程序预约和排队其他用户仍走人工窗口。每天结束时拿小程序订单数据和前台签到表对账确认“小程序已付款数 前台签到数 报告完成数”这三条线完全一致。这一步能暴露绝大多数状态不同步问题。预约并发也要做一次简单验证。用一个 for 循环脚本模拟 100 个请求同时打下单接口然后查数据库确认没有库存扣成负数、没有同一用户生成两笔订单。如果日志里出现乐观锁冲突再看是哪个接口绕过了状态机。压测不需要复杂工具关键是验证“同一用户同一天只能一单”和“库存不为负”这两条底线。最后说两个我踩了多次后才养成的工程习惯。第一所有写操作接口都要幂等。用户在支付页手抖点两次或者运营商的网络重发请求后端绝对不能生成两笔订单。除了接口里做校验订单表还要加唯一索引兜底。第二接口返回不要只给“够用”的字段。排队接口不要只返回“前面还有几个人”最好返回队列快照数组和用户当前状态因为过两个月你可能要加“去几号诊室”“医生是谁”这些需求。我现在做这类系统做完任何一个模块都会先问自己一句“如果这个数据丢了、重复了、延迟了用户会看到什么”想清楚再提交代码。这个习惯帮我省掉了大量上线后的夜间修复希望也能帮到你少踩几个坑。本文还有配套的精品资源点击获取
