尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

微信小程序+ThinkPHP+Uniapp医院门诊智能预约平台实战复盘

微信小程序+ThinkPHP+Uniapp医院门诊智能预约平台实战复盘 微信小程序 ThinkPHP Uniapp 医院门诊智能就诊预约平台实操复盘这两年医疗健康类小程序的需求一直很猛尤其是门诊预约这块几乎每个医院、社区卫生服务中心都在做信息化改造。市面上现成的SaaS方案不少但真要落到“院内系统对接”“排班数据实时同步”“可视化运营监控”这些具体场景还是得自己搭一套才靠谱。我之前刚好完整做过一个“医院门诊智能就诊预约平台”前端用Uniapp编译成微信小程序后端用ThinkPHP 8中间还嵌了一套可视化的运营数据看板。今天不聊虚的直接把整个项目从架构设计到落地实现的细节捋一遍包括我踩过的坑和最后的解决方案。先说说这套系统到底是干什么的。患者端是微信小程序支持在线建档、门诊挂号、分时段预约、签到取号、候诊队列查询、报告查看管理端是Web后台负责科室管理、医生排班、号源池配置、预约规则设置另外还有一个可视化驾驶舱把挂号量、号源消耗率、科室热度、患者来源、高峰时段等数据全部用图表展示出来给门诊部做运营决策用。技术选型上前端Uniapp uView Plus后端ThinkPHP 8 MySQL 8 Redis可视化部分用ECharts 自研大屏布局。这套组合最核心的价值是患者少跑腿、护士少打电话、管理者能看到真实运营数据。如果你是正在做医疗类小程序、或者准备用Uniapp ThinkPHP做全栈项目、又或者纯粹是对“预约系统里的号源池和锁号机制”感兴趣这篇内容应该都能给你一些能直接抄作业的参考。我尽量讲得细一点从数据库设计讲到并发锁号再到可视化大屏的落地全程附踩坑经历。1. 整体设计与技术选型思路1.1 为什么是“Uniapp ThinkPHP”这套组合先说前端。医院门诊场景下患者用的终端非常杂有人用微信有人用支付宝还有人习惯用App。如果给每个端都单独开发一套原生应用光维护成本就够喝一壶的。Uniapp的核心理念是“一套代码多端发布”Vue语法写一遍编译成微信小程序、支付宝小程序、H5、App都行。我们实际项目里微信小程序是首发端H5和管理后台的访问端也都从同一套Uniapp代码打包出来开发效率确实高。再说后端。ThinkPHP是国产PHP框架里生态最成熟的一个8.x版本引入了注解路由、中间件、事件机制整体开发体验已经比较现代化。选择它还有一个现实原因很多做医疗信息化的外包团队和院内信息科都用PHP后续交接维护的门槛低。你要是自己玩用Java Spring Boot也行但ThinkPHP在快速迭代和中小并发场景下真的够用。Redis在这个项目里不是可有可无的。预约系统的核心是“号源不能超卖、同一患者不能重复占号”这需要原子化的库存扣减操作还要有锁号过期机制。我把号源状态和预约临时锁都放在Redis里做MySQL只做最终落库这样既不伤数据库又能扛住每天几千次的并发预约请求。1.2 三层架构和核心模块划分整个系统按“患者端小程序 - 业务后端 - 运营管理后台/可视化大屏”三层来拆分患者端小程序微信授权登录、手机号绑定、在线建档、科室/医生检索、号源日历、分时段预约、我的预约、签到取号、候诊队列、报告查询、消息通知。运营管理后台科室管理、医生管理、排班管理、号源池配置、停诊/加号、预约记录、黑名单、数据统计。数据可视化大屏挂号量趋势、号源消耗率、科室排行、患者来源结构、实时候诊人数、医生工作负荷。模块划分有一个原则我用了很久把“预约链路”和“基础数据管理”彻底拆开。也就是说科室、医生、排班这些是基础档案数据预约订单是高频业务数据两者不要耦合在同一个服务里虽然目前是单体项目但代码层面必须分层清晰。这样才能在后续扩展成微服务或者拆模块的时候不用重写业务逻辑。1.3 为什么一定要做“可视化”这块说句实话很多医院采购系统的时候功能清单里都会写“数据可视化大屏”但真正把可视化用起来的项目不多。我做的这套系统里可视化不是装饰品而是切切实实解决了一个问题门诊部负责人以前要了解今天的号源消耗情况得让信息员从后台导Excel导出后还要手工整理。有了大屏之后院长办公室、门诊部的大电视上实时滚动挂号趋势、剩余号源、各科室压力一目了然决策效率提升了好几个量级。可视化的数据来源就是预约订单表和排班表通过定时任务和实时统计两种方式聚合。实时部分查Redis里的号源缓存历史部分查MySQL的汇总表两者拼出一个完整大屏。技术栈用的是ECharts WebSocket推送后端起一个Swoole WebSocket服务也可以用Workerman把Redis里订阅的号源变化事件推送到前端大屏做到秒级更新。2. 核心功能模块设计与实现拆解2.1 患者端从微信登录到建档的完整链路微信小程序登录这步常规流程是wx.login 拿到 code后端拿 code 换 openid然后生成自定义登录态 token。这套流程本身不复杂但有几个细节不处理好后面全是坑。第一个坑是手机号授权。如果你在小程序里用button open-typegetPhoneNumber直接拿手机号后端需要先获取 access_token再调用phonenumber.getPhoneNumber接口解密。这里有个安全点access_token 一定不能存到小程序前端必须由后端持有。我见过不止一个人把 access_token 放到前端缓存里这等于把后门敞开了。第二个坑是建档信息。医院场景必须有实名制所以建档表单要包括姓名、身份证号、手机号、紧急联系人等等。小程序端要做身份证号的格式校验后端必须再做一次严格校验。身份证号最后一位可能是X提交的时候要注意大小写统一转大写。还有一个体验优化点患者首次进来不要让他填完所有信息才能浏览。我的做法是“先逛后建档”——允许游客查看科室、医生、号源情况到真正点击“预约”按钮的时候才强制登录 建档。这样把转化路径上的摩擦降到了最低。2.2 医生排班与号源池设计预约的核心地基预约系统最核心的表结构是科室表、医生表、排班表、号源表、预约订单表。这里面最容易设计错的就是“排班”和“号源”的关系。我用的模型是排班驱动号源。一个医生在某天某时段有一条排班记录这条排班记录生成一批号源比如上午放30个号每个号源有自己唯一的编号如202501160800001。患者预约时不是去抢“排班记录”这一个资源而是去抢“号源”这个原子资源。排班表的字段包括医生ID、科室ID、排班日期、开始时间、结束时间、午别上午/下午/晚、号源总数、已预约数、剩余号数、状态正常/停诊/已满。号源表其实可以不用一张独立的表存每一个号位而是用排班表里的“剩余号数 Redis原子递减”来实现。但为了后续能精确到“第几个号”做签到我还是设计了号源明细表每条预约订单记录关联一个具体的号源序号。生成号源有两种方式按时间段生成固定号数比如上午8:00-12:00每20分钟一个号段每个号段放3个号。适合普通门诊。按号序生成不限时段比如全天80个号8点开始叫号患者预约的时候只能看到“当前排到多少号”适合排队压力大的发热门诊。两种模式在排班表里用一个字段区分后端预约逻辑统一处理。2.3 预约、锁号与防超卖机制Redis事务详解这块是整个系统最核心、也最容易出并发问题的部分。先说需求同一个患者同一时段不能重复预约号源不能超卖预约成功但未支付的号需要在一定时间内释放锁号15分钟15分钟不支付自动解锁。实现方案是“Redis预占 MySQL落单”的两段式提交。第一步用户发起预约请求后端检查 Redis 里的预约锁。这个锁的 key 设计成patient:{userId}:{date}:{period}value 存预约人ID和过期时间。用SETNX尝试加锁如果返回1说明没有重复预约万事大吉如果返回0说明你已经在这个时段约过了直接拦截。第二步对号源做原子递减。Redis key 是schedule:{scheduleId}:remain用DECR操作扣减余号。这里的关键是DECR之后要拿返回值判断是否小于0小于0说明超卖了需要把刚才的DECR回滚INCR回去并且提示“该时段已约满”。这一步不能用“先GET后SET”的方式因为并发下GET的结果已经过期了必须原子操作。第三步把预约订单信息写入MySQL。这里需要注意MySQL落库前要先查一下当前排班表剩余号数防止Redis和MySQL数据不一致比如后台人工调整了号源。落库成功后ACK到Redis把临时锁改成正式状态有效期延长到预约就诊日结束。这套机制实测下来在模拟并发500个用户同时抢一个医生号源的时候没有出现一条超卖记录。如果你只是做毕业设计或者小流量系统可能觉得用不到这么复杂但一旦真实上线这套“Redis预占”机制能救命。2.4 签到与候诊队列小程序端的实时互动预约只是第一步患者当天到了医院还要签到取号然后进入候诊队列。这里有两个实现要点一是签到方式。我们提供了两种到院扫码签到和手动点击签到。扫码签到的码是动态生成的二维码内容是一个带签名参数的URL后端验签后确认有效。这里一定要做时间戳有效期限制比如二维码5分钟内有效防止有人截图转发。二是候诊队列的实时推送。小程序不像Web页面能保持长连接微信的wx.connectSocket在切后台会被回收。我的方案是进入候诊队列页面时开启 WebSocket订阅当前科室的叫号频道同时每15秒调一次 REST 接口拉取最新候诊序号作为兜底。这样即使WebSocket断了界面数据也不会停更。实际操作中腾讯云的WebSocket服务和ThinkPHP的Swoole服务我们都跑过稳定性和延迟都还行。候诊队列的数据结构也不复杂诊室当前叫号数 患者当前排队序号前端展示“前面还有X人”。这个“X人”不是简单用“当前排队序号减去当前叫号数”还要排除已过号、已退号的患者。所以我在队列里维护了一个状态机待签到 - 已签到待候诊 - 叫号中 - 过号 - 就诊中 - 已完成。每次叫号操作都会把状态机往前推进一步前端拿到的永远是经过状态过滤后的真实排队人数。3. Uniapp 前端实现细节与 HBuilderX 插件实践3.1 Uniapp 项目初始化与 uView Plus 引入别踩插件市场的坑前端工程用 HBuilderX 创建的时候选“默认模板”还是“Hello Uniapp模板”是个小分水岭。我建议不勾选任何内置示例页面直接生成空白项目因为模板里的示例代码和多余样式会在后续替换的时候挡住你的视线。uView Plus 是 uView 的 Vue3 版本专门给 Uniapp 用的 UI 组件库内置了轮播图、表单校验、弹窗、步骤条、日历等组件开发效率提升非常大。引入uView Plus有两条路径插件市场直接导入在 HBuilderX 的插件市场里搜索“uView Plus”点导入插件它会自动把组件库放进你的项目。这种方式最方便版本管理也由插件市场负责。npm 安装适合项目已经是 CLI 创建方式的情况命令是pnpm add uview-plus然后在main.js里app.use(uviewPlus)在uni.scss里引入主题变量。实际用过之后我的建议是能用插件市场导入就用插件市场因为 Uniapp 项目在 HBuilderX 中运行和打包时对 npm 包的解析兼容性问题比正常 Vite 项目多。最容易踩的坑是npm 安装后提示“文件查找失败uview-plus/xxx”十有八九是路径配置或 easycom 规则没配好。3.2 easycom 规则配置解决组件自动引入问题uView Plus 文档里会要求你在pages.json里配置easycom这个配置的作用是让 Uniapp 在编译时自动识别页面上用到的 uView 组件标签自动引入对应组件文件不用每个页面手动 import。我的配置是这样的easycom: { autoscan: true, custom: { ^u-(.*): uview-plus/components/u-$1/u-$1.vue } }这个配置能让u-button自动映射到uview-plus/components/u-button/u-button.vue。但有几个注意事项项目根目录的uni_modules目录下如果没有 uview-plus 组件文件路径会失效。插件市场导入的 uView Plus 一般会把组件放在uni_modules/uview-plus下面。如果同时用了uni_modules里的其他组件库autoscan 扫描阶段偶尔会报“easycom 组件冲突”解决办法是检查两个库是不是都有同名的u-前缀组件。使用 easycom 自动引入的组件在代码里不需要显式 import但如果你开了 ESLint可能会被提示“组件未定义”这就需要把u-前缀组件加到 ESLint 的全局变量列表中。3.3 小程序顶部导航栏适配问题微信小程序的顶部导航栏高度是个老生常谈但非常容易出错的点。不同机型状态栏高度不一样iPhone X 及以上机型有刘海Android 厂商各自为政导航栏高度计算不对页面头部的布局就会错位。我的适配方案是const { statusBarHeight } uni.getSystemInfoSync() // 胶囊按钮位置信息 const menuButtonInfo uni.getMenuButtonBoundingClientRect() // 导航栏总高度 状态栏高度 胶囊按钮高度 上下留白 const navBarHeight (menuButtonInfo.top - statusBarHeight) * 2 menuButtonInfo.height这个公式的推导过程是胶囊按钮在导航栏中垂直居中所以胶囊顶部到状态栏底部的距离等于胶囊底部到导航栏底部的距离。那么导航栏总高度就等于 状态栏高度 胶囊高度 两倍的胶囊与状态栏间距。在写自定义导航栏组件的时候这个高度计算要放在onLoad里执行一次然后存到 Vuex / Pinia 里全局共享。注意不同机型的状态栏高度可能不一样Android有全面屏和虚拟按键之分不能写死。3.4 微信小程序单选框与表单交互优化医疗预约表单里有性别、医保类型、证件类型这些选项。微信原生用的是 radio-group但样式特别丑。uView Plus 里提供了u-radio-group和u-checkbox-group配合u-form的表单校验插件整体体验能做得非常顺滑。这里有一个表单校验的小技巧身份证号码的校验不能只用正则还要做校验码验证。身份证18位最后一位是校验码算法是加权求和后对11取模。我在前端和后端都实现了同一个校验函数前端只提示错误后端直接拒绝双保险。另外一个容易被忽略的点是表单里的“勾选同意《用户协议》”必须做成阻止默认事件的二次确认。如果用户没勾选就点了提交按钮不要用uni.showToast一闪而过的提示而是要把未勾选的文字标红抖动并弹窗提示“请先阅读并同意协议”。这个细节看似小事但在医疗类应用审核时是合规的硬性检查项。3.5 UniApp 打包与上架微信小程序的完整流程Uniapp 微信小程序打包常规操作是HBuilderX 菜单栏点击“运行 - 运行到小程序模拟器 - 微信开发者工具”这一步会把代码编译到dist/dev/mp-weixin目录下。如果本地没装微信开发者工具会提示生成编译产物让你手动打开。发行发布版时要选择“发行 - 小程序-微信”会生成dist/build/mp-weixin然后在微信开发者工具里导入这个目录填写 AppID点击“上传”。这里的坑集中在两点第一在 HBuilderX 里配置的 AppID 和微信开发者工具里的 AppID 必须一致。不一致会出现“project.config.json 里的 appid 无效”的报错。一般是在manifest.json的“微信小程序配置”里填AppID编译的时候会自动生成 project.config.json。第二勾选“ES6转ES5”和“上传时压缩代码”。如果你的小程序包体超过2MB的主包限制除了分包加载之外压缩选项也能帮你省不少空间。实际操作中我遇到过因为误开了“运行时不校验合法域名”而在真机上访问不到接口这个选项只在开发调试时勾选正式发布前必须关掉。还有一个很多人搞不清楚的问题Uniapp 的项目要上架安卓应用市场怎么办实际上 HBuilderX 的“发行 - 原生App-云打包”可以生成 apk/aab 包然后去各安卓商店走应用上架流程。这里特别提醒应用商店审核要求隐私政策弹窗Uniapp 生成的 App 里要确保隐私弹窗在你所有隐私接口调用之前出现不然会被市场审核打回。4. ThinkPHP 后端接口设计与系统实现4.1 ThinkPHP 项目运行环境与目录结构ThinkPHP 8 要求 PHP 8.0建议直接用 PHP 8.1 或 8.2。项目安装用 Composercomposer create-project topthink/think tp开发环境我用的是 LaragonWindows和 DockerLinux前者适合本地快速跑后者适合部署。ThinkPHP 的入口文件是public/index.phpNginx 配置里要把 root 指向public目录并配置伪静态location / { if (!-e $request_filename) { rewrite ^(.*)$ /index.php?s$1 last; } }如果不配伪静态所有接口都要带index.php路径小程序端请求地址会非常丑而且某些微信审核情况下还容易被判定为不规范。ThinkPHP 8 的多应用模式非常实用。我建了三个应用index小程序端接口、admin管理后台接口、api公共接口每个应用有独立的控制器、模型和中间件。目录大概是这样的app/ ├── index/ │ ├── controller/ │ ├── model/ │ └── middleware/ ├── admin/ ├── api/ └── common/4.2 请求参数校验与统一返回格式小程序端的接口必须做到“参数校验前置统一错误码返回”。我自己封装了一个BaseController所有子控制器继承它。返回格式统一为{ code: 200, msg: success, data: {} }参数校验用 ThinkPHP 内置的validate类。举个实际例子预约接口的校验规则是这样的protected $rule [ schedule_id require|number, patient_id require|number, period require|in:morning,afternoon,evening, visit_date require|date, ]; protected $message [ schedule_id.require 排班ID不能为空, period.in 预约时段参数错误, ];注意一个细节日期类型不能只用date校验因为2025-02-30这种不存在的日期也能通过date校验。我加了一个自定义校验器去查日历表确认这个日期真实存在且不是周末有些科室周末不开诊。4.3 Redis 在微信登录态和预约锁场景的具体用法微信登录态我存的是 token 映射到 openid 的结构。用户登录成功后后端生成一个 32 位随机 token以token:{token}为 keyopenid 为 value 写入 Redis过期时间设为 7 天。小程序端每次请求带Authorization: Bearer {token}后端通过中间件换取 openid。这样做的好处是用户修改密码、被封禁时可以主动失效 token不需要改小程序端的代码。预约锁的实现具体代码如下// 检查有无重复预约锁 $lockKey patient:{$userId}:{$visitDate}:{$period}; $isLocked Redis::setnx($lockKey, $scheduleId); if (!$isLocked) { return json([code 400, msg 您已预约该时段请勿重复操作]); } Redis::expire($lockKey, 900); // 15分钟锁号 // 扣减号源 $remainKey schedule:{$scheduleId}:remain; $remain Redis::decr($remainKey); if ($remain 0) { Redis::incr($remainKey); Redis::del($lockKey); return json([code 400, msg 该时段号源已约满]); }设置锁过期时间必须在setnx成功之后立即执行否则如果用户在setnx后、expire前断开了连接这个锁就永远不释放了。真实项目中我用 Redis 的SET key value EX 900 NX一条命令完成这两个操作这是最稳妥的。Redis 锁的 key 设计还有个技巧要把“患者维度”和“排班维度”分别考虑。患者维度防重复约排班维度防超卖。两者是两条独立的key不要混在一个key里。4.4 可视化数据接口的聚合查询与缓存设计可视化大屏的数据接口不能直接查订单明细表那样大屏每次刷新都全表扫描数据库压力扛不住。我建了一套“统计汇总表”定时任务每5分钟跑一次把订单数据按日期、科室、医生维度聚合好。大屏接口只查这些汇总表性能每分钟复杂度都是常数级。聚合逻辑大概三类挂号量趋势按就诊日期分组统计订单数再用 ECharts 的折线图展示。科室热度排名按科室分组统计预约量用横向柱状图显示 Top10。号源消耗率按排班表对比已约数和总数计算百分比用环形图展示。Redis 在大屏这里的作用是缓存热点数据。比如当前小时内的实时预约量每次订单创建成功时INCR hourly:{date}:{hour}大屏接口直接读这个值不用查数据库。缓存过期策略设为当天24点自动失效第二天自动重新计数逻辑非常清爽。WebSocket 推送部分我用 ThinkPHP 的think-swoole扩展启动了一个独立服务监听 9501 端口。每当预约接口成功落单就往某个频道发布一条channel:hospital:booking消息大屏端订阅这个频道后更新看板数据。这个联动初看有点复杂但跑通了之后院长办公室大屏上“当前预约人数”那个数字是实时跳动的观感完全不同。5. 可视化大屏实现要点与 ECharts 实战5.1 大屏布局设计先从“看什么”出发再谈“怎么画”可视化大屏最忌讳的就是一上来就堆图表。我总结的布局设计原则是从上到下回答三个问题——今天整体情况怎么样、哪个科室压力大、号源和候诊趋势如何。我的大屏分成四块顶部标题栏 当前时间 当日预约总量 完成就诊量 平均候诊时长三个核心KPI数字。左侧科室热度榜横向条形图 号源消耗率环形进度图。中间当日挂号量趋势折线图按小时粒度。右侧患者来源结构饼图/环形图 实时候诊队列滚动列表。这个布局的逻辑是核心KPI最显眼一眼知道今天忙不忙左侧回答“哪里忙”中间回答“什么时段忙”右侧回答“患者从哪来、当前堵在哪”。整个大屏用 1920x1080 分辨率设计为了保证在不同尺寸的电视上不变形我开发的时候全部用百分比和 flex 布局没有写死像素。5.2 ECharts 在微信小程序里跑起来的正确姿势ECharts 的官方示例都是跑在浏览器里的微信小程序里没有 DOM 和 Canvas 的原生接口直接用会报错。解决方案是用echarts-for-weixin这个适配组件它把 ECharts 的初始化逻辑封装成了微信小程序的组件。在 Uniapp 里用 ECharts 有两种方式使用lime-echart这是 Uniapp 生态里比较成熟的 ECharts 封装支持 Vue2 和 Vue3npm 安装后就能用底层是 canvas小程序和App端都支持。使用 renderjsUniapp 的 renderjs 可以直接调用浏览器的 DOM 接口在H5端效果最好但在微信小程序端不完全可用。我实际用的是 lime-echart。基本用法是view classchart-container l-echart refchartRef finishedonChartFinished / /viewimport * as echarts from echarts import LChart from lime-echart // 初始化图表 const chart this.$refs.chartRef.init(echarts) chart.setOption({ tooltip: { show: true }, xAxis: { type: category, data: hours }, yAxis: { type: value }, series: [{ type: line, data: counts }] })注意l-echart的 init 必须在组件挂载完成之后调用而且 canvas 的父容器必须有确定的宽高不能是 0 或者 undefined。在微信小程序里如果遇到图表不显示的情况八成是 canvas 的宽高没设置对或者初始化时机太早。5.3 大屏数据更新的三种刷新策略大屏上不同的图表更新频率需求不一样秒级更新实时候诊数WebSocket 推送。分钟级更新挂号趋势、号源消耗定时器每 60 秒轮询一次 REST 接口。小时级更新患者来源、科室排行数据变化慢每 5 分钟拉一次就够。轮询的实现有一个细节一定要设置定时器清理。在 Uniapp 小程序里页面onUnload时必须 clearInterval 和关闭 WebSocket不然切后台、退出页面后定时器还在跑会报错或者泄漏内存。我见过不少人在小程序里不清理定时器最后导致页面卡死、电池疯狂耗电。还有一点大屏接口需要做跨域。管理后台部署的域名和接口域名可能不同ThinkPHP 侧需要设置跨域头header(Access-Control-Allow-Origin: *); header(Access-Control-Allow-Methods: GET, POST, OPTIONS); header(Access-Control-Allow-Headers: Content-Type, Authorization);注意生产环境不要把Access-Control-Allow-Origin直接设成*最好是配白名单防止任意网站都能请求你的大屏数据。6. 常见问题与排查经验全是实地踩过的坑6.1 微信小程序里页面列表加载更多总是不触发小程序列表分页加载最常见的问题是“触底事件不触发”。排查方向有三个第一确认滚动容器是页面本身而不是某个view。Uniapp 的onReachBottom只在页面级滚动时生效如果你把列表放在一个固定高度的scroll-view里要改用scroll-view的scrolltolower事件。第二检查onReachBottomDistance是否设置合理默认50px如果你列表底部有空白或者遮挡元素可能导致永远达不到阈值。我的做法是设为100-150px提前触发体验更顺滑。第三最重要的一个坑当接口返回的数据不足一屏时onReachBottom 永远不会触发因为你已经到底了但没有滚动事件。解决办法是在onLoad后先判断列表数据是否填满一屏如果没填满就自动加载第二页直到填满或没有更多数据为止。这个逻辑是真实上线时被用户骂了“为什么只显示5条就不加载了”之后才补上的。6.2 微信开发者工具不出预览二维码或者预览报错经常遇到的情况是开发者工具里点击“预览”二维码显示不出来或者扫码后提示“无法打开”。排查顺序微信开发者工具的“详情 - 本地设置”里确认“将JS编译成ES5”、“上传时压缩代码”都没问题。确认调试基础库版本不能太低建议 3.0 以上。有些新组件库在老版本基础库下直接白屏。预览二维码的有效期只有30分钟如果长期未刷新二维码过期了重新点一次预览。如果你是用 HBuilderX 编译到开发者工具每次编译前最好先关掉开发者工具里的自动预览模式否则频繁刷新可能导致开发者工具卡死。还有一个坑二维码扫码版和体验版不是一回事。扫码预览是临时的开发机上能看到但给客户试用还是要“上传代码 - 设为体验版”体验版成员只能在“成员管理”里添加并且需要绑定手机号才能扫码。之前有次客户反馈说“扫码打不开”查了半天发现是没把他加为体验成员白折腾一场。6.3 预约接口偶发超时排查是数据库连接池问题上线第二周用户反馈高峰期预约偶尔转圈很久。我查了日志发现是 MySQL 连接数打满了。原因很简单ThinkPHP 每个请求默认使用单例数据库连接但并发高的时候慢查询会把连接占满。解决方案做了三个给高频查询加上 Redis 缓存比如医生列表、科室列表、排班日历这些数据变化频率低完全没必要每次查库。预约落单的 SQL 优化把原来的“先查排班再更新剩余号”改成一条 UPDATE 语句带上剩余号数条件比如UPDATE schedule SET remain remain - 1 WHERE id ? AND remain 0用受影响行数判断是否成功。开启 ThinkPHP 数据库断线重连功能配置break_reconnect true避免偶发的 MySQL 连接超时导致接口报错。这里要强调MySQL 的UPDATE ... WHERE remain 0是防止超卖的第二道保险即使 Redis 锁失效数据库层也能兜底。双保险机制上线后预约接口的稳定性明显提升高峰期再也没出现超时。6.4 管理后台 echarts 图表不渲染是容器初始化时隐藏了管理后台用的是 Vue3 ECharts有过一次图表不显示的奇葩问题页面加载时图表容器处于隐藏状态v-if false等数据拿到后才显示结果图表画布白屏。原因ECharts 初始化的时候容器宽高为0它计算 canvas 尺寸失败后续即使容器显示了画布也不会自动重绘。解决方案有两个在容器显示后再执行chart.resize()手动触发重绘。用nextTick或者v-if确保图表容器渲染完成后再init。在 uniapp 小程序里也是同理l-echart的 init 时机如果遇到页面隐藏最好也用延迟初始化或者onShow时补一次resize。6.5 Redis 可视化管理工具的选型参考排查 Redis 问题的时候命令行redis-cli虽然够用但效率低尤其要看 key 的 TTL、内存占用、热 key 分布时非常痛苦。我用过的可视化工具有Another Redis Desktop Manager免费开源跨平台支持查看 key 的 TTL、value 的 JSON 格式化日常排查完全够用。推荐。Redis InsightRedis 官方出的界面好看支持查询分析、内存分析但启动内存占用大一些。Tiny RDM新出的国产工具颜值高轻量支持 SSH 隧道连接适合连生产环境的跳板机场景。无论用哪个工具线上环境一定不能把 Redis 绑定 0.0.0.0 并开放公网要设置密码并且只允许内网访问。我有一次自己测试服务器 Redis 没设密码结果被挖矿程序扫到直接种了木马CPU 飙到 100%血的教训。还有就是生产环境不要把数据库名、key 前缀设计得太简单排查问题时看到一堆k1、k2这种 key 能崩溃。7. 上线部署与后续演进建议7.1 部署架构一台服务器怎么跑完整套系统很多项目初期就一台云服务器2核4G也能跑前提是配置合理。我的部署拓扑是Nginx PHP-FPM MySQL Redis 全部在一台机器上但进程和端口分开。Nginx 负责处理静态文件和转发 PHP 请求MySQL 只监听内网端口Redis 同样不对外。部署流程里有一个坑ThinkPHP 的目录权限。runtime目录需要可写否则会报“目录没有写入权限”。用chmod -R 755 runtime不够最好把 owner 改成 www 用户PHP-FPM 的运行用户不然上传图片、生成日志都会失败。小程序端接口域名必须是 HTTPS并且要在微信公众平台配置服务器域名白名单。这个配置在“开发管理 - 开发设置 - 服务器域名”里request 合法域名、uploadFile 合法域名、socket 合法域名都要分开配。如果你用 webSocket 推送候诊队列要记得在 socket 合法域名里加。7.2 微信小程序年审与版本更新注意事项小程序每年都要做年审否则会被限制新版本发布和部分功能使用。这里有几个经验年审要提前做准备审核周期一般是1-7个工作日别等到要发新功能了才想起来。医疗类目的小程序资质要求比普通类目严格需要提供《医疗机构执业许可证》或者相应的互联网医院资质。这个在类目选择阶段就要搞清楚不然后期封禁类目就麻烦了。版本更新后建议先灰度发布在微信公众平台把版本设为“分阶段发布”先给内部成员体验没问题再全量。我遇到过新版本引入了一个严重的样式错乱因为提前灰度了影响范围控制在最小。7.3 后续功能扩展方向从预约到全流程服务这套平台跑通后我计划中的扩展方向有三个一是支付闭环。目前预约是线上占号、线下缴费后续要接入微信支付实现在线支付挂号费。技术上不复杂但要注意退号退费的对账逻辑以及“爽约”费用的扣除规则是否符合当地卫健委的政策。二是智能推荐。基于患者的就诊历史和科室热度在患者选择科室时推荐“最合适”的科室或医生。这个功能可以用简单的协同过滤实现不需要上大模型但数据积累到一定程度后建议做个性化引导。三是接入院内 HIS 系统。让预约平台的挂号和院内 HIS 的号表实时同步避免“线上显示有号、线下已经约满”的乌龙。这个接口一般由院内信息科提供不同厂商的 HIS 接口标准不一样最稳妥的方案是让 HIS 提供一个 REST 接口平台调用后以 JSON 格式返回号源数据。如果你是自己模仿着做这个项目我强烈建议把“预约锁号”和“可视化”这两个点做扎实它们是最能体现系统复杂度和工程能力的地方。前者体现稳定性后者体现数据思维两者都做好这个项目就已经超过市面上不少外包团队交付的水平了。
返回列表