自备药扫码登记 — 系统方案
一背景与需求
▼养老机构中,家属经常带来自备药品给长者使用。当前缺乏系统化的自备药登记工具。本方案实现从扫码识别 → 入库登记 → 审核确认 → 同步轻流的完整流程。
技术栈
| 层级 | 技术 |
|---|---|
| 后端框架 | Python Flask (端口 9090) |
| 前端 | 原生 HTML5 + CSS3 + JavaScript |
| 数据库 | MySQL 8.0 (Docker, 数据库 nursing) |
| 外部对接 | 轻流 OpenAPI (私有部署 care.yckycn.com) |
| 扫码识别 | 当前 Mock 数据,待接入码上放心 API |
核心数据链路
二药品追溯码调研
▼药品包装上有两种编码:药品追溯码(20位数字,一物一码)和商品码(13位GTIN,一品一码)。支付宝扫出完整信息是因为对接了阿里「码上放心」平台——国家药监局指定的药品追溯第三方平台。
对接方案
淘宝开放平台免费公开 API:alibaba.alihealth.drug.code.kyt.querycode,无需额外授权,但需要 ref_ent_id(企业入驻码上放心后获得)。
当前阶段:Mock 数据
在码上放心正式接入前,使用 Mock 药品库(5种常见药品),扫码查询返回随机批号和效期。待获取 ref_ent_id 后替换 _mock_scan() 函数即可。
- 阿莫西林胶囊(国药准字H13023976,石药集团)
- 硝苯地平控释片(国药准字H20203345,拜耳)
- 盐酸二甲双胍缓释片(国药准字H20051243,施贵宝)
- 阿托伐他汀钙片(国药准字H20051408,辉瑞)
- 氯沙坦钾片(国药准字H20000372,默沙东)
三数据库设计
▼表名:biz_self_medication_records,存放每次扫码登记的自备药记录。包含 22 个字段 + 6 个索引。
核心字段
| 字段 | 类型 | 说明 |
|---|---|---|
| id | INT AUTO_INCREMENT | 主键 |
| institution | VARCHAR(100) | 机构名称(索引) |
| trace_code | VARCHAR(30) | 药品追溯码(索引,去重键) |
| drug_name | VARCHAR(200) | 药品名称 |
| approval_no / manufacturer / specification / dosage_form | VARCHAR | 批准文号 / 生产企业 / 规格 / 剂型 |
| batch_no / produce_date / expire_date | VARCHAR / DATE | 批号 / 生产日期 / 有效期至 |
| resident_name / id_number / family_member | VARCHAR | 长者姓名 / 身份证 / 家属 |
| quantity | INT | 数量(默认1) |
| registered_by / notes | VARCHAR | 登记人 / 备注 |
| sync_status | ENUM('pending','synced','failed') | 同步状态(索引) |
| sync_time | DATETIME | 同步时间 |
| is_deleted | TINYINT(1) | 软删除标记(默认0) |
蓝色标记的 3 个字段是为「暂存审核 + 批量同步」架构新增的。
追溯码去重规则
-- 同一机构下追溯码唯一(排除已删除记录) SELECT id FROM biz_self_medication_records WHERE institution = %s AND trace_code = %s AND is_deleted = 0
删除记录时设置 is_deleted=1(软删除),追溯码自动释放,可重新登记。
四轻流对接方案
▼连接配置
| 配置项 | 值 |
|---|---|
| 域名 | care.yckycn.com(私有部署) |
| 表单 AppKey | eqi4u0ls1401 |
| Token | 永久 Token,errCode=0 即成功 |
| 推送 URL | /openApi/app/{appKey}/apply |
14 个字段映射
| 轻流字段 | queId | queType | 质控字段 key |
|---|---|---|---|
| 长者姓名 | 208941678 | 2 | resident_name |
| 身份证号码 | 208941680 | 2 | id_number |
| 药品名称 | 208941681 | 2 | drug_name |
| 批准文号 | 208941682 | 2 | approval_no |
| 生产企业 | 208941683 | 2 | manufacturer |
| 规格 | 208941684 | 2 | specification |
| 剂型 | 208941685 | 2 | dosage_form |
| 追溯码 | 208941686 | 2 | trace_code |
| 登记人 | 208941687 | 2 | registered_by |
| 备注 | 208941688 | 2 | notes |
| 生产日期 | 208941689 | 4 | produce_date |
| 有效期至 | 208941690 | 4 | expire_date |
| 登记时间 | 208941691 | 4 | created_at |
| 数量 | 208941694 | 8 | quantity |
API 调用规范
POST https://care.yckycn.com/openApi/app/eqi4u0ls1401/apply
Headers:
accessToken: {token}
Content-Type: application/json
Body:
{
"applyUser": {"email": "wangdezhi@njycky.com"},
"answers": [
{"queId": 208941678, "queType": 2, "values": [{"value": "王德志"}]},
...
]
}
1. 禁止在 Header 中传 userId(私有部署版会导致 errCode 49307)
2. 判断标准是 errCode == 0(不是 applyId,私有部署版始终为 null)
3. 机构字段(queType=22 部门选择)已从映射中移除,轻流侧通过身份证号自动匹配
五暂存审核架构
▼最初的方案是登记一条就实时推一条到轻流。但在测试中发现:如果业务员录错了(选了错误的老人、填错了数量),轻流那边也会收到错误数据,需要两边都删除。因此改为登记暂存 + 手动推送模式:MySQL 是主数据源,轻流是镜像,审核后一次性推送。
三阶段流程
(pending)→ 📋 暂存区审核
(改数量/删)→ ☁️ 批量推送
(synced)
API 端点(共 8 个)
| 端点 | 方法 | 说明 |
|---|---|---|
| /api/self-medication/scan | POST | 扫码查询药品信息(当前 Mock) |
| /api/self-medication/residents | GET | 搜索在住老人(姓名/身份证/床位) |
| /api/self-medication/register | POST | 登记入库,不推送,标记 pending |
| /api/self-medication/{id} | PUT | 修改数量(仅允许 pending 状态) |
| /api/self-medication/{id} | DELETE | 软删除(仅允许 pending,追溯码释放) |
| /api/self-medication/sync | POST | 批量推送轻流(支持指定 ID 列表) |
| /api/self-medication/records | GET | 记录列表(支持 sync_status 筛选) |
| /api/self-medication/stats | GET | 统计摘要(含 pending 数量) |
码上放心 API 对接方案(待实施)
当前扫码查询使用本地 Mock 数据。正式上线后,_mock_scan() 函数将替换为码上放心真实 API 调用,实现药品追溯码的真实信息查询。
alibaba.alihealth.drug.code.kyt.querycode调用方式:HTTP POST,传入追溯码(20位数字)和
ref_ent_id(企业入驻码上放心后获得)返回信息:药品名称、批准文号、生产企业、规格、剂型、包装规格、批号、生产日期、有效期至等
交互流程:
- 步骤 1:扫码触发 — 前端输入 20 位追溯码 → 调用
POST /api/self-medication/scan - 步骤 2:码上放心查询 — 后端
_scan_drug(trace_code)→ 调用淘宝开放平台 API,传入 trace_code + ref_ent_id → 解析返回的药品 JSON - 步骤 3:信息展示 — 药品名称、批准文号、生产企业、规格、剂型、批号、效期等返回到前端药品信息卡片
- 步骤 4:效期预警 — 如果有效期距今天不足 90 天,前端红色高亮标注
- 步骤 5:用户确认 — 选择老人、填写数量/登记人 → 存入暂存区(pending)
- 步骤 6:审核推送 — 在暂存区确认后,选中记录批量推送到轻流
ref_ent_id。集团内约 20 家机构,建议各机构独立申请(出库追溯可准确定位到具体院区)。持有《医疗机构执业许可证》即可走「医疗机构入驻」通道。
六同步状态流转
▼三种状态 & 操作权限
| 状态 | 含义 | 改数量 | 删除 | 同步 |
|---|---|---|---|---|
| pending | 待同步 | ✅ | ✅ 追溯码释放 | ✅ |
| synced | 已同步到轻流 | ❌ | ❌ 联系管理员 | — |
| failed | 同步失败 | ❌ | ❌ | ✅ 重试 |
状态流转规则
登记 ──→ pending ──→ 推送成功 ──→ synced(锁定,不可改删)
└──→ 推送失败 ──→ failed ──→ 重试 ──→ synced
└──→ 删除 ──→ is_deleted=1(追溯码释放)
七前端交互设计
▼页面布局:双栏 + 全宽
- 左侧面板(460px):机构/休养区选择 → 输入追溯码查询 → 显示药品信息 → 选择老人 → 输入数量/登记人 → 存入暂存区
- 右侧面板(自适应):三个 Tab(暂存区 / 已同步 / 失败)+ 机构筛选 + 关键词搜索 + 分页
- 顶部统计卡片:累计登记 / 待同步 / 本月登记 / 近效期预警
暂存区 Tab 交互
- 每条记录可内联修改数量(仅 pending 状态,失焦即保存)
- 每条记录可删除(弹窗确认,追溯码释放,提示可重新登记)
- 支持全选 + 批量推送(弹窗确认数量后一次性推送轻流)
- 推送完成后自动跳转到「已同步」Tab
已同步 Tab
纯只读列表,不显示任何操作按钮。每条记录末尾显示绿色已同步标签。
失败 Tab
显示同步失败的记录,提供「重试」按钮(单条重推)。
八踩坑记录
▼以下是开发联调过程中遇到的关键问题和解决方案,按时间顺序记录。
轻流 API 对接
| 问题 | 现象 | 根因 & 解决 |
|---|---|---|
| URL 用错 | 推送到 api.qingflow.com 无响应 | 实际是私有部署 care.yckycn.com |
| userId Header 导致 49307 | errCode: 49307 | 私有部署版不支持,必须从 headers 中移除 |
| 旧表单 eqhbegdo1402 不入库 | errCode=0 但数据不显示 | 表单配置问题,重建新表单 eqi4u0ls1401 解决 |
| applyId 始终为 null | 误判为推送失败 | 私有部署版不返回 applyId,判断标准是 errCode==0 |
| queType=22 部门字段 | 传文本值不入库 | 需传 {"id": dept_id} 格式;改为轻流侧身份证自动匹配 |
架构决策
| 决策 | 原因 |
|---|---|
| 实时同步 → 批量同步 | 避免录错数据污染轻流,引入暂存区审核机制 |
| 硬删除 → 软删除 | 保留审计记录,追溯码通过 is_deleted 管理生命周期 |
| 机构不推送 | 避免维护部门 ID 映射表,轻流侧用身份证号自动匹配 |
| 失败标记 + 重试 | 失败不阻断其他记录推送,支持单条重试 |
其他技术细节
- MySQL 时区:默认 UTC → 改为
pymysql.connect(time_zone='+08:00') - Flask 双进程:端口冲突 →
fuser -k 9090/tcp清理后重启 - 函数名冲突:
api_sync()与 leak_check.py 重名 → 改为api_med_sync() - 推送日志位置:
/var/log/qf_push.log(含请求体、响应、errCode、requestId)