feat(handler): 暴露健康档案 upsert 接口(T-204)
This commit is contained in:
@@ -161,6 +161,8 @@ type ManageInfo struct { DSFMC, OperateUnit, OperateUser string }
|
||||
|
||||
T-213 已落地可复用的 `HealthRecordUpsertService`。它接收 `source.HealthRecordTask`,通过注入的 `Converter`、`HealthRecordClient`、`IdempotencyStore`、`ReportSink` 和 `PHISStatusWriter` 完成单条健康档案编排;HTTP handler 与未来 worker 必须复用该服务,不复制分支规则。
|
||||
|
||||
T-204 已通过 `POST /api/health-record/upsert` 接入该服务。HTTP 运行时先执行无网络字段/码表预校验,再按请求中的 `manaUnitId` 加载责任医生和机构字典,映射层仍执行真实主数据 membership 校验;同步接口使用进程内 `MemoryIdempotencyStore` 只防同进程并发,成功后释放租约,避免稳定 `checkId` 阻断后续合法更新。HTTP 模式不执行 PHIS 回调,调用方直接消费结构化 outcome;持久化幂等和真实 PHIS 状态回写仍分别由阶段 3、T-214 承接。
|
||||
|
||||
健康档案采用 query-first:映射校验通过后,以稳定 `checkId + idCard` 的哈希键原子获取幂等租约,再按身份证调用 JKDA00002。明确 0 条才调用 JKDA00001;恰好 1 条且身份证、状态、机构、责任医生、`phrId` 均安全时调用 JKDA00003;多条或任一授权条件不满足均转 `manual_review`。查询失败、创建失败、更新失败均不切换另一种写操作。
|
||||
|
||||
当前幂等接口是带 owner token 的 `Acquire/Complete/Release` 契约,完成和释放必须匹配租约 token,防止过期 worker 改写新租约;T-213 只用假实现验证占用和完成语义,Redis/文件持久化、租约超时与恢复属于阶段 3。报告与 PHIS 状态同样仅定义端口,真实回写由 T-214 实现。CHIS 已明确写入成功后,必须先完成幂等记录再发布 `done`;完成失败时保留业务结果 `done`、返回基础设施错误且不发送成功通知,调用方不得据此重放 CHIS 写入。副作用失败通过 `NotificationError` 分别暴露 `PriorErr/ReportErr/StatusErr`,并只携带失败端的补偿载荷,补偿时不重新执行 upsert。
|
||||
|
||||
+37
-4
@@ -1,6 +1,6 @@
|
||||
# 07 · 本项目 HTTP 接口
|
||||
|
||||
`chis_osi` **server 模式**对外提供的 HTTP 查询接口——供前端/运维/PHIS 侧调用。
|
||||
`chis_osi` **server 模式**对外提供的 HTTP 查询与健康档案 upsert 接口——供前端/运维/PHIS 侧调用。
|
||||
|
||||
> 这是本项目**自己**对外的接口的单一事实来源;调用**上游厂家 OSI** 的接口契约看 `docs/01`、`docs/04`。
|
||||
> 新增/修改端点后同步本文(与 `handler/`、`server.go` 保持一致)。
|
||||
@@ -25,8 +25,9 @@ go run . -mode server -addr 127.0.0.1:9000 # 指定地址
|
||||
|
||||
| 项 | 说明 |
|
||||
| --- | --- |
|
||||
| 方法 | 一律 `GET`,查询条件走 query string |
|
||||
| 成功响应 | **直接回写平台完整 JSON**(`{code,message,data}`,不裁字段),`Content-Type: application/json` |
|
||||
| 查询方法 | `GET`,查询条件走 query string |
|
||||
| Upsert 方法 | `POST`,请求体为 PHIS 健康档案响应信封,最大 1 MiB |
|
||||
| 查询成功响应 | **直接回写平台完整 JSON**(`{code,message,data}`,不裁字段),`Content-Type: application/json` |
|
||||
| 平台成功码 | `code="01"`(字符串,见 docs/01 §1)——本服务不改写,原样透传 |
|
||||
| 参数错误 | `400`,体为 `{"error":"..."}`(如缺必填标识符) |
|
||||
| 上游失败/网络错误 | `502`,体为 `{"error":"..."}` |
|
||||
@@ -34,6 +35,8 @@ go run . -mode server -addr 127.0.0.1:9000 # 指定地址
|
||||
|
||||
> 设计取舍:端点**原样回写平台响应**(同 `Result.Raw`),不做字段裁剪/转换——保证平台未建模字段也能拿到,便于查看完整档案/体检。
|
||||
|
||||
健康档案 upsert 不透传 OSI 原文,只返回脱敏的结构化结果。其运行时先做无网络字段/码表预校验,再按档案 `manaUnitId` 查询责任医生和机构主数据,最后调用 T-213 的 query-first 应用服务。当前 HTTP 模式只使用**进程内并发租约**:可防同进程同时投递,成功后立即释放,后续版本仍会重新查询并更新;它不提供跨重启完成记录,持久化幂等仍属于路线图阶段 3。
|
||||
|
||||
---
|
||||
|
||||
## 端点清单
|
||||
@@ -183,6 +186,35 @@ GET /api/elderly/self-care
|
||||
|
||||
- 上游:`LNRZLPG00002` `/auto/lnr/query`(厂家文档契约,待真实联调确认)
|
||||
- 返回:`data` 为**数组**,每条含进餐/梳洗/穿衣/如厕/活动及总评的原始值、等级和评分;完整字段原样回写
|
||||
|
||||
### 11. 创建或更新个人健康档案
|
||||
|
||||
```
|
||||
POST /api/health-record/upsert
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
请求体使用 PHIS 健康档案响应信封:顶层 `code` 必须为 `200`,`data.archId` 必填;`data.doctor` 和 `data.record` 字段结构见 `source.HealthRecordTask`。医生账号、密码即使出现在原始 JSON 中也会被解码器忽略,不进入领域模型和 OSI 请求。
|
||||
|
||||
处理规则:按身份证查询 CHIS,明确 0 条时创建,1 条且身份证、状态、机构、责任医生和 `phrId` 均安全时更新;多条或不安全目标转人工处理。查询失败绝不降级创建。
|
||||
|
||||
| HTTP 状态 | pipeline 状态 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `200` | `done` | 创建、更新或幂等跳过完成 |
|
||||
| `409` | `manual_review` | 多档案、跨机构、跨医生、状态不可更新等,禁止自动写入 |
|
||||
| `422` | `failed` | PHIS 字段、必填、格式、码表或主数据校验失败 |
|
||||
| `502` | `failed` 或内部错误 | CHIS/字典上游失败,或写入成功后的本地收尾失败 |
|
||||
| `503` | `retry` | 查询/写入暂时失败或同一档案正在处理 |
|
||||
|
||||
正常响应示例:
|
||||
|
||||
```json
|
||||
{"status":"done","action":"update","serviceId":"JKDA00003","responseCode":"01","phrIdHint":"****1234"}
|
||||
```
|
||||
|
||||
内部错误响应包含 `retrySafe`。若 CHIS 已明确写入成功但幂等完成或通知失败,会保留 `outcome.status="done"` 且返回 `retrySafe=false`,调用方不得重放整个 upsert,只能按错误补偿后置动作。
|
||||
|
||||
> 当前未完成 T-215 真实写入验收。没有安全测试档案和明确写入授权时,不得用真实居民调用此端点。
|
||||
---
|
||||
|
||||
## 调用示例
|
||||
@@ -198,12 +230,13 @@ curl "http://127.0.0.1:8080/api/dictionaries/doctors?manaUnitId=<机构码>&oper
|
||||
curl "http://127.0.0.1:8080/api/dictionaries/drugs?pageNo=1&pageSize=10&ypmc=<药品名>&pym=<拼音码>" # pageNo 必填
|
||||
curl "http://127.0.0.1:8080/api/dictionaries/orgs?organizCode=<机构码>&parentId=<上级机构ID>"
|
||||
curl "http://127.0.0.1:8080/api/elderly/self-care?idCard=<身份证>&phrId=<档案号>&checkId=<检查主键>"
|
||||
curl -X POST "http://127.0.0.1:8080/api/health-record/upsert" -H "Content-Type: application/json" --data-binary "@<phis-health-record.json>"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 尚未提供(后续)
|
||||
|
||||
- 写入类端点(档案/体检 create/update):`handler` 侧 `/api/health-record/save` 等待 T-204/T-206 及厂家写入授权。
|
||||
- 体检等其他业务写入端点:待对应 PHIS 转换、upsert 编排及厂家写入授权。
|
||||
- 老年人中医体质辨识、中医指导查询端点:待对应 OSI serviceId/字段契约(docs/06 B4)。
|
||||
- 鉴权、访问日志、限流:当前是本机联调工具形态,对外前必须补齐。
|
||||
|
||||
+10
-7
@@ -6,11 +6,11 @@
|
||||
## 当前快照
|
||||
|
||||
- 日期:2026-07-16
|
||||
- 阶段:**T-212 转换器与 T-213 健康档案 upsert 应用编排已完成;下一步 T-204 暴露 HTTP upsert,真实写入验收由 T-215 单独受阻**;T-303 老年人生活自理能力评估查询代码完成、真实联调受阻
|
||||
- 阶段:**T-212 转换器、T-213 upsert 编排和 T-204 HTTP 适配已完成;健康档案本地写入链路就绪,真实写入验收由 T-215 单独受阻**;T-303 老年人生活自理能力评估查询代码完成、真实联调受阻
|
||||
- 技术栈:Go 1.24 单二进制;`main.go -mode server|deliver`;配置读取使用 viper,支持环境变量覆盖;OSI 客户端已具备签名、信封、传输、基础判码、JKDA00002 Find、JKDA00005 FindRqbj(人群分类,已按实测 auto 路径校准)、JKDA00001 Create、JKDA00003 Update、LNRZLPG00002 老年人生活自理能力查询(待真实联调),以及 WGDZ/ZRYS/YPML/CXJG 四个公开查询薄封装
|
||||
- 生产代码:`source/health_record.go` 承接 PHIS 档案 DTO 并剥离医生凭据;`mapping/health_record.go` 把 PHIS 主体、既往史和生活环境转换为 JKDA 写入请求;`pipeline/health_record_upsert.go` 已实现可注入、可被 HTTP/worker 复用的 query-first upsert,覆盖原子幂等占用、创建/更新/人工处理/失败分类和脱敏事件
|
||||
- 生产代码:`source/health_record.go` 承接 PHIS 档案 DTO 并剥离医生凭据;`mapping/health_record.go` 转换 JKDA 写入请求;`pipeline/health_record_upsert.go` 实现 query-first upsert;`POST /api/health-record/upsert` 接收 PHIS 信封,运行时加载医生/机构主数据并复用同一服务,返回脱敏结构化 outcome
|
||||
- 联调现实:**JKDA00002 个人档案查询已用 Go 侧真实请求打通**,返回 `code="01" message="操作成功" data_count=1`;**公开查询 WGDZ00001/ZRYS00001/CXJG00002 已用真实档案主数据验证通过**,均返回 `code="01"` 且数组非空;药品目录 YPML00001 已完成客户端封装和单测,尚未做真实药品关键字样本验证;**JKDA00001 create 未跑真实请求**,避免在没有安全测试居民/写入授权时污染平台档案
|
||||
- 测试:`go test ./...` 通过;当前测试覆盖 mode 解析、配置加载与环境变量覆盖、MD5 签名、请求头组装、JSON POST 传输、头名大小写保留、identity 响应编码声明、超时配置、SOCKS5 代理地址校验、信封结构、serviceId 路由、成功/重试判码、Client.Call 请求与响应解析、JKDA00002 查询响应契约、JKDA00001/00003 创建更新请求契约与客户端方法、JKDA00002 Find、JKDA00005 FindRqbj(personSign/idCard/phrId)、公开查询四接口、公开查询 HTTP API、字典缓存快照与持久化失败不阻断、映射码表双向查找、民族 01~56 完整性、健康档案映射必填/码表校验、主数据名称反查、创建请求组装、checkId 确定性、docx/联调风格映射样本基线、JKDA00002 验证入口
|
||||
- 测试:`go test ./...` 通过;除既有查询、契约、映射与 OSI 客户端测试外,已覆盖 upsert create/update/manual_review/retry/failed/error HTTP 映射、成功后收尾错误的 `retrySafe=false`、路由注册、运行时主数据加载,以及进程内租约 acquire/release/complete/stale token
|
||||
- 标准启动路径:`./init.sh` 已配置三步:依赖下载、`go test ./...`、`go run . -mode server -config config.yaml.example`
|
||||
- 标准验证路径:`go test ./...`、`go build ./...`
|
||||
- 当前 blocker:T-215 真实 create/update 验收缺少可写入沙箱的安全测试居民资料或明确写入授权;PHIS 源键已在 T-212 固化为 `archId`,`addressNumber`、`createDate` 和逐档案 `operateUser` 仍需 T-215 写入后回查确认。软限制:当前机器从 Git Bash 启动 Go 会出现标准库路径/构建缓存权限异常,`init.sh` 无法完整跑完;PowerShell 下等价 Go 命令和真实查询通过。厂家侧 B1/B2/B4/B5 契约缺口只影响阶段 4
|
||||
@@ -46,6 +46,9 @@
|
||||
| `handler/public.go` | 已有 | 公开查询端点 `GET /api/dictionaries/grid-addresses`、`/doctors`、`/drugs`、`/orgs` |
|
||||
| `server.go` `osi_client.go` | 已有 | server 模式起 HTTP 服务、注册路由;`buildOSIClient` 共用构造(verify 与 server 复用) |
|
||||
| `pipeline/health_record_upsert.go` | 已有 | T-213 query-first upsert 应用服务;外部客户端、原子幂等、报告与 PHIS 状态均接口注入,持久化待阶段 3 |
|
||||
| `handler/health_record_upsert.go` | 已有 | `POST /api/health-record/upsert` 薄适配;1 MiB 限制、PHIS 信封解码、状态码映射、内部错误脱敏 |
|
||||
| `health_record_upsert_runtime.go` | 已有 | server 模式加载责任医生/机构字典并组装 T-213 服务;HTTP 模式不执行 PHIS 回调 |
|
||||
| `pipeline/memory_idempotency.go` | 已有 | T-204 同步 HTTP 的进程内 owner-token 并发租约;成功即释放,不缓存 completed,非阶段 3 持久化实现 |
|
||||
|
||||
## 已验证事实(写代码时直接依赖)
|
||||
|
||||
@@ -91,6 +94,7 @@ curl "http://127.0.0.1:8080/api/dictionaries/grid-addresses?parentCode=<区划
|
||||
curl "http://127.0.0.1:8080/api/dictionaries/doctors?manaUnitId=<机构码>" # 责任医生
|
||||
curl "http://127.0.0.1:8080/api/dictionaries/drugs?pageNo=1&ypmc=<药品名>" # 药品目录(pageNo 必填)
|
||||
curl "http://127.0.0.1:8080/api/dictionaries/orgs?parentId=<上级机构ID>" # 机构查询
|
||||
curl -X POST "http://127.0.0.1:8080/api/health-record/upsert" -H "Content-Type: application/json" --data-binary "@<phis-health-record.json>" # 未获写入授权时禁止用真实居民
|
||||
|
||||
# 本地 Python 联调查询(脚本硬编码凭据,勿提交)
|
||||
python3 scripts/query_health_record.py
|
||||
@@ -100,10 +104,9 @@ python3 scripts/query_health_record.py
|
||||
|
||||
> 2026-07-15 已把本地能力与真实验收拆开:T-206 标记本地 DONE,T-215 承接授权后的真实写入验收。
|
||||
|
||||
1. **T-213**:用假 OSI、幂等、report、PHIS 回写接口完成 upsert 应用编排;不等待写入授权。
|
||||
2. **T-204**:复用 T-213 暴露 `POST /api/health-record/upsert`,handler 不复制业务逻辑。
|
||||
3. 并行催厂家:写入授权与可写测试档案、更新目标标识、逐档案 `operateUser` 最终规则。
|
||||
4. 授权到位后执行 T-215 真实 create→query→update→query 验收。
|
||||
1. 并行催厂家:写入授权与可写测试档案、更新目标标识、逐档案 `operateUser` 最终规则。
|
||||
2. 授权到位后执行 T-215 真实 create→query→update→query 验收。
|
||||
3. 若授权继续等待,则把路线图阶段 3 的持久化幂等、报告和补偿调度拆成独立任务后实施。
|
||||
|
||||
## 维护规则
|
||||
|
||||
|
||||
+176
-3
@@ -1,8 +1,8 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: chis_osi Server API
|
||||
version: 0.1.0
|
||||
description: 'chis_osi server 模式对外提供的本机 HTTP 查询接口。
|
||||
version: 0.2.0
|
||||
description: 'chis_osi server 模式对外提供的本机 HTTP 查询与健康档案 upsert 接口。
|
||||
|
||||
|
||||
本文档描述的是本项目自己的 HTTP API,不是上游 CHIS OSI 平台接口。
|
||||
@@ -21,7 +21,7 @@ servers:
|
||||
description: 本地默认 server 模式地址
|
||||
tags:
|
||||
- name: health-record
|
||||
description: 健康档案查询
|
||||
description: 健康档案查询与 upsert
|
||||
- name: health-check
|
||||
description: 健康体检查询
|
||||
- name: dictionaries
|
||||
@@ -29,6 +29,72 @@ tags:
|
||||
- name: elderly
|
||||
description: 老年人业务查询
|
||||
paths:
|
||||
/api/health-record/upsert:
|
||||
post:
|
||||
tags:
|
||||
- health-record
|
||||
summary: 创建或更新个人健康档案
|
||||
description: '接收 PHIS 健康档案响应信封,复用 query-first upsert 应用服务。
|
||||
|
||||
|
||||
只有 JKDA00002 明确返回非 nil 空数组时才创建;单条安全目标更新,多条或跨机构等转人工。
|
||||
当前使用成功即释放的进程内并发租约,不保存跨请求完成记录;真实写入验收仍由 T-215 阻断。
|
||||
|
||||
'
|
||||
operationId: upsertHealthRecord
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/PHISHealthRecordEnvelope'
|
||||
responses:
|
||||
'200':
|
||||
description: 创建、更新或幂等跳过完成。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HealthRecordUpsertOutcome'
|
||||
'400':
|
||||
$ref: '#/components/responses/BadRequest'
|
||||
'409':
|
||||
description: 目标档案必须人工处理,未执行写入。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HealthRecordUpsertOutcome'
|
||||
'413':
|
||||
description: 请求体超过 1 MiB。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
'422':
|
||||
description: PHIS 字段、格式、码表或主数据校验失败。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HealthRecordUpsertOutcome'
|
||||
'502':
|
||||
description: CHIS/字典上游失败或 upsert 后置动作失败。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
oneOf:
|
||||
- $ref: '#/components/schemas/HealthRecordUpsertOutcome'
|
||||
- $ref: '#/components/schemas/HealthRecordUpsertError'
|
||||
'503':
|
||||
description: 暂时失败可重试,或同一档案正在处理中。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/HealthRecordUpsertOutcome'
|
||||
'405':
|
||||
description: 仅支持 POST。
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/ErrorResponse'
|
||||
/api/health-record/find:
|
||||
get:
|
||||
tags:
|
||||
@@ -585,6 +651,113 @@ components:
|
||||
value:
|
||||
error: empty response from OSI
|
||||
schemas:
|
||||
PHISHealthRecordEnvelope:
|
||||
type: object
|
||||
required:
|
||||
- code
|
||||
- data
|
||||
properties:
|
||||
code:
|
||||
type: integer
|
||||
enum:
|
||||
- 200
|
||||
msg:
|
||||
type: string
|
||||
compress:
|
||||
type: boolean
|
||||
data:
|
||||
type: object
|
||||
required:
|
||||
- archId
|
||||
- record
|
||||
properties:
|
||||
archId:
|
||||
type: string
|
||||
description: PHIS 健康档案稳定源主键。
|
||||
businessId:
|
||||
type: string
|
||||
description: PHIS 业务追踪/回写标识,不参与 checkId 主键选择。
|
||||
empiId:
|
||||
type: string
|
||||
phrId:
|
||||
type: string
|
||||
doctor:
|
||||
type: object
|
||||
properties:
|
||||
doctorId:
|
||||
type: string
|
||||
realName:
|
||||
type: string
|
||||
additionalProperties: true
|
||||
description: sxtAccount/sxtPassword 等凭据会被服务忽略。
|
||||
record:
|
||||
type: object
|
||||
required:
|
||||
- idCard
|
||||
- manaDoctorId
|
||||
- manaUnitId
|
||||
- regionCode
|
||||
properties:
|
||||
idCard:
|
||||
type: string
|
||||
description: 身份证件号,普通日志不得记录原文。
|
||||
personName:
|
||||
type: string
|
||||
manaDoctorId:
|
||||
type: string
|
||||
manaUnitId:
|
||||
type: string
|
||||
regionCode:
|
||||
type: string
|
||||
additionalProperties: true
|
||||
description: 完整 PHIS record 字段及映射规则见 docs/04 §14。
|
||||
additionalProperties: false
|
||||
HealthRecordUpsertOutcome:
|
||||
type: object
|
||||
required:
|
||||
- status
|
||||
- action
|
||||
properties:
|
||||
status:
|
||||
type: string
|
||||
enum:
|
||||
- done
|
||||
- retry
|
||||
- failed
|
||||
- manual_review
|
||||
action:
|
||||
type: string
|
||||
enum:
|
||||
- none
|
||||
- create
|
||||
- update
|
||||
- skip
|
||||
reason:
|
||||
type: string
|
||||
serviceId:
|
||||
type: string
|
||||
example: JKDA00003
|
||||
responseCode:
|
||||
type: string
|
||||
example: '01'
|
||||
phrIdHint:
|
||||
type: string
|
||||
description: 脱敏档案编号,仅保留末四位。
|
||||
example: '****1234'
|
||||
HealthRecordUpsertError:
|
||||
type: object
|
||||
required:
|
||||
- error
|
||||
- retrySafe
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
example: health record upsert failed
|
||||
retrySafe:
|
||||
type: boolean
|
||||
description: false 表示 CHIS 可能已完成写入,禁止重放整个 upsert。
|
||||
outcome:
|
||||
$ref: '#/components/schemas/HealthRecordUpsertOutcome'
|
||||
ErrorResponse:
|
||||
type: object
|
||||
required:
|
||||
|
||||
Reference in New Issue
Block a user