背景与需求

养老机构中,家属经常带来自备药品给长者使用。当前缺乏系统化的自备药登记工具。本方案实现从扫码识别 → 入库登记 → 审核确认 → 同步轻流的完整流程。

技术栈

层级技术
后端框架Python Flask (端口 9090)
前端原生 HTML5 + CSS3 + JavaScript
数据库MySQL 8.0 (Docker, 数据库 nursing)
外部对接轻流 OpenAPI (私有部署 care.yckycn.com)
扫码识别当前 Mock 数据,待接入码上放心 API

核心数据链路

🔍 扫码追溯码 📦 识别药品信息 💾 入库 MySQL 📋 暂存审核 ☁️ 批量推送轻流

药品追溯码调研

药品包装上有两种编码:药品追溯码(20位数字,一物一码)和商品码(13位GTIN,一品一码)。支付宝扫出完整信息是因为对接了阿里「码上放心」平台——国家药监局指定的药品追溯第三方平台。

对接方案

淘宝开放平台免费公开 API:alibaba.alihealth.drug.code.kyt.querycode,无需额外授权,但需要 ref_ent_id(企业入驻码上放心后获得)。

养老机构入驻路径:持有《医疗机构执业许可证》的养老机构可走「医疗机构入驻」通道。集团约20家机构,建议各机构独立申请 ref_ent_id(出库追溯可定位到具体院区)。

当前阶段:Mock 数据

在码上放心正式接入前,使用 Mock 药品库(5种常见药品),扫码查询返回随机批号和效期。待获取 ref_ent_id 后替换 _mock_scan() 函数即可。

  • 阿莫西林胶囊(国药准字H13023976,石药集团)
  • 硝苯地平控释片(国药准字H20203345,拜耳)
  • 盐酸二甲双胍缓释片(国药准字H20051243,施贵宝)
  • 阿托伐他汀钙片(国药准字H20051408,辉瑞)
  • 氯沙坦钾片(国药准字H20000372,默沙东)

数据库设计

表名:biz_self_medication_records,存放每次扫码登记的自备药记录。包含 22 个字段 + 6 个索引。

核心字段

字段类型说明
idINT AUTO_INCREMENT主键
institutionVARCHAR(100)机构名称(索引)
trace_codeVARCHAR(30)药品追溯码(索引,去重键)
drug_nameVARCHAR(200)药品名称
approval_no / manufacturer / specification / dosage_formVARCHAR批准文号 / 生产企业 / 规格 / 剂型
batch_no / produce_date / expire_dateVARCHAR / DATE批号 / 生产日期 / 有效期至
resident_name / id_number / family_memberVARCHAR长者姓名 / 身份证 / 家属
quantityINT数量(默认1)
registered_by / notesVARCHAR登记人 / 备注
sync_statusENUM('pending','synced','failed')同步状态(索引)
sync_timeDATETIME同步时间
is_deletedTINYINT(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(私有部署)
表单 AppKeyeqi4u0ls1401
Token永久 Token,errCode=0 即成功
推送 URL/openApi/app/{appKey}/apply

14 个字段映射

轻流字段queIdqueType质控字段 key
长者姓名2089416782resident_name
身份证号码2089416802id_number
药品名称2089416812drug_name
批准文号2089416822approval_no
生产企业2089416832manufacturer
规格2089416842specification
剂型2089416852dosage_form
追溯码2089416862trace_code
登记人2089416872registered_by
备注2089416882notes
生产日期2089416894produce_date
有效期至2089416904expire_date
登记时间2089416914created_at
数量2089416948quantity

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/scanPOST扫码查询药品信息(当前 Mock)
/api/self-medication/residentsGET搜索在住老人(姓名/身份证/床位)
/api/self-medication/registerPOST登记入库,不推送,标记 pending
/api/self-medication/{id}PUT修改数量(仅允许 pending 状态)
/api/self-medication/{id}DELETE软删除(仅允许 pending,追溯码释放)
/api/self-medication/syncPOST批量推送轻流(支持指定 ID 列表)
/api/self-medication/recordsGET记录列表(支持 sync_status 筛选)
/api/self-medication/statsGET统计摘要(含 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(追溯码释放)
安全规则:已同步到轻流的记录(synced)不可通过业务端删除或修改。如需撤销,必须联系管理员在 MySQL 和轻流两边同时处理。追溯码通过 is_deleted 状态管理生命周期:登记占用 → 删除释放 → 同步锁定。

前端交互设计

页面布局:双栏 + 全宽

  • 左侧面板(460px):机构/休养区选择 → 输入追溯码查询 → 显示药品信息 → 选择老人 → 输入数量/登记人 → 存入暂存区
  • 右侧面板(自适应):三个 Tab(暂存区 / 已同步 / 失败)+ 机构筛选 + 关键词搜索 + 分页
  • 顶部统计卡片:累计登记 / 待同步 / 本月登记 / 近效期预警

暂存区 Tab 交互

  • 每条记录可内联修改数量(仅 pending 状态,失焦即保存)
  • 每条记录可删除(弹窗确认,追溯码释放,提示可重新登记)
  • 支持全选 + 批量推送(弹窗确认数量后一次性推送轻流)
  • 推送完成后自动跳转到「已同步」Tab

已同步 Tab

纯只读列表,不显示任何操作按钮。每条记录末尾显示绿色已同步标签。

失败 Tab

显示同步失败的记录,提供「重试」按钮(单条重推)。

踩坑记录

以下是开发联调过程中遇到的关键问题和解决方案,按时间顺序记录。

轻流 API 对接

问题现象根因 & 解决
URL 用错推送到 api.qingflow.com 无响应实际是私有部署 care.yckycn.com
userId Header 导致 49307errCode: 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)
✅ 当前状态:Mock 扫码 + MySQL 入库 + 暂存审核 + 批量推送到轻流,全链路已走通。待码上放心 ref_ent_id 就位后,替换 Mock 层即可实现端到端。