
基于微信小程序的社区医院药品库存管理和自动提醒系统的设计与实现社区医院药房是个很容易被低估的地方。我去过不少社区医院调研发现很多药房的管理方式还停留在手工台账Excel的阶段入库靠手写出库靠划勾盘点靠人海战术。药品过期了没人知道库存积压了也没人察觉等到月底对账才发现一批药已经报废。这个问题不是小诊所的专利一些规模不小的社区医院同样如此。所以当我接到一个社区医院药品库存管理的项目需求时第一反应就是必须做一套能自动提醒的系统而且终端最好用微信小程序。理由很简单——药房工作人员和社区医生手机上都有微信不需要额外装App管理员发一条订阅消息就能触达所有人开发成本还远低于原生应用。这篇文章把整个项目的设计与实现过程完整拆开讲包括为什么这么设计、核心数据表怎么建、小程序端哪些细节容易被坑、自动提醒的完整链路怎么打通以及部署上线时需要注意的事情。无论是拿去做毕业设计还是真给社区医院做信息化改造都能派上用场。1. 社区药房药品管理的真实痛点与微信小程序切入点1.1 药库现场到底在乱什么先说我调研到的实际情况。一家服务两三万居民的社区医院药房常备药品大概在500到800种之间加上不同规格、不同批次库存记录条数能到几千条。库存周期从几天到几个月不等这里面最要命的就是近效期药品——一批药生产日期早、有效期短如果没人盯着很容易就放过期了。我在一家社区医院看到他们的做法每月月底药剂师把药品按有效期排序把三个月内到期的品种抄下来再逐一核对库存数量决定是退回供应商还是继续使用。这个流程听起来不复杂但非常耗时而且容易漏。特别是那些周转慢的应急药品例如某些解毒剂、抗过敏药平时几乎不动等要用的时候才发现过期了这就不是经济损失的问题而是用药安全问题。另一个痛点是信息不透明。医生开处方时不知道某药库存还有没有药剂师不知道哪些药快用完了该进货院长想知道各科室药品消耗情况但数据都在一堆纸质单据里。任何一方想了解信息都要跑一趟药房去翻账本。这种模式下药品的效期管理、库存上下限预警、消耗统计全部依赖人工出错是常态不出错才是运气。1.2 为什么是微信小程序而不是App或Web端最初考虑过两个方向做一套PC端Web管理系统或者做原生App。后来都推翻了。Web端确实适合管理员做库存录入、统计报表但社区医院的药房工作人员每天在药架间来回走动不可能一直坐在电脑前操作。药品入库时手机扫一下码、出库时点两下屏幕这种移动化的操作需求Web端满足不了。原生App功能上没问题但社区医院没有专门的信息化运维人员让工作人员装App、注册账号、升级版本每一样都是麻烦。还有一个最现实的问题App得单独做iOS和Android两套开发周期和成本直接翻倍。微信小程序把这个矛盾完美化解了。用户通过扫码或搜索就能打开用完即走不需要安装微信自带的订阅消息能力可以直接用来做药品过期提醒和库存预警省掉了自己搭推送通道的成本而且小程序天生支持摄像头扫码药品条码扫描这类功能实现起来很顺手。对社区医院来说微信已经是员工日常使用的工具学习成本几乎为零。顺便说一句如果你是在做毕业设计选微信小程序还有个隐藏的好处前后端分离的开发模式和真实企业项目高度一致答辩的时候能讲清楚为什么这么选比做了什么功能更能拿分。我从项目一开始就确定了技术栈小程序端用原生框架也可以用uni-app后面细说后端用Spring Boot数据库选MySQL部署在一台低配服务器上。这个组合足够稳定社区医院的并发量根本不是什么压力。2. 系统角色、模块与业务流程的整体设计2.1 三个角色职责边界要划清楚整个系统围绕三个角色设计系统管理员、药房操作员、普通员工医生/护士。一开始我也纠结过要不要加一个供应商角色让供应商也能登录查看库存情况后来项目评审时被否决了——社区医院根本不想让供应商直接看到进销存数据他们更希望由药剂师手工把进货需求发给供应商。所以不要把流程设计理想化以实际业务为准。三个角色的权限边界如下角色核心权限说明系统管理员用户管理、药品档案维护、预警阈值配置、全量数据查看通常是院长或药房主任药房操作员入库、出库、盘点、近效期预警处理在药库现场操作的药剂师普通员工库存查询、药品信息查看医生开处方前查库存用这里有一个很容易被忽略的设计细节普通员工只能查库存不能看采购价和供应商信息。从商业和数据安全角度都说得通我在数据库设计中特意把采购相关的字段放到了单独的视图层级接口层面做了字段过滤后面讲到权限校验时详细说。2.2 功能模块拆分从入库到提醒是一条完整链路功能模块我按业务流拆成六个板块基础档案管理药品信息维护包括药品名称、通用名、规格、生产厂家、批准文号、包装单位、库存上下限预警阈值。入库管理手动录入或条码扫描入库记录生产批号、生产日期、有效期、入库数量、供应商信息。出库管理按批号出库支持拦截近效期药品。出库时要遵守近效期先出原则下面会讲。库存盘点支持按药品盘点、按批次盘点盘盈盘亏自动生成调整记录。预警提醒药品库存低于下限、药品有效期不足N天、药品已过期三种情况分别触发不同级别提醒。统计报表库存流水、月度消耗排行、近效期药品清单、过期损耗统计。2.3 核心业务流程近效期先出是药品库存的灵魂药品库存和普通商品库存最大的区别在于批次管理。同一盒布洛芬一批是2025年5月到期另一批是2026年3月到期出库时如果先拿2026年的2025年那批就会压着压着就过期了。所以整个出库流程必须锁定近效期先出FEFOFirst Expired First Out原则。我的出库流程是这样的操作员选择药品系统自动列出当前所有非空批次按有效期从近到远排序然后引导操作员从最近效期的批次开始出库。同时前端做了一道人机校验如果出库的批次效期在90天以内并且库存充足页面弹出二次确认框该批药品90天内到期是否确认出库防止操作员手滑。入库流程相对简单但有一个细节值得注意同一批次的药品允许分多次入库。比如供应商一天内送了两趟货操作员第一次入库20盒、第二次又入库30盒这两条记录虽然是同一个生产批号但应该拆成两个批次记录来管——因为它们的入库时间不同追溯时信息不能混。我在设计数据库时特意给批次表加了一个自增ID而不是用生产批号做唯一键就是为了应对这种情况。预警流程是系统主动触发的。后端每天凌晨跑一次定时任务扫描所有批次的有效期和库存数量把满足预警条件的记录生成待办消息通过微信订阅消息推送给相关角色。后面第5章我会把这条链路完整展开。3. 批次级库存数据模型一张表撑起整个预警体系3.1 核心数据表设计思路数据库设计是整个系统的地基这里我踩过不少坑直接给出经过修正的最终版本。核心表有五张。药品信息表drug_info存药品静态属性。关键字段包括drug_code院内药品编码不是国药准字号是医院自己编的内部码、drug_name、spec、dosage_form剂型、manufacturer、approval_no批准文号、low_stock_threshold、expiry_alert_days。最后两个字段就是预警配置为什么放在药品表而不是单独的表因为社区医院药品总量有限整个系统就几百种药不需要做复杂的预警规则引擎直接在每个药品上配置库存低于多少盒提醒提前多少天提醒效期就足够了简单直接。药品批次表drug_batch这是整个系统最核心的表。CREATE TABLE drug_batch ( batch_id BIGINT PRIMARY KEY AUTO_INCREMENT, drug_code VARCHAR(50) NOT NULL, batch_no VARCHAR(100) NOT NULL COMMENT 生产批号, production_date DATE, expiry_date DATE NOT NULL, initial_qty INT NOT NULL COMMENT 初始库存, current_qty INT NOT NULL COMMENT 当前库存, supplier VARCHAR(200), status TINYINT COMMENT 1有效 2冻结 3用完, create_time DATETIME, KEY idx_drug_expiry (drug_code, expiry_date) );注意我加了一个status字段。当一批药品数量归零时不是直接删除记录而是标记为3用完这样流水记录里的外键引用不会断回溯历史记录时信息仍然完整。idx_drug_expiry这个联合索引是出库查询和预警查询都会用的别省略。出入库流水表stock_record记录每一次库存变动。字段包括drug_code、batch_id、record_type1入库/2出库/3盘点调整、change_qty正负号区分入出、operator操作人、create_time、remark。这张表只做插入不做更新和删除。谁改库存、什么时候改、改了多少永久留痕。药品追溯的时候全靠它。用户表app_useropenid、user_name、role_id、phone、active_flag。这里有个细节用户是管理员提前录好的还是自己注册的社区医院场景下我建议管理员后台统一录入员工首次打开小程序时通过微信登录并绑定手机号后台审核通过后激活。如果完全开放注册医生离职后账号就没法清理权限边界会失控。3.2 为什么批次表能撑起整个预警体系预警逻辑其实不复杂但前提是数据模型支持。我把预警类型拆成三种低库存预警针对药品汇总各有效批次的current_qty与drug_info.low_stock_threshold比较低于阈值触发。近效期预警针对单个批次expiry_date与当前日期相差天数小于drug_info.expiry_alert_days时触发。过期预警expiry_date已过current_qty 0触发最高级别提醒。这三种预警都可以在批次表上用一条SQL查出来-- 近效期预警示例查询90天内到期且仍有库存的批次 SELECT d.drug_name, b.batch_no, b.current_qty, b.expiry_date FROM drug_batch b LEFT JOIN drug_info d ON b.drug_code d.drug_code WHERE b.status 1 AND b.current_qty 0 AND b.expiry_date BETWEEN CURDATE() AND DATE_ADD(CURDATE(), INTERVAL 90 DAY) ORDER BY b.expiry_date ASC;这也是为什么我说一张表撑起整个预警体系——只要批次表的数据是准的预警的逻辑就极其干净。3.3 高并发扣库存的坑要的是原子性不是复杂锁社区医院药房同时操作的人数不会很多但药品出库这件事仍然存在并发场景两个窗口同时给患者发药都在扫同一批药品的条码如果没有限制就会发生库存被扣成负数的情况。我最初想到的方案是给整条批次记录加数据库悲观锁SELECT ... FOR UPDATE简单粗暴。后来想了想这种做法的风险是持有锁期间万一代码抛异常没释放锁整个药房的操作就卡死了。更稳妥的方式是利用MySQL的原子更新UPDATE drug_batch SET current_qty current_qty - #{qty} WHERE batch_id #{batchId} AND current_qty #{qty}current_qty #{qty}这个条件就是隐形的乐观锁。如果受影响行数为0说明库存不够业务层直接提示库存不足并终止后续流程。配合事务先做这条UPDATE再插入stock_record流水两件事要么都成功要么都失败。这里有一个我实测踩过的坑千万别先查再改。很多新手写代码习惯先SELECT查一下库存够不够够就再UPDATE这在并发下必出问题——两次查询之间数据可能已经被别人改了。原子更新一条SQL就解决不要想复杂了。4. 小程序端实现登录态、列表加载更多与录入表单4.1 微信登录与角色绑定的完整流程小程序端的登录逻辑用一句话概括就是前端调wx.login()拿到临时code传给后端后端拿这个code去微信接口换openid。之后每次请求都带上后端签发的自定义token后端通过token识别用户身份。这里有几个容易踩坑的点第一wx.login()生成的code只能用一次五分钟内有效。很多新手把code存在全局变量里反复用就会遇到接口返回40029之类的错误。正确做法是每次需要登录态的时候都重新调wx.login()。第二用户信息获取要克制。微信官方从2021年起不再默认返回用户头像昵称的授权弹窗现在用户头像昵称的获取方式是头像昵称填写能力——用户自己点一个组件手动选择头像和昵称。旅游、工具类的项目可以不处理这个但社区医院系统里最好还是有个实名登记毕竟涉及药品安全。我这里的做法是基础登录只拿openid在用户设置页面用官方提供的button open-typechooseAvatar让用户主动完善头像昵称后台管理员审核后分配角色权限。第三token过期要自动续期。我在后端给token设了7天有效期小程序端请求封装里统一处理401状态码——遇到401就重新走一遍登录流程然后重放失败的那个请求用户无感知。这个请求封装怎么写第6章专门说。4.2 库存列表加载更多的实现别被onReachBottom坑了库存列表是小程序端使用频率最高的页面。几百种药品如果一次性全部渲染页面直接卡死。分页加载是必然选择。小程序的分页核心是onReachBottom生命周期——页面滚动到底部时触发。我的页面逻辑是维护一个page变量当前页码和hasMore标志是否还有下一页。首次进入页面page 1调用loadList(1)。onReachBottom触发时如果hasMore为truepage并加载下一页。每次加载返回的数据条数小于每页数量比如每页20条返回10条就把hasMore置为false。看起来很简单实操中有一个特别隐蔽的坑在某些安卓机型上onReachBottom会在页面初始渲染时误触发一次。表现就是页面刚打开还没来得及操作page已经变成了2。原因是页面内容不足一屏时微信会把页面已经到底部当成一次滑动到底事件触发onReachBottom。解决方式是引入一个首屏加载完成的标志位首次加载完成后至少等500毫秒再放开onReachBottom的处理逻辑onReachBottom() { if (!this.pageReady || !this.hasMore || this.loading) return; this.page; this.loadList(this.page); } // 在列表渲染完成后的setData回调里设置 this.pageReady true这个坑不吃一次真的很难发现——用户不会每次都跟你在同一台手机上测试等你上线后收到反馈页面数据重复划两下就到底了再排查会花不少时间。列表页还有一个性能要点大批量数据不要用setData直接传整个数组追加。比如库存列表渲染50条记录setData时只传新增的那20条用数组拼接的方式更新而不是把整个50条重新set进去。差量更新在小程序里的性能提升非常明显特别是低端安卓机上。4.3 药品录入表单从扫码到手动兜底入库扫码是提升操作效率的关键功能。药品包装上通常有商品条码69开头的EAN-13码用小程序内置扫码能力扫一下就能自动填充。但注意——药品的商品条码和药品名称不是一一对应的。同一款药不同规格、不同厂家生产的条码完全不同系统里可能根本没有这个条码的档案。所以扫码只作为一个辅助录入手段扫到能匹配的药就直接带出匹配失败的还是要走手动录入流程。扫码组件我用了wx.scanCode。这里有一个体验相关的细节wx.scanCode是全屏扫码界面药剂师一天可能扫几十次每次都要对准、扫描、等待跳转效率还是偏低的。后来我把流程改成连续入库模式扫码后不跳转直接在当前页面上追加一条待提交的入库记录药扫完了统一提交。一扫码就跳详情、填完再扫下一个的模式用户用几次就会骂人。批量操作场景下的交互流程设计真的很重要。手动录入表单里有一个反直觉的字段批准文号。一般人的直觉是录国药准字Z2005xxxx或是H开头的那串就能当唯一标识但实际情况是同一药品不同生产厂家的批准文号不同一个药品档案可能对应用多个批准文号。设计上就按一个药品对应一个主批准文号来约束避免自找麻烦。4.4 顶部导航栏高度和自定义导航栏的适配问题很多社区医院的前端操作员用的都是低价安卓手机屏幕尺寸五花八门导航栏适配不好页面就会显示错位。我本来想偷懒用默认导航栏后来发现我们要在页面顶部放一个近效期预警的徽标入口默认导航栏做不了只能自定义导航栏。这就牵出一个经典问题自定义导航栏的高度怎么算。微信小程序获取导航栏高度的正确姿势const menuButton wx.getMenuButtonBoundingClientRect(); const navBarHeight (menuButton.top - statusBarHeight) * 2 menuButton.height;其中statusBarHeight从wx.getSystemInfoSync()里取。menuButton.top是胶囊按钮到屏幕顶部的距离(menuButton.top - statusBarHeight)是导航栏在状态栏下方的高度乘2加上胶囊高度是为了给上下留间距。实测下来iPhone的胶囊高度是32px安卓通常也是32px但这个公式在几乎所有机型上都通用。自定义导航栏的另一个坑是页面滚动时导航栏背景透明度的联动。药房操作员在手机上看库存列表时页面往下滚导航栏如果不跟着改变背景色字就会和内容糊在一起。用onPageScroll监听滚动距离动态切换导航栏的background和文字颜色这个小细节做出来了整个界面质感会提升一个档次。5. 自动提醒的实现链路订阅消息、定时扫描与通知触达5.1 订阅消息的授权策略不要一上来就弹框自动提醒是整个系统的大脑而微信订阅消息是提醒的出口。这里面的门道比较多我一个个说。微信订阅消息分两种一次性订阅和长期订阅。长期订阅目前只对部分类目开放比如医疗健康类的部分服务可用一般开发者能用的都是一次性订阅。所谓一次性订阅就是用户点击一次允许按钮你只能推送一条消息。用户再点击一次你再获得一次推送机会。这对我们这种每天推送一次预警汇总的场景来说授权频次根本不够。我的解决思路是改变推送策略从主动推送改成授权兑换。具体做法是用户进入预警设置页看到开启库存预警提醒的开关。开关打开时弹出订阅消息授权框用户点允许系统拿到1次推送额度。后端把这次额度换算成当天的推送次数用户在当天可以收到多条提醒。这个方案绕开了长期订阅的限制但需要一个前提条件来配合就是提醒尽量在授权的同一记忆周期内触达。比如药剂师下午3点开了提醒开关当天傍晚库存不足的消息推送过来用户的记忆还是连续的感知到这个功能确实有用如果第二天才来消息用户早就忘了自己授权过什么还容易觉得是骚扰。还有一个实测很关键的细节不要在小程序启动时就弹授权框。微信对授权弹框的时机没有任何硬性限制但从用户心理上说刚打开页面还没搞明白你是什么应用就弹授权拒绝率极高。我在做这个项目时把授权请求放到了开启预警开关的用户主动动作之后授权率从30%出头提升到70%以上差别非常大。5.2 订阅消息推送的后端封装订阅消息接口调微信的subscribeMessage.send需要准备的是接收者的openid、模板ID、跳转小程序的页面路径、模板里要填充的数据。后端封装成统一的发送方法public void sendSubscribeMsg(String openid, String templateId, String page, MapString, TemplateData data) { WxMpSubscribeMessage message WxMpSubscribeMessage.builder() .toUser(openid) .templateId(templateId) .page(page) .data(data) .build(); wxMpService.getSubscribeMsgService().send(message); }我这里的模板参数是药品名称、批号、当前库存、有效期截止日期四选二组合使用按预警类型不同组装。微信订阅消息的模板参数有长度限制比如药品名称参数最长20个字药品名称超长的需要截断处理这个处理逻辑别放在后端接口层在组装模板数据时统一做避免不同的调用方各写一套截断规则最后参差不齐。推送提醒的页面路径也有讲究。消息点进去默认打开小程序首页但如果能定位到对应药品的详情页体验会更好。我在跳转参数里带了drugCode小程序首页的onLoad里解析参数有参数就自动跳转到该药品的库存详情页。这就是从提醒到处理的最短路径。5.3 定时扫描任务的设计与执行时机后端用Spring Boot自带的Scheduled定时任务就够了不需要引入Quartz增加复杂度。扫描频率我设置为每天凌晨1点扫描一次。为什么选这个时间白天药房随时有人操作库存数据一直变动扫描结果很快就不准了凌晨1点基本没人操作数据相对稳定生成预警清单最有参考价值。扫描任务的逻辑分三步扫描所有status1且current_qty 0的批次校验有效期。按药品汇总有效库存与low_stock_threshold比对。把生成的预警记录写入warning_record表同时给绑定了订阅消息额度的用户发推送。这里有个消息发不发的细节同一批药品连续多天处于低库存状态是否需要每天推送如果每天推用户第三天就会把订阅消息当作骚扰直接卸载小程序。我的做法是——第一天的预警做了提示后端记录一个已提醒状态只有当库存数量发生变化比如更低或已补货时才再次触发推送。这样药剂师每天收到的预警都是新增或变化的信息而不是一片复读机。定时任务的执行失败处理也要考虑。凌晨1点的扫描如果服务器当时恰好重启任务不会自动补跑预警就漏了。我加了一个补救机制每天第一次有人登录小程序时后端顺手跑一次轻量级扫描只推送当天新出现的预警。这个逻辑很轻不需要定时器只要在登录接口里加一个是否已完成今日扫描的标志判断即可。6. 后端接口封装、联调踩坑与发布上线经验6.1 请求封装的正确打开方式小程序端的请求封装看着简单实际上决定了整个前端团队哪怕就你一个人的开发和排错效率。我最终封装的核心逻辑包含三件事公共参数注入、token自动续期、错误码统一处理。公共参数包括appId、timestamp、sign防篡改签名社区医院内部项目可以不搞这么重但基础的timestamp必须加方便后面排查问题、以及token。我把这些统一放进请求头不散落在每个请求里。token过期处理刚才提过这里给出代码逻辑request(url, data, method) { return new Promise((resolve, reject) { wx.request({ url: baseUrl url, method, data, header: { token: wx.getStorageSync(token) }, success: (res) { if (res.data.code 401) { // token过期静默重新登录 this.reLogin().then(() this.request(url, data, method)) .then(resolve).catch(reject); } else { resolve(res.data); } } }); }); }错误码统一处理指的是后端无论哪个接口报错都返回{ code: xxx, msg: ... }的结构。小程序端在请求封装里把非成功状态码统一弹toast提示不再每个页面各写一遍错误提示逻辑。错误提示的文案要直接可读例如药品编码已存在而不是系统错误请稍后重试——稍后重试这种话用户看到只会更困惑。有一点值得多说接口的返回数据结构必须前后端提前约定好。我见过太多项目因为返回结构前后端理解不一致联调阶段反复改来改去。直接用统一的{ code, msg, data }三件套接口文档里标明每个字段的含义和可选值省掉大量扯皮时间。6.2 真实联调时踩过的几个典型报错微信小程序请求fail的真凶不是代码而是域名。开发阶段在开发者工具里关掉校验合法域名就能跑通但真机预览时如果依然报request:fail十有八九是正式环境没配置request合法域名。在微信公众平台后台的开发管理-服务器域名里把https接口域名加进去记得必须https且域名不能带路径。如果你用的是IP地址或者带端口的域名微信目录下只支持443端口就需要考虑用nginx做反向代理。10002错误我很意外地遇到过不少次。具体场景是这样的开发环境中点击订阅消息的授权框没反应或者偶尔报错10002。排查后发现这是因为我的测试号没有绑定正确的模板ID到微信公众平台后台找到对应类目的模板消息库申请添加模板再通过审核才能正常使用。这个错和代码逻辑没关系纯粹是配置问题但排查的过程真的很费时间如果你也用订阅消息第一件事先去后台确认模板ID是不是有效。**H5唤起小程序链接无法访问**的情况也遇到过。我们有个需求是社区医院官网的文章里加一个打开小程序的入口需要用到URL Link或者URL Scheme。生成URL Link的时候后端要配置小程序的路径和参数。我最初生成的链接直接在浏览器里打开一直报错后来发现是要给URL Link配置一个备用网页在不能打开小程序的场景下会跳转到那个网页否则就会报无法访问。另外要注意URL Link的有效期默认30天长期入口需要用有效期大于30天的URL Link类型需要单独申请。6.3 体验版试用、年审与合规这些非技术活小程序写完了总得给同事、给医院的负责人测试。直接把代码通过微信开发者工具上传然后在公众平台后台把版本设为体验版生成了体验版二维码。体验版二维码的权限是受控的只有添加到体验成员列表里的微信号才能打开这在测试阶段很重要——不然一个没做完的系统被外人扫到麻烦就大了。收集试用反馈这件事我也建议做得有仪式感一点。体验版发给10个医院的工作人员用一周收集到的反馈大部分集中在录入表单流程繁琐、列表加载速度慢、推送消息点进去看不到对应数据——这些都是开发者在开发环境中根本感受不到的真实问题。比如有个反馈是列表加载慢我在开发工具里完全没察觉但医院的老安卓机上确实卡后来做了分页优化、差量setData优化后明显改善。到期年审的事别忘。微信小程序每年要交300元认证费认证到期前一个月公众平台会提醒。如果因为没及时年审导致小程序被冻结恢复流程会很折腾。还有一个与合规相关的点类目选择。我们做的是药品库存管理不是直接卖药类目选医疗下面的健康管理就够了如果涉及在线售药需要额外提供《药品经营许可证》等资质项目边界没划好后面的审核会卡很久。我不建议为了过审谎报类目——也就是网上传的骗审——因为审核不通过是一回事被平台发现类目不符下架是另一回事对真实项目来说风险完全不值得。6.4 部署环境与性能优化建议部署这块社区医院项目没有高并发需求一台2核4G的云服务器完全够用。我在nginx里配了反向代理域名走https证书用免费版就行。MySQL用默认配置定时任务跑起来毫无压力。数据量方面社区医院一年大约产生5到10万条出入库流水MySQL对这种数据量根本用不着分库分表但索引一定要建对。我的经验是根据三类高频查询来设计索引——按药品编码查询批次drug_code上建普通索引、按有效期范围查询批次expiry_date上建普通索引、按时间范围查询流水create_time上建普通索引。冗余索引不要建太多否则写操作的成本反而上去了。最后一个小技巧灰度发布。小程序不是传统的客户端理论上每次发布都是全量。我刚做这个系统时很谨慎每次改版都先在体验版环境跑一周让医院的人先试确认没有新问题再上传正式版。因为小程序上传新版本后用户需要重新进入才会加载新版本如果当天有紧急修复老用户还停留在旧版上容易出岔子。这个系统从需求调研到上线前后大概用了两个多月。真正写代码的时间不算长大头都耗在调研和价值取舍上——社区医院到底要什么、不要什么比技术本身更重要。比如供应商端我最初拍脑袋加了这个模块后来被医院否了。再比如报表导出的格式医院要的是能直接打印贴在药房墙上的格式而不是一个花里胡哨的图表。这些非主流需求恰恰是项目成败的关键。做这类面向特定场景的小程序我最深的体会是技术方案的选择永远服务于使用者。微信小程序解决的不是用技术炫技的问题而是社区医院药房那个最低效的环节到底在哪的问题。技术选型、数据模型、提醒链路这些都是手段最终目标就是让药剂师少跑几趟、少翻几次台账、少浪费几批药。如果你也正在做类似的项目我的建议是先去医院药房待一个下午看看他们真实的工作流然后再决定怎么设计系统。你会发现很多你认为理所当然的需求在现场根本不存在而现场那些真正让人头疼的时刻恰恰是系统最应该解决的。