软件系统架构师实战分享,手机扫码app赋能药店溯源码核验的离线缓存技术方案

软件系统架构师实战分享:手机扫码APP如何撑起药店溯源码核验的离线缓存方案
做了十来年企业级软件架构,最近几年大部分时间泡在医药零售数字化里。跟药店老板、店长打交道多了,才发现很多看似简单的合规要求,落到门店那台老安卓平板或店员自己的手机上,全是坑。就拿药监局强推的药品溯源码核验来说,政策要求逢码必扫、逢扫必验,但真实门店的网络环境,尤其是社区里那些小药店,WiFi信号弱得像摆设,4G也经常抽风。去年冬天我去东北一家连锁药房做实地调研,正赶上促销活动,店员拿手机扫码核验,界面转圈转了三四秒,顾客在后面排长队,那场面别提多尴尬。当时我就意识到,纯在线的核验接口哪怕优化到极致,也救不了弱网下的体验,必须上离线缓存。
我们团队后来给几家区域龙头做APP架构设计的时候,就把“离线优先(Offline First)”当成核心原则。这篇文章就跟各位同行拆解一下,手机扫码APP赋能药店溯源码核验的离线缓存技术方案,这里头有些实战踩过的坑,值得拿出来唠唠。
最初版本,我们图省事,让客户端用SharedPreferences存溯源码和对应信息,JSON往里一塞。结果药店品规少的也有大几千,多的过万,每次启动全量加载直接OOM,低端机直接闪退。这才老实回来用嵌入式数据库。最终选型是SQLite配合SQLCipher做透明加密,上层用Android Jetpack的Room组件封装,既能写DAO也能撸原生SQL。表结构设计上,遵循国家药品追溯协同服务平台的28位或32位编码规范,我们建了trace_code表,字段除了code本身,还有prod_name、spec、batch_no、mfg_date、exp_date、status(已核销/在库)、local_flag(本地新增未同步)等,并且在code和batch_no上建了联合索引,确保扫码瞬间走索引命中,不走全表扫。
离线缓存最麻烦的不是存,而是更新。码段来源于药企对接的“中国药品电子监管码”历史库及新追溯平台,是动态下发的,新品规、召回码都得及时同步。我们设计了一套增量拉取机制:服务端每天凌晨生成前一日的码库变更集(增量的insert/update/delete),带上一个全局版本号version_id。APP端在连上WiFi且电量充足时(比如夜间闲置插电),调用/sync/delta接口,根据本地last_version拉取差异。这里有个坑得提醒一下:删除操作千万别直接物理删本地记录,我们一开始这么干,结果某次同步失败重推,旧码又冒出来能验,合规审计直接亮红灯。后来改成逻辑删除,打一个is_deleted标记,核验时过滤掉,保留痕迹满足GSP审计。
扫码核验的交互路径也重新梳理了。店员用APP扫追溯码,底层用ZXing的native移植版做解码,避免Java层卡顿。解码后先查本地SQLite,如果是命中且status正常,立刻弹窗显示药品信息,耗时控制在50毫秒内,顾客根本感知不到。同时把这次核验动作写入本地outbox队列表,待网络恢复批量上报。如果本地没命中,APP会尝试实时调在线核验接口(当时网络允许的话),若连在线也超时,则进入“待确认”模式,记录条码并提醒店员人工核对批号,不阻断交易但留痕。这个妥协其实是和业务商量的,毕竟不能为了纯技术合规让药店不卖药。
数据一致性方面,我们采用乐观锁加时间戳。服务端是权威源,本地outbox上报后,服务端可能返回“该码已被其他门店核销”的冲突,APP就弹窗告警并刷新本地状态。对于批量上报失败,采用指数退避重试,避免雪崩。
安全上,溯源码属于敏感经营数据,SQLite文件必须用SQLCipher加密,密钥不硬编码,而是通过设备指纹加门店令牌向鉴权服务动态获取,退出登录清密钥。这样即使手机丢了,拖库也解不开。
这套方案在一家300门店的连锁客户上线后,效果很直观:离线核验覆盖率到了99.5%,平均核验响应从原来的2.1秒降到60毫秒以内,促销日再没出现排队投诉。更关键的是,药监局飞行检查时光盘导出本地库,记录完整,合规过关。
回过头看,作为架构师,技术选型永远得跟着业务场景走。药店溯源码核验看似就是扫个码,但背后弱网、老旧设备、合规留痕的约束,逼着我们把离线缓存当成一等公民来设计。希望这点实战经验,能给正在做类似医药零售系统的朋友一点参考。有问题欢迎拍砖交流。

微信号:18581869297
添加微信好友, 获取更多信息
复制微信号



常见问题相关资讯

复制成功
微信号: 18581869297
添加微信好友, 获取更多信息
我知道了