docs: 初始化 chis_osi 设计文档与协作规范
- docs/: OSI 接口规范分析、旧项目架构评估、目标架构设计、字段与接口映射、实施路线图 - CLAUDE.md: 协作规则(项目认知、osi/mapping/pipeline 三层技术约束、安全/验证、提交规范) - .gitignore: 补充项目特定忽略(config.yaml/logs/样本等) - 随项目留存厂家 OSI 规范源材料(docx/xlsx,内部保密,仅限本私有仓库) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
+41
-27
@@ -1,27 +1,41 @@
|
|||||||
# ---> Go
|
# ---> Go
|
||||||
# If you prefer the allow list template instead of the deny list, see community template:
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
#
|
#
|
||||||
# Binaries for programs and plugins
|
# Binaries for programs and plugins
|
||||||
*.exe
|
*.exe
|
||||||
*.exe~
|
*.exe~
|
||||||
*.dll
|
*.dll
|
||||||
*.so
|
*.so
|
||||||
*.dylib
|
*.dylib
|
||||||
|
|
||||||
# Test binary, built with `go test -c`
|
# Test binary, built with `go test -c`
|
||||||
*.test
|
*.test
|
||||||
|
|
||||||
# Output of the go coverage tool, specifically when used with LiteIDE
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
*.out
|
*.out
|
||||||
|
|
||||||
# Dependency directories (remove the comment below to include it)
|
# Dependency directories (remove the comment below to include it)
|
||||||
# vendor/
|
# vendor/
|
||||||
|
|
||||||
# Go workspace file
|
# Go workspace file
|
||||||
go.work
|
go.work
|
||||||
go.work.sum
|
go.work.sum
|
||||||
|
|
||||||
# env file
|
# env file
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# ---> chis_osi 项目特定
|
||||||
|
# 真实配置(含 orgCode/userName/ask 密钥、PHIS token、服务器地址)——只提交 config.yaml.example
|
||||||
|
/config.yaml
|
||||||
|
config.local.yaml
|
||||||
|
# 运行期产物:日志、报告、快照、幂等状态
|
||||||
|
/logs/
|
||||||
|
*.jsonl
|
||||||
|
# 本地存储/缓存
|
||||||
|
*.db
|
||||||
|
*.sqlite
|
||||||
|
/tmp/
|
||||||
|
# 含个人信息的联调样本(身份证/姓名等),如需保留请脱敏后手动 add
|
||||||
|
/samples/*.real.json
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,164 @@
|
|||||||
|
# CLAUDE.md
|
||||||
|
|
||||||
|
适用于 `chis_osi` 仓库的 Claude Code 协作规则。
|
||||||
|
|
||||||
|
> **当前为 pre-code 阶段**:已 git 管理,仅有设计文档与源材料,代码骨架尚未建立(见路线图阶段 0)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 会话启动
|
||||||
|
|
||||||
|
1. 读取 `docs/README.md` — 了解文档集结构与一句话结论
|
||||||
|
2. 执行 `git log --oneline -10` — 了解上次做到哪里
|
||||||
|
3. 确认当前所处阶段(见 `docs/05-实施路线图.md` 的阶段 0~6)
|
||||||
|
|
||||||
|
> 需要架构细节读 `docs/03-目标架构设计.md`;需要接口字段/码表读 `docs/01-OSI接口规范分析.md` 与 `docs/04-字段与接口映射.md`。
|
||||||
|
|
||||||
|
> 用户说"继续开发""继续上次的"时,完成以上步骤后直接接续,不重新介绍项目背景。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 项目定位
|
||||||
|
|
||||||
|
**chis_osi**——对接「广东省基层医疗机构管理系统(CHIS)」厂家(和宇健康科技)提供的**官方统一对外服务接口(OSI)**,
|
||||||
|
把上游 PHIS(公卫系统)数据转换并投递到省基卫平台。
|
||||||
|
|
||||||
|
- **当前阶段**:设计完成、待编码(脚手架尚未建立,见路线图阶段 0)
|
||||||
|
- **语言/运行**:Go 1.24,单二进制双子命令(`cmd/server` 同步 API、`cmd/deliver` 投递 worker)
|
||||||
|
- **团队定位**:默认由初级程序员维护,所有改动优先保证可读、可理解、可接手,避免过度抽象
|
||||||
|
- **核心链路**:PHIS(拉取)→ 字段映射 → MD5 签名 + 统一信封 → CHIS OSI(POST `/osi/api/...`)→ 状态回写
|
||||||
|
|
||||||
|
**关键架构判断(务必牢记):**
|
||||||
|
OSI 是**无状态服务端接口**,鉴权 = 请求头 MD5 签名(`password=md5("ts=<ts>&ask=<ask>")` 32 位小写)。
|
||||||
|
参考项目 `/mnt/d/GoP/chis_upload`(逆向 Chrome F12 实现)里的 SM2 登录 / Cookie / Redis 会话 / 身份链反查 / 网页报文对齐这部分复杂度**在本项目不存在、不要引入**。
|
||||||
|
本项目核心资产是 **PHIS→OSI 表驱动字段映射层** 与 **投递流水线**(重试分类/幂等/熔断/批次报告),幂等键用 OSI 的 `checkId`。
|
||||||
|
|
||||||
|
源材料(`docs/`):`统一对外服务接口文档-.docx`(OSI 规范 V1.5.7)、`广东省基层...需要的接口.xlsx`(本期 25 个接口)。这两份是厂家标注"内部资料注意保密"的材料,仅在本私有仓库内随项目留存,不得外发或推送到公开仓库。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 目录职责
|
||||||
|
|
||||||
|
> 以下为**目标**结构(见 `docs/03-目标架构设计.md` 第 3 节),多数目录尚未创建,按路线图阶段逐步建立。
|
||||||
|
|
||||||
|
| 目录 | 说明 |
|
||||||
|
|------|------|
|
||||||
|
| `cmd/server/` `cmd/deliver/` | 双入口:同步投递 API / 定时投递 worker |
|
||||||
|
| `internal/osi/` | ★ 薄客户端:签名、传输、`Call`、各业务域调用方法 |
|
||||||
|
| `internal/contract/` | ★ 校验后的接口契约(结构体 + serviceId 常量),docx 坑点的唯一修正点 |
|
||||||
|
| `internal/mapping/` | ★ PHIS→OSI 字段/字典映射 + checkId 生成(本项目核心) |
|
||||||
|
| `internal/source/phis/` | PHIS 拉取客户端 + 任务模型 + 状态回写 |
|
||||||
|
| `internal/pipeline/` | 投递编排:校验→映射→调用→分类重试→幂等→熔断→报告 |
|
||||||
|
| `internal/observ/` `internal/store/` | report log(redis 优先、文件降级)/ redis(可选)+文件存储 |
|
||||||
|
| `handler/` `router/` | server 模式对外 HTTP 接口与路由 |
|
||||||
|
| `config/` | viper 读取配置(osi / phis / redis / proxy) |
|
||||||
|
| `docs/` | 所有设计文档,与代码同等重要 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 修改原则
|
||||||
|
|
||||||
|
- 先读现有实现和相关文档,再动手;优先最小化改动范围。
|
||||||
|
- 已有实现可复用时,不新增平行实现。
|
||||||
|
- 先定位根因,再修复问题,不做只遮盖现象的补丁。
|
||||||
|
- 非任务要求,不改对外接口字段、返回结构、配置键名、文件名、编码。
|
||||||
|
- 涉及**签名逻辑、serviceId 常量、checkId 生成、码表、幂等存储**时必须保守处理——它们直接影响平台对账与重复建档。
|
||||||
|
- **不照搬 docx 字段表**:docx 有复制粘贴错误(serviceId 串台、字段名/类型不一致,见 `docs/01` 第 7 节)。以 `contract/` + 联调真实样本为准。
|
||||||
|
- 架构或字段映射发生变化时,同步更新 `docs/` 对应文件(`docs/03` 架构、`docs/04` 字段映射),不让代码改了文档没跟上。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 技术约定
|
||||||
|
|
||||||
|
### 通用
|
||||||
|
- **语言**:Go 1.24,**优先标准库**,谨慎引入第三方依赖。
|
||||||
|
- **注释**:必要的中文注释,只写 WHY 不写 WHAT;重点注释业务规则不直观处(字段映射、码表、分支判定)、外部系统约束(OSI 固定字段/签名规则)、易误改的关键路径(签名、checkId、幂等)。
|
||||||
|
- **错误**:显式处理不吞错;**命名**直接表达用途,避免过度抽象。
|
||||||
|
|
||||||
|
### osi(薄客户端)
|
||||||
|
- 签名只在 `osi` 层完成,业务层无感;用 `crypto/md5` 即可,**不引入 SM2/任何加密库**。
|
||||||
|
- `ts` 取 13 位毫秒;`password` 取 32 位小写;`ask` **仅参与签名**,不进请求头、不进报文、不进日志。
|
||||||
|
- 返回判定集中在 `codes.go`:`code=="1"` 成功(注意是**字符串**);`405` 超时(可重试);其它失败。
|
||||||
|
- 传输层移植自 `chis_upload`(保留 SOCKS5/超时,**去掉 cookiejar 与网页拟态头**)。
|
||||||
|
|
||||||
|
### mapping(核心)
|
||||||
|
- 码表集中 `dict.go`,双向查表,**未命中显式报 ValidationError,绝不静默置空**。
|
||||||
|
- 映射函数为纯函数,返回结构化校验错误,便于用文档样例 + 联调样本单测。
|
||||||
|
- `checkId` 必须**确定性生成**:同一源记录重试得同一 checkId,防止平台重复建档。
|
||||||
|
- 完整度(completeLevel/perfection)**默认不本地计算**,依赖平台;联调确认需自算后再移植旧逻辑。
|
||||||
|
|
||||||
|
### pipeline / 可观测
|
||||||
|
- 重试分类:网络错误 / HTTP 5xx / 429 / `code==405` 可重试;参数错、权限错、映射校验错不可重试;退避 `2s*attempt`,最多 3 次。
|
||||||
|
- 幂等键 = `checkId`;存储 redis 优先、本地 JSON 降级。
|
||||||
|
- Redis 完全可选:启动失败不阻断(沿用 `chis_upload` 行为)。
|
||||||
|
- 一套 report log,不重建 apitrace/snapshot 多套追踪。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 安全要求
|
||||||
|
|
||||||
|
- **严禁**将 `ask` 密钥、`orgCode`/`userName`/`deviceSN`、PHIS token、真实账号、含真实地址的配置提交到 git。
|
||||||
|
- `ask` 等敏感项走环境变量或部署密文,不写进仓库配置、不打印到日志。
|
||||||
|
- 配置示例用 `config.yaml.example`(占位值,不含真实地址与密钥)。
|
||||||
|
- 不提交 `logs/`、快照、抓包样本中含真实身份证/个人信息的文件。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 验证要求
|
||||||
|
|
||||||
|
改动完成后做最小必要验证:
|
||||||
|
|
||||||
|
- **签名改动**:单测校验 `password` 形态(32 位小写)+ 用最简查询接口(机构查询 CXJG00002)打通真实请求。
|
||||||
|
- **映射改动**:补/跑 `mapping` 单测,用 docx 样例与联调样本对齐;确认必填/码表/格式校验生效。
|
||||||
|
- **投递流水线改动**:跑批确认批次报告字段(total/success/failed/skipped/retry)与幂等跳过、熔断行为正确。
|
||||||
|
- 至少执行 `go test ./...` 或与改动最相关的包级测试;无法验证时说明原因和风险。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 文档同步要求
|
||||||
|
|
||||||
|
代码和文档同步,不允许代码改了文档没跟上。完成一组相关改动(1~3 个功能/修复/重构)后,自主判断是否同步更新 `docs/`,无需用户提醒。
|
||||||
|
|
||||||
|
| 发生什么 | 必须更新 |
|
||||||
|
|---------|---------|
|
||||||
|
| 架构或分层发生变化 | `docs/03-目标架构设计.md` |
|
||||||
|
| serviceId / 接口路径 / 字段映射 / 码表校准 | `docs/01-OSI接口规范分析.md`、`docs/04-字段与接口映射.md` |
|
||||||
|
| 联调确认了开放问题(完整度 / checkId 规则 / 缺漏接口等) | `docs/03` 第 8 节、`docs/04` 第 7 节待补清单 |
|
||||||
|
| 阶段推进或验收通过 | `docs/05-实施路线图.md` 勾选项与阶段状态 |
|
||||||
|
| 做了重要技术决策 | `docs/decisions/00N-简短描述.md`(背景 / 决策 / 原因 / 影响) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 提交规范
|
||||||
|
|
||||||
|
> 提交、推送仅在用户要求时进行。代码按每个逻辑改动单独 commit。
|
||||||
|
|
||||||
|
格式:`<type>(<scope>): <简短描述>`
|
||||||
|
|
||||||
|
| type | 用途 | | scope | 对应 |
|
||||||
|
|------|------|---|------|------|
|
||||||
|
| `feat` | 新功能 | | `osi` | 薄客户端 |
|
||||||
|
| `fix` | 修复问题 | | `mapping` | 字段/码表映射 |
|
||||||
|
| `docs` | 文档变更 | | `pipeline` | 投递流水线 |
|
||||||
|
| `refactor` | 重构(不改功能) | | `contract` | 接口契约 |
|
||||||
|
| `chore` | 构建/配置/依赖 | | `phis` | 任务源 |
|
||||||
|
| `test` | 测试相关 | | `config` `handler` … | 其余包/文档 |
|
||||||
|
|
||||||
|
```
|
||||||
|
feat(osi): 实现 MD5 头签名与统一 Call
|
||||||
|
feat(mapping): 落地健康档案字段映射与 checkId 生成
|
||||||
|
docs: 校准 jkda serviceId 并更新接口映射文档
|
||||||
|
chore: 初始化 go module 与双子命令骨架
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 提交前检查
|
||||||
|
|
||||||
|
- [ ] 未改动无关文件,未引入不必要重构或第三方依赖
|
||||||
|
- [ ] 未硬编码 `ask` 密钥、机构码、账号、PHIS token、服务器地址
|
||||||
|
- [ ] 未提交 `config.yaml`、`logs/`、含个人信息的样本
|
||||||
|
- [ ] 未留下临时代码、调试输出、未说明的 `TODO`
|
||||||
|
- [ ] serviceId / 字段映射 / 架构变更已同步更新 `docs/` 对应文件
|
||||||
|
- [ ] 重要技术决策已补 `docs/decisions/` ADR 文件
|
||||||
|
- [ ] `go test ./...` 通过(或说明未验证的原因与风险)
|
||||||
@@ -0,0 +1,199 @@
|
|||||||
|
# 01 · OSI 接口规范分析
|
||||||
|
|
||||||
|
来源:《广东省基层医疗机构管理系统 统一对外服务接口 API 规范文档 V1.5.7》(2023-02-02,和宇健康科技)
|
||||||
|
配套:《广东省基层医疗机构管理系统服务接口对接回复后需要的接口.xlsx》(本期需对接的 25 个接口清单)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 总体约定
|
||||||
|
|
||||||
|
| 项 | 约定 |
|
||||||
|
| --- | --- |
|
||||||
|
| 协议 | HTTP,方法统一 `POST` |
|
||||||
|
| 通用 URL | `http://${hostname}/osi/api/...`(各接口在此基础上拼子路径) |
|
||||||
|
| 报文格式 | 请求/响应均为 JSON 字符串 |
|
||||||
|
| 字符集 | UTF-8 |
|
||||||
|
| 返回码 | `code == "1"` 成功;其它值失败;`405` = 服务调用超时 |
|
||||||
|
|
||||||
|
> 注意:返回码 `code` 是**字符串** `"1"`,不是数字 `1`;判定成功务必按字符串比较或归一化处理。
|
||||||
|
|
||||||
|
### 1.1 响应统一结构
|
||||||
|
|
||||||
|
```json
|
||||||
|
{ "code": "1", "message": "操作成功", "data": { ... } }
|
||||||
|
```
|
||||||
|
|
||||||
|
- `data` 在创建类接口是对象(如 `{ "phrId": "...", "createUnit": "..." }`);
|
||||||
|
- 在查询/列表类接口可能是对象或数组,需按接口分别建模。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 鉴权:请求头 MD5 签名(无状态)
|
||||||
|
|
||||||
|
所有接口通过 **HTTP 请求头**鉴权,**没有登录、没有 Cookie、没有 Session**:
|
||||||
|
|
||||||
|
| 请求头 | 说明 |
|
||||||
|
| --- | --- |
|
||||||
|
| `Content-Type` | `application/json` |
|
||||||
|
| `orgCode` | 机构编码(机构的社会统一信用代码,约 18~20 位)。接入方先把组织/机构信息提供给平台,由平台生成 |
|
||||||
|
| `deviceSN` | 设备序列号(创建/更新/查询类接口均要求必填) |
|
||||||
|
| `ts` | 13 位毫秒级时间戳 |
|
||||||
|
| `userName` | 平台分配的用户名(对应一个 `ask` 密钥;同时也是报文里的第三方接入公司名称编码 `DSFMC`) |
|
||||||
|
| `password` | `md5("ts=<时间戳>&ask=<密钥>")`,取 **32 位小写** |
|
||||||
|
|
||||||
|
签名要点:
|
||||||
|
- `password` 的明文是字符串 `ts=<ts>&ask=<ask>`,其中 `<ts>` 必须与请求头里发送的 `ts` 完全一致;
|
||||||
|
- `ask` 为平台下发的密钥,**只参与签名,绝不放进请求头或报文**;
|
||||||
|
- 每个请求现算 `ts`/`password`,天然防重放(平台侧通常校验 `ts` 时效)。
|
||||||
|
|
||||||
|
> 对比旧项目:这里**不需要** SM2 公钥加密、不需要 `lw_d`/`d`/查询 `d` 加密参数、不需要按年份变化的动态字段名。
|
||||||
|
> 一个标准库 `crypto/md5` 即可完成全部鉴权。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 请求报文信封
|
||||||
|
|
||||||
|
请求体(HTTP body)统一为:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"serviceId": "<固定服务码>",
|
||||||
|
"uploadinfo": { ... } // 创建/更新类:写入数据
|
||||||
|
}
|
||||||
|
```
|
||||||
|
或查询类:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"serviceId": "<固定服务码>",
|
||||||
|
"baseInfo": { ... } // 查询条件
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- `serviceId` 是每个接口的**固定常量**(见第 5 节映射表),平台据此路由业务。
|
||||||
|
- 创建/更新类信封内含 `manageInfo`(管理节点)+ 业务数据节点:
|
||||||
|
- `manageInfo.DSFMC` = 第三方接入公司名称编码(与请求头 `userName` 一致)
|
||||||
|
- `manageInfo.operateUnit` = 操作机构编码(与请求头 `orgCode` 一致)
|
||||||
|
- `manageInfo.operateUser` = 责任医生 ID
|
||||||
|
- 文档中创建样例外层出现的 `"headers": {...}` 仅用于演示 HTTP 头,**不是 body 的一部分**;实际 POST body 只发 `serviceId`+数据节点。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 本期需对接接口清单(来自 xlsx,25 项)
|
||||||
|
|
||||||
|
| # | 模块 | 接口名称 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 1 | 档案 | 个人健康档案信息列表查询 |
|
||||||
|
| 2 | 档案 | 个人健康档案信息创建 |
|
||||||
|
| 3 | 档案 | 个人健康档案信息更新 |
|
||||||
|
| 4 | 档案 | 个人健康档案信息查询 |
|
||||||
|
| 5 | 档案 | 居民人群标记与子档案标记查询 |
|
||||||
|
| 6 | 体检 | 健康体检已检/待检人员列表查询 |
|
||||||
|
| 7 | 体检 | 最近一次健康体检查询 |
|
||||||
|
| 8 | 体检 | 健康体检创建 |
|
||||||
|
| 9 | 体检 | 健康体检更新 |
|
||||||
|
| 10 | 体检 | 健康体检查询 |
|
||||||
|
| 11 | 老年人·中医体质辨识 | 列表查询 |
|
||||||
|
| 12 | 老年人·中医体质辨识 | 创建 |
|
||||||
|
| 13 | 老年人·中医体质辨识 | 更新 |
|
||||||
|
| 14 | 老年人·生活自理能力评估 | 列表查询 |
|
||||||
|
| 15 | 老年人·生活自理能力评估 | 创建 |
|
||||||
|
| 16 | 老年人·生活自理能力评估 | 更新 |
|
||||||
|
| 17 | 老年人·生活自理能力评估 | 查询 |
|
||||||
|
| 18 | 老年人·中医健康指导 | 列表查询 |
|
||||||
|
| 19 | 老年人·中医健康指导 | 保存 |
|
||||||
|
| 20 | 老年人·中医健康指导 | 更新 |
|
||||||
|
| 21 | 老年人·中医健康指导 | 查询 |
|
||||||
|
| 22 | 公共服务 | 查询网格地址 |
|
||||||
|
| 23 | 公共服务 | 责任医生查询 |
|
||||||
|
| 24 | 公共服务 | 药品目录查询 |
|
||||||
|
| 25 | 公共服务 | 机构查询 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 接口 → 路径 → serviceId 映射表
|
||||||
|
|
||||||
|
> 以文档正文为准整理。文档中部分 serviceId 因复制粘贴存在错误(见第 7 节),下表为校正后的推断值,**联调时需逐一回填确认**。
|
||||||
|
|
||||||
|
### 5.1 健康档案(JKDA)
|
||||||
|
|
||||||
|
| 业务 | 路径 | serviceId | 主数据节点 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 创建 | `/osi/api/jkda/create` | `JKDA00001` | `uploadinfo.healthRecord` + `pastHistory`/`jwsjb`/`jwsss`/`jwsws`/`jwssx`/`familyMiddle` |
|
||||||
|
| 查询 | `/osi/api/auto/jkda/find` | `JKDA00002` ⚠ 文档样例误写 `TNB00004` | `baseInfo`(idCard/phrid/personName 三选一) |
|
||||||
|
| 更新 | `/osi/api/jkda/update` | `JKDA00003` | 同创建 |
|
||||||
|
| 人群/子档案标记查询 | `/osi/api/jkda/findrqbj` | `JKDA00005` | `baseInfo`(phrid/idCard 二选一) → `data.personSign` |
|
||||||
|
| 列表查询 | (清单第 1 项,文档正文未见独立路径,疑与 `find` 合并或缺漏) | 待确认 | — |
|
||||||
|
|
||||||
|
`findrqbj` 返回的 `personSign` 取值:`PU` 普通 / `GRQY` 已签约 / `LAO` 老年人 / `GAO` 高血压 / `TANG` 糖尿病 / `FU` 孕产妇 / `ER` 儿童 / `FEI` 肺结核 / `JING` 精神障碍 / `CAN` 残疾人,多个以逗号分隔。
|
||||||
|
|
||||||
|
### 5.2 健康体检(JKTJ)
|
||||||
|
|
||||||
|
| 业务 | 路径 | serviceId |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 创建 | `/osi/api/jktj/create` | `JKTJ00001` |
|
||||||
|
| 查询 | `/osi/api/auto/jktj/query` | `JKTJ00002` |
|
||||||
|
| 更新 | `/osi/api/jktj/update` | `JKTJ00003` |
|
||||||
|
| 已检/待检人员列表 | `/osi/api/auto/jktjlist/query` | `JKTJLIST00002` |
|
||||||
|
| 最近一次体检 | `/osi/api/auto/jktjlscx/query` | `JKTJLSJL00002` |
|
||||||
|
|
||||||
|
> 体检报文体量大(hcData/lsData/exaData/aeData 等数十~上百字段),是字段映射工作量最大的一块。
|
||||||
|
|
||||||
|
### 5.3 老年人(LNR)
|
||||||
|
|
||||||
|
| 业务 | 路径 | serviceId |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 生活自理能力评估·创建 | `/osi/api/lnrzlpg/create` | `LNRZLPG00001` |
|
||||||
|
| 生活自理能力评估·更新 | `/osi/api/lnrzlpg/update` | `LNRZLPG00003` |
|
||||||
|
| 生活自理能力评估·查询/列表 | (清单第 14/17 项) | 待确认(推断 `LNRZLPG00002`) |
|
||||||
|
| 中医体质辨识·创建 | `/osi/api/lnrzyygl/create` | `LNRZYTZ00001` |
|
||||||
|
| 中医体质辨识·更新 | `/osi/api/lnrzyygl/update` | `LNRZYTZ00003` |
|
||||||
|
| 中医体质辨识·查询/列表 | (清单第 11 项) | 待确认(推断 `LNRZYTZ00002`) |
|
||||||
|
|
||||||
|
> 注意路径与 serviceId 的命名不一致:中医体质辨识的**路径**用 `lnrzyygl`,而 **serviceId** 用 `LNRZYTZ`。对接时以文档逐条为准,不要据路径猜 serviceId。
|
||||||
|
|
||||||
|
### 5.4 中医健康指导(ZYJKZD)
|
||||||
|
|
||||||
|
| 业务 | 路径 | serviceId |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 保存 | `/osi/api/auto/zyjkzd/create` | 待确认 |
|
||||||
|
| 更新 | `/osi/api/auto/zyjkzd/update` | 待确认 |
|
||||||
|
| 查询/列表 | (清单第 18/21 项) | 待确认 |
|
||||||
|
|
||||||
|
### 5.5 公共服务(查询类,请求体 `baseInfo`)
|
||||||
|
|
||||||
|
| 业务 | 路径 | serviceId | 关键入参 | 关键出参 |
|
||||||
|
| --- | --- | --- | --- | --- |
|
||||||
|
| 查询网格地址 | `/osi/api/auto/wgdzcx/query` | `WGDZ00001` | `parentCode`,`pageNo`,`operateUser` | `regionCode`,`regionName`,`isFamily`(层级) |
|
||||||
|
| 责任医生查询 | `/osi/api/auto/zryscx/query` | `ZRYS00001` | `manaUnitId`,`operateUser` | `personId`,`personName` |
|
||||||
|
| 药品目录查询 | `/osi/api/auto/ypmlcx/query` | `YPML00001` | `pageNo`,`ypmc`,`pym` | `ypmc`,`ypdw`,`ypgg`,`jldw` |
|
||||||
|
| 机构查询 | `/osi/api/auto/cxjg/query` | `CXJG00002` | `organizCode`,`parentId` | `organizCode`,`organizName`,`organizType`,`parentId` |
|
||||||
|
|
||||||
|
> 公共服务接口是**基础字典服务**:网格地址→`regionCode`、责任医生→`personId`、机构→`organizCode`。
|
||||||
|
> 它们正是创建类接口所需主数据(`regionCode`/`manaDoctorId`/`manaUnitId` 等)的来源,建议优先打通并本地缓存为字典。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 关键字段语义(创建健康档案为例)
|
||||||
|
|
||||||
|
- `checkId`:**第三方系统业务唯一识别码(流水码)**。这是接入方自己生成、用于和平台对账与去重的关键键,是新项目幂等设计的基石(见 04 文档)。
|
||||||
|
- `idCard` + `personName` + `sexCode` + `birthday`:人口学主键四要素,必填。
|
||||||
|
- `regionCode`:12 位行政区划/网格代码,来自「网格地址查询」。
|
||||||
|
- `manaDoctorId` / `manaUnitId` / `operateUser`:责任医生与管辖机构,来自「责任医生查询」「机构查询」。
|
||||||
|
- 大量字段是**带码表的枚举**(民族 56 项、职业、文化程度、婚姻、血型、医保支付方式、既往史/家族史多选用逗号拼接等),是映射层的主要工作量。
|
||||||
|
- `isFillShhj`(y/n)显式标记是否填写生活环境(`familyMiddle`)。
|
||||||
|
- 推断:完整度(旧项目里的 `completeLevel`/`perfection`)很可能由**平台服务端自行计算**,接入方只需如实上送文档字段;这与旧项目"客户端重算完整度"形成对比,需在联调中确认(见 03 文档"开放问题")。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 文档坑点(务必在联调中校验)
|
||||||
|
|
||||||
|
官方 docx 存在明显的复制粘贴/排版错误,建模前需逐一核对:
|
||||||
|
|
||||||
|
1. **serviceId 串台**:`jkda/find` 的请求样例里 serviceId 写成 `TNB00004`(糖尿病接口的码),实际应为 `JKDA00002`。
|
||||||
|
2. **字段名不一致**:创建档案家族史父亲在字段定义里叫 `jzsfqn`,在请求样例里叫 `jzsfq`;现住址门牌号在创建里 `addressNumber`、在查询里 `adressNumber`(少一个 d)。
|
||||||
|
3. **类型不一致**:`familyMiddle` 在创建接口标 `object`,在查询接口标 `list`;`pastHistory` 同样在不同接口标注不同。
|
||||||
|
4. **样例 JSON 非法**:多处查询样例花括号不配对(如 `{ "baseInfo": {...} }, "serviceId": "..." }`),应理解为 `{ "serviceId": "...", "baseInfo": {...} }`。
|
||||||
|
5. **列表查询路径缺漏**:xlsx 要求"档案列表查询""自理/体质/中医指导列表查询""自理查询"等,但 docx 正文未给出全部独立路径与 serviceId,需向厂家索要补充。
|
||||||
|
|
||||||
|
> 结论:**不能直接照搬 docx 字段表生成契约**。落地前应整理一份「校验后的接口契约」(见 03 文档 `contract/` 包),
|
||||||
|
> 并以厂家沙箱环境的真实请求/响应样本做回归校准。
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
# 02 · 旧项目(chis_upload)架构评估
|
||||||
|
|
||||||
|
`chis_upload` 是通过**逆向 Chrome F12 网页接口**实现的 PHIS→CHIS 上传工具(Go 1.24)。
|
||||||
|
本文梳理它的分层与组件,区分「哪些复杂度是被网页逆向逼出来的、新项目应删除」与「哪些工程经验值得保留」。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 整体定位与数据流
|
||||||
|
|
||||||
|
旧项目本质是一个**数据搬运管道**:
|
||||||
|
|
||||||
|
```
|
||||||
|
PHIS(上游公卫系统) --拉取任务--> chis_upload --(模拟网页登录态)--> CHIS 网页后端
|
||||||
|
```
|
||||||
|
|
||||||
|
它对外暴露一组 HTTP API(`/api/health-record/save` 等),内部再以网页同款报文调用 CHIS。
|
||||||
|
另有 worker 模式:轮询 PHIS 拉任务、按 `dataType` 分流、调用自身 save API 完成投递。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 分层与目录职责(摘自其 AGENTS.md 与源码)
|
||||||
|
|
||||||
|
| 目录 | 职责 |
|
||||||
|
| --- | --- |
|
||||||
|
| `config/` | viper 读取 `config.yaml` |
|
||||||
|
| `util/` | SM2 加密、Redis、报告日志(reportlog)、apitrace、日志 |
|
||||||
|
| `chis/` | CHIS 调用核心:client/transport/auth/login + 各业务域 save/query + 归一化器/补齐器/身份链/完整度计算 |
|
||||||
|
| `model/` | 请求/响应结构体 |
|
||||||
|
| `handler/` | 对外 HTTP API 处理函数 |
|
||||||
|
| `middleware/` | Cookie 中间件(登录态注入) |
|
||||||
|
| `router/` | 路由注册(每个 save/query 都套 `authMW`) |
|
||||||
|
| `worker/` | phis_poll_worker、mock_task_worker、幂等存储、批次报告 |
|
||||||
|
|
||||||
|
`chis/` 目录尤其庞大(30+ 文件),是复杂度集中地。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 复杂度来源分析
|
||||||
|
|
||||||
|
### 3.1 由「网页逆向」逼出来的复杂度 —— 新项目应整体删除
|
||||||
|
|
||||||
|
| 旧项目机制 | 为什么存在 | OSI 下的命运 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **SM2 加密**(`util/encrypt.go`:`EncryptSM2`/`GenerateLwD`/`GenerateQueryD`/`YearLlxKey`) | 网页登录与查询参数 `lw_d`/`d` 用 SM2 公钥加密,按年份还有动态字段名 `2026llx` | ❌ 删除。OSI 用 MD5 头签名 |
|
||||||
|
| **登录流程**(`chis/login.go`、`LoginWithRoleHint`、`identity_chain.go`) | 网页要先登录拿 Cookie,且要处理多角色选择 | ❌ 删除。OSI 无登录 |
|
||||||
|
| **Cookie/会话**(`middleware/cookie.go`、`chis/auth.go` `RedisCookieAuth`、`validate_cookie.go`、`util/redis.go` cookie 缓存、`cookie_ttl` 配置) | 维持网页登录态、跨请求复用、过期重登 | ❌ 删除。OSI 每请求现签,无状态 |
|
||||||
|
| **身份链反查**(`chis/identity_chain.go`:`idCard → empiId/phrId/...`) | 网页保存需要先把身份证换成内部主键链路 | ❌ 删除/大幅简化。OSI 直接用 `idCard`+`checkId`,`phrId` 由创建接口回传 |
|
||||||
|
| **Chrome 报文对齐**(`chrome_payload_compat_enable`、各 `*_payload_normalizer.go`、`health_check_enricher.go`) | 必须把字段补齐成 Chrome F12 抓到的同款形态,否则后端拒绝 | ⚠ 转化。不再"对齐网页",而改为"映射到文档契约"——见下 |
|
||||||
|
| **完整度服务端重算**(`health_record_complete_level.go` 37/33 项、`health_check_perfection.go` 70 项) | 逆向得知网页会算 completeLevel/perfection,客户端不可信任传入值,需本地重算 | ⚠ 待定。OSI 有显式 `isFillShhj`,完整度很可能由平台算;需联调确认后决定保留与否 |
|
||||||
|
| **网页拟态请求头**(`buildCHISCallHeaders`:`Origin`/`Referer`/`X-Requested-With`/`Host`) | 让请求看起来像浏览器发的 | ❌ 删除。OSI 是正式服务接口,无需伪装 |
|
||||||
|
| **apitrace + 多套快照**(`util/apitrace.go`、`phis_snapshot_*`,且注释标"兼容保留") | 逆向期排障留下的多套追踪 | 🔁 收敛为一套 report log |
|
||||||
|
|
||||||
|
> 量级判断:`chis/` 目录约 70%~80% 的代码是为「骗过网页 + 复刻网页隐藏算法」服务的。
|
||||||
|
> OSI 接口让这部分**整体失去存在意义**。
|
||||||
|
|
||||||
|
### 3.2 值得保留的工程经验 —— 新项目应继承
|
||||||
|
|
||||||
|
| 旧项目机制 | 价值 | 新项目去向 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| **分层清晰 + 面向初级维护者**(AGENTS.md:可读、不过度抽象、必要中文注释) | 团队约定,降低接手成本 | ✅ 继承这套协作规范 |
|
||||||
|
| **统一调用入口**(`chis/client.go` `Call(ctx, account, path, req, out)`:拼地址→注入头→传输→反序列化→记日志) | 单点收敛传输与可观测 | ✅ 演化为 OSI `client.Call(ctx, serviceId, body, out)` |
|
||||||
|
| **传输层与代理**(`transport.go`/`http_client.go`,SOCKS5 支持、超时控制、避免 typed-nil jar panic) | 内网穿透/超时是真实运维需求 | ✅ 几乎原样保留(去掉 cookiejar) |
|
||||||
|
| **投递流水线**(`worker/`:任务校验分流、**重试分类**、**幂等去重**、**熔断**、**批次报告**、跑完自动退出) | 这是搬运管道的可靠性核心,与接口形态无关 | ✅ 重点保留并升级 |
|
||||||
|
| **重试错误分类**(网络/5xx/429/网络型 401 可重试;参数/权限不可重试;退避 `2s*attempt`) | 来之不易的运维经验 | ✅ 保留,适配 OSI 返回码(`405` 超时可重试) |
|
||||||
|
| **report log + 降级**(Redis 优先、不可用降级写本地 JSONL;带 trace_id) | 可观测 + 不强依赖 Redis | ✅ 保留,Redis 改为完全可选 |
|
||||||
|
| **统一错误体**(`APIError{error_code,error_message,trace_id}`) | 上游稳定解析 | ✅ 保留 |
|
||||||
|
| **配置驱动 + Redis 启动失败不阻断** | 联调友好 | ✅ 保留 |
|
||||||
|
|
||||||
|
### 3.3 旧项目的待改进点(新项目顺手修正)
|
||||||
|
|
||||||
|
- **幂等键弱**:旧键 `account|idCard|dataType|sha1(saveBody)`,依赖报文哈希,报文微调即视为新任务。OSI 有天然的 `checkId`,应作为幂等主键。
|
||||||
|
- **任务源是 mock 文件**:`mock_task_worker` 读本地 JSON,真实 PHIS 拉取/状态回写一直 TODO(见其 `phase9_todo_open_items.md`)。新项目应直接把 PHIS 接入做实。
|
||||||
|
- **多套追踪并存**:apitrace / reportlog / snapshot 三套,注释里自承"兼容保留",应收敛为一套。
|
||||||
|
- **handler 里大量"从原始 JSON 多路径兜底提取字段"**(`fillEMPIAndPHRFromContext` 等),是历史请求体不规范的补丁。新项目以明确契约杜绝。
|
||||||
|
- **server 与 worker 杂糅在 `main.go`**(flag 极多)。新项目用子命令拆分。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 给新项目的迁移启示(一句话)
|
||||||
|
|
||||||
|
> 把 `chis/` 里"对付网页"的 70% 删掉,保留并强化 `worker/` 那套"可靠投递"的 30%,
|
||||||
|
> 再把删掉的部分替换成一个**薄 OSI 客户端**和一个**厚字段映射层**。
|
||||||
@@ -0,0 +1,261 @@
|
|||||||
|
# 03 · 目标架构设计(chis_osi)
|
||||||
|
|
||||||
|
本文给出 `chis_osi` 的整体架构、目录结构、数据流、分层职责与关键设计决策。
|
||||||
|
设计原则延续团队约定:**清晰分层、面向初级维护者、不过度抽象、优先标准库**。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 设计目标
|
||||||
|
|
||||||
|
1. **薄客户端**:OSI 调用收敛为「MD5 签名 + 统一信封 + HTTP 传输」三件事,一个 `Call` 贯穿。
|
||||||
|
2. **厚映射层**:把"PHIS 数据 → OSI 文档契约"的字段/字典映射做成**显式、表驱动、可单测**的独立层——这是新项目的核心资产。
|
||||||
|
3. **可靠投递**:继承旧项目的重试分类/幂等/熔断/批次报告,并用 `checkId` 升级幂等。
|
||||||
|
4. **无状态、弱依赖**:删除登录/Cookie/Redis 会话;Redis 仅用于幂等与报告缓存,且完全可选。
|
||||||
|
5. **可联调、可对账、可交接**:一套 report log + 快照,trace 贯穿 PHIS 任务号 / checkId。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 系统上下文
|
||||||
|
|
||||||
|
```
|
||||||
|
┌────────────┐ 拉取待上送任务/明细 ┌─────────────────────────────┐ POST /osi/api/... ┌──────────────┐
|
||||||
|
│ PHIS │ ───────────────────────▶ │ chis_osi │ ────签名+信封+JSON──▶ │ CHIS OSI │
|
||||||
|
│ 公卫上游 │ ◀─────────────────────── │ (本项目:转换 + 投递 + 对账) │ ◀──── code/data ───── │ 省基卫平台 │
|
||||||
|
└────────────┘ 回写状态(done/fail) └─────────────────────────────┘ └──────────────┘
|
||||||
|
│ ▲
|
||||||
|
字典缓存读写 │ │ 可观测/幂等
|
||||||
|
▼ │
|
||||||
|
┌──────────────┐
|
||||||
|
│ Redis(可选) │
|
||||||
|
│ + 本地文件 │
|
||||||
|
└──────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
两种运行形态(同一份代码、子命令切换):
|
||||||
|
- **server**:对外提供同步 HTTP API(供其它系统直接调用投递),便于联调与按需触发。
|
||||||
|
- **deliver-worker**:定时从 PHIS 拉取 → 映射 → 投递 → 回写状态,是生产主力。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 目录结构
|
||||||
|
|
||||||
|
```
|
||||||
|
chis_osi/
|
||||||
|
├── cmd/
|
||||||
|
│ ├── server/ main:HTTP 服务入口
|
||||||
|
│ └── deliver/ main:投递 worker 入口
|
||||||
|
├── config/ viper 配置(osi/phis/redis/log/proxy)
|
||||||
|
├── internal/
|
||||||
|
│ ├── osi/ ★ 薄客户端:与平台的全部交互
|
||||||
|
│ │ ├── sign.go MD5 签名(ts + password)
|
||||||
|
│ │ ├── transport.go HTTP 发送、超时、SOCKS5
|
||||||
|
│ │ ├── client.go Call(ctx, serviceId, body, &out):注入头+信封+发送+判码
|
||||||
|
│ │ ├── codes.go 返回码常量与成功/可重试判定
|
||||||
|
│ │ ├── jkda.go 档案:Create/Update/Find/FindRqbj
|
||||||
|
│ │ ├── jktj.go 体检:Create/Update/Query/List/Last
|
||||||
|
│ │ ├── lnr.go 老年人:自理评估、中医体质辨识
|
||||||
|
│ │ ├── zyjkzd.go 中医健康指导
|
||||||
|
│ │ └── public.go 网格/责任医生/药品/机构 字典查询
|
||||||
|
│ ├── contract/ ★ 校验后的接口契约(请求/响应结构体 + serviceId 常量)
|
||||||
|
│ │ ├── envelope.go 通用信封:{serviceId, uploadinfo|baseInfo, manageInfo}
|
||||||
|
│ │ ├── jkda.go / jktj.go / lnr.go / ...
|
||||||
|
│ ├── mapping/ ★ PHIS→OSI 映射(本项目核心)
|
||||||
|
│ │ ├── dict.go 码表:性别/民族/血型/职业/文化程度/婚姻/医保...(双向)
|
||||||
|
│ │ ├── health_record.go 档案字段映射 + 校验
|
||||||
|
│ │ ├── health_check.go 体检字段映射
|
||||||
|
│ │ ├── elderly.go 老年人映射
|
||||||
|
│ │ └── checkid.go checkId 生成(确定性,幂等键来源)
|
||||||
|
│ ├── source/phis/ PHIS 拉取客户端 + 任务模型 + 状态回写
|
||||||
|
│ ├── pipeline/ 投递编排:校验→映射→调用→分类→重试→幂等→报告
|
||||||
|
│ │ ├── deliver.go 单条投递
|
||||||
|
│ │ ├── retry.go 重试与错误分类
|
||||||
|
│ │ ├── idempotency.go 基于 checkId 的去重存储(redis|file)
|
||||||
|
│ │ ├── circuit.go 熔断
|
||||||
|
│ │ └── report.go 批次报告
|
||||||
|
│ ├── observ/ report log(redis 优先,文件降级)+ 快照 + trace
|
||||||
|
│ └── store/ redis 客户端封装(可选)+ 文件存储
|
||||||
|
├── handler/ HTTP handlers(server 模式对外接口)
|
||||||
|
├── router/ 路由
|
||||||
|
├── docs/
|
||||||
|
└── AGENTS.md 沿用旧项目协作规范
|
||||||
|
```
|
||||||
|
|
||||||
|
> `internal/` 用于约束包边界,避免被外部误用;若团队更习惯扁平结构,可去掉 `internal/` 层级,保持包名不变。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 分层职责(自底向上)
|
||||||
|
|
||||||
|
### 4.1 `osi` —— 薄客户端
|
||||||
|
|
||||||
|
唯一与平台对话的层。核心是一个泛化调用:
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 伪代码:统一调用。serviceId 固定常量;body 是已映射好的数据节点;out 反序列化目标。
|
||||||
|
func (c *Client) Call(ctx context.Context, serviceID string, body any, out any) (Result, error) {
|
||||||
|
ts := nowMillis() // 13 位毫秒
|
||||||
|
pwd := md5Hex("ts=" + ts + "&ask=" + c.ask) // 32 位小写
|
||||||
|
headers := map[string]string{
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"orgCode": c.orgCode,
|
||||||
|
"deviceSN": c.deviceSN,
|
||||||
|
"ts": ts,
|
||||||
|
"userName": c.userName,
|
||||||
|
"password": pwd,
|
||||||
|
}
|
||||||
|
payload := envelope{ServiceID: serviceID, Body: body} // 按 create/query 决定 uploadinfo|baseInfo
|
||||||
|
status, raw, err := c.transport.PostJSON(ctx, c.baseURL+"/osi/api"+pathOf(serviceID), payload, headers)
|
||||||
|
// 记录 report log;解析 {code,message,data};按 code 判定成功/超时(405)
|
||||||
|
...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
要点:
|
||||||
|
- **签名只在这一层**,业务层完全无感。
|
||||||
|
- `code == "1"` 成功;`405` 超时(可重试);其它失败。统一在 `codes.go` 判定。
|
||||||
|
- 各 `jkda.go/jktj.go/...` 只是给 `Call` 包一层带类型的方法(如 `CreateHealthRecord(ctx, *contract.HealthRecordCreate)`)。
|
||||||
|
- 传输层从旧项目 `transport.go`/`http_client.go` 直接移植(去掉 cookiejar、去掉网页拟态头)。
|
||||||
|
|
||||||
|
### 4.2 `contract` —— 校验后的接口契约
|
||||||
|
|
||||||
|
按 01 文档第 7 节,**不照搬 docx**。这里维护一份经联调校准的结构体与常量,承担"文档坑点的唯一修正点"。
|
||||||
|
信封统一:
|
||||||
|
|
||||||
|
```go
|
||||||
|
type Envelope struct {
|
||||||
|
ServiceID string `json:"serviceId"`
|
||||||
|
UploadInfo any `json:"uploadinfo,omitempty"` // 创建/更新
|
||||||
|
BaseInfo any `json:"baseInfo,omitempty"` // 查询
|
||||||
|
}
|
||||||
|
type ManageInfo struct { DSFMC, OperateUnit, OperateUser string }
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 `mapping` —— PHIS→OSI 映射(核心)
|
||||||
|
|
||||||
|
把旧项目"对齐 Chrome"的隐式逻辑,重写为"对齐文档"的显式逻辑:
|
||||||
|
|
||||||
|
- **字典层 `dict.go`**:集中所有码表(性别 0/1/2/9、民族 56 项、血型、职业、文化程度、婚姻、医保支付方式、人群标记 personSign 等)。提供 `PHISToOSI` / `OSIToPHIS` 双向查表,未命中有明确报错而非静默丢值。
|
||||||
|
- **字段映射**:每个业务域一个文件,函数签名形如 `func MapHealthRecord(src phis.Record) (contract.HealthRecordCreate, []ValidationError)`。返回结构化校验错误,缺必填字段在投递前就拦截。
|
||||||
|
- **多选字段**:既往史/家族史/残疾等"逗号拼接多选"集中处理。
|
||||||
|
- **`checkId` 生成 `checkid.go`**:确定性生成(如 `源系统ID|业务类型|源记录主键` 哈希),保证同一源记录重试得到同一 checkId → 平台侧不重复建档。**这是幂等的根。**
|
||||||
|
|
||||||
|
> 完整度(completeLevel/perfection):默认**不本地计算**,按文档字段如实上送,依赖平台计算。
|
||||||
|
> 若联调发现平台要求接入方计算,再把旧项目 `health_record_complete_level.go`/`health_check_perfection.go` 移植进 `mapping/`(开放问题,见第 8 节)。
|
||||||
|
|
||||||
|
### 4.4 `source/phis` —— 任务源
|
||||||
|
|
||||||
|
- 拉取待上送明细(替代旧项目的本地 mock 文件)。
|
||||||
|
- 任务模型:`{taskId, dataType, payload(PHIS原始), ...}`。
|
||||||
|
- 投递后回写状态 `done/retry/failed`(旧项目一直 TODO,新项目做实)。
|
||||||
|
- `trace_id` 建议直接用 PHIS 任务号,贯穿日志与报告。
|
||||||
|
|
||||||
|
### 4.5 `pipeline` —— 投递编排
|
||||||
|
|
||||||
|
单条投递流程(继承旧项目 worker 经验):
|
||||||
|
|
||||||
|
```
|
||||||
|
拉取任务 → 校验+按 dataType 分流 → mapping 映射(+校验) → 计算 checkId
|
||||||
|
→ 幂等检查(命中 success 则跳过) → osi.Call → 按 code 分类
|
||||||
|
→ 成功:记 success + 回写 PHIS done
|
||||||
|
→ 可重试失败:退避重试(最多 N 次) / 触发熔断
|
||||||
|
→ 不可重试失败:记 failed + 回写 PHIS failed
|
||||||
|
→ 全程写 report log,结束出批次报告
|
||||||
|
```
|
||||||
|
|
||||||
|
- **重试分类**(移植并适配 OSI):网络错误 / HTTP 5xx / 429 / `code==405`(超时) 可重试;参数错、权限错、映射校验错不可重试;退避 `2s*attempt`,最多 3 次。
|
||||||
|
- **幂等**:键 = `checkId`(替代旧项目报文哈希);存储 redis 优先、文件降级。
|
||||||
|
- **熔断**:连续网络失败达阈值则暂停(阈值/休眠秒可配)。
|
||||||
|
- **批次报告**:`total/success/failed/skipped/retry`、失败 Top、逐条明细(沿用旧项目格式)。
|
||||||
|
|
||||||
|
### 4.6 `observ` / `store` —— 可观测与存储
|
||||||
|
|
||||||
|
- 一套 report log(收敛旧项目的 apitrace/reportlog/snapshot 三套):记录每次 PHIS 输入、映射结果、OSI 请求/响应、最终判定。
|
||||||
|
- Redis 优先、本地 JSONL 降级;Redis 启动失败不阻断(沿用旧项目)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. 关键时序:一条体检记录的投递
|
||||||
|
|
||||||
|
```
|
||||||
|
deliver-worker source/phis mapping osi.Client CHIS OSI
|
||||||
|
│ 拉取任务 │ │ │ │
|
||||||
|
│ ───────────────────▶│ │ │ │
|
||||||
|
│ ◀─ task(原始体检) │ │ │ │
|
||||||
|
│ MapHealthCheck ─────────────────────▶ │ │ │
|
||||||
|
│ ◀─ contract + 校验ok │ │ │ │
|
||||||
|
│ 计算 checkId / 幂等检查(未命中) │ │ │
|
||||||
|
│ Call(JKTJ00001) ───────────────────────────────────────▶ │ │
|
||||||
|
│ │ │ 签名+信封+POST ──────────────────▶ │
|
||||||
|
│ │ │ │ ◀─ {code:"1"} ── │
|
||||||
|
│ ◀── Result(success, phrId...) ──────────────────────────── │ │
|
||||||
|
│ 记 success + report │ │ │ │
|
||||||
|
│ 回写 PHIS done ────▶│ │ │ │
|
||||||
|
```
|
||||||
|
|
||||||
|
失败分支:`code!="1"` 且非超时 → 不重试,记 failed + 回写;网络错误/`405` → 退避重试,超次数计熔断。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 配置(对比旧项目,做减法)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
# === OSI 平台 ===
|
||||||
|
osi_base_url: "http://<hostname>" # 不含 /osi/api
|
||||||
|
osi_org_code: "<机构社会信用代码>"
|
||||||
|
osi_user_name: "<平台分配用户名/DSFMC>"
|
||||||
|
osi_ask: "<平台下发密钥,仅参与签名>" # 敏感,建议走环境变量
|
||||||
|
osi_device_sn: "<设备序列号>"
|
||||||
|
osi_operate_user: "<默认责任医生ID>" # 可被任务覆盖
|
||||||
|
osi_timeout_sec: 20
|
||||||
|
socks5_proxy: "" # 可选,保留
|
||||||
|
|
||||||
|
# === PHIS 任务源 ===
|
||||||
|
phis_base_url: "..."
|
||||||
|
phis_token: "..."
|
||||||
|
phis_region_code: "..."
|
||||||
|
phis_poll_interval_sec: 5
|
||||||
|
|
||||||
|
# === Redis(可选,仅幂等/报告缓存)===
|
||||||
|
redis_addr: "" # 留空=纯文件模式
|
||||||
|
redis_db: 0
|
||||||
|
report_log_file_dir: "logs"
|
||||||
|
|
||||||
|
# === 投递 ===
|
||||||
|
retry_max: 3
|
||||||
|
circuit_fail_threshold: 5
|
||||||
|
circuit_sleep_sec: 60
|
||||||
|
```
|
||||||
|
|
||||||
|
**已删除**(相比旧项目):`sm2_pub_key`、`cookie_ttl`、`chrome_payload_compat_enable`、`health_record_debug_log`、`api_trace_*`、登录账号密码、role hint。
|
||||||
|
|
||||||
|
> 敏感项 `osi_ask` 不写进仓库配置,按 AGENTS.md「不硬编码密钥」走环境变量或部署密文。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 与旧项目的映射对照(一图流)
|
||||||
|
|
||||||
|
| 旧项目 | 新项目 | 变化 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `util/encrypt.go`(SM2) | `osi/sign.go`(MD5) | 重写,体量骤降 |
|
||||||
|
| `chis/login.go`+`middleware/cookie.go`+`RedisCookieAuth`+`validate_cookie.go` | — | 整体删除 |
|
||||||
|
| `chis/identity_chain.go` | — | 删除(用 idCard+checkId) |
|
||||||
|
| `chis/client.go`/`transport.go`/`http_client.go` | `osi/client.go`/`transport.go` | 移植简化(去 cookiejar/拟态头) |
|
||||||
|
| `chis/*_payload_normalizer.go`/`*_enricher.go` | `mapping/*.go` | 语义从"对齐网页"改为"对齐文档" |
|
||||||
|
| `chis/*_complete_level.go`/`*_perfection.go` | (默认删除,待联调) | 开放问题 |
|
||||||
|
| `worker/`(retry/idempotency/circuit/report) | `pipeline/` | 保留升级,checkId 做幂等键 |
|
||||||
|
| `util/reportlog.go`+`apitrace.go`+snapshot | `observ/` | 三套收敛为一套 |
|
||||||
|
| `model/` | `contract/` | 改为"校验后契约" |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 8. 开放问题(需厂家/联调确认)
|
||||||
|
|
||||||
|
1. **完整度是否需接入方计算**?(`completeLevel`/`perfection` 是否由平台算;`isFillShhj` 之外有无完整度入参)
|
||||||
|
2. **列表/查询类接口的完整路径与 serviceId**(档案列表、自理/体质/中医指导的列表与查询,docx 缺漏)。
|
||||||
|
3. **中医健康指导(zyjkzd)的 serviceId 与字段表**。
|
||||||
|
4. **`405` 之外的错误码字典**(是否有更细的失败码用于重试/告警分级)。
|
||||||
|
5. **`checkId` 规则约束**(长度 20、是否要求全局唯一、平台是否以 checkId 做幂等去重——若是,则更新走 update 还是 create 幂等覆盖)。
|
||||||
|
6. **`deviceSN` 取值来源**(一机构一值还是一设备一值)。
|
||||||
|
7. **沙箱环境地址与一组真实可用账号**,用于回归样本采集。
|
||||||
|
|
||||||
|
> 这些问题不阻塞架构落地:薄客户端、契约、映射层、流水线均可先按现有文档搭骨架,再以联调样本校准。
|
||||||
@@ -0,0 +1,147 @@
|
|||||||
|
# 04 · 字段与接口映射
|
||||||
|
|
||||||
|
本文是 `mapping/` 与 `contract/` 两层的落地指引:接口能力映射、PHIS→OSI 字段映射策略、码表清单、checkId 幂等键设计。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. 接口能力 → 内部方法映射
|
||||||
|
|
||||||
|
| OSI serviceId | 路径 | 内部方法(`osi` 包) | dataType(pipeline 分流) |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| JKDA00001 | `/jkda/create` | `CreateHealthRecord` | `health_record` |
|
||||||
|
| JKDA00003 | `/jkda/update` | `UpdateHealthRecord` | `health_record` |
|
||||||
|
| JKDA00002 | `/auto/jkda/find` | `FindHealthRecord` | (查询) |
|
||||||
|
| JKDA00005 | `/jkda/findrqbj` | `FindPersonSign` | (查询) |
|
||||||
|
| JKTJ00001 | `/jktj/create` | `CreateHealthCheck` | `health_check` |
|
||||||
|
| JKTJ00003 | `/jktj/update` | `UpdateHealthCheck` | `health_check` |
|
||||||
|
| JKTJ00002 | `/auto/jktj/query` | `QueryHealthCheck` | (查询) |
|
||||||
|
| JKTJLIST00002 | `/auto/jktjlist/query` | `ListHealthCheckPeople` | (查询) |
|
||||||
|
| JKTJLSJL00002 | `/auto/jktjlscx/query` | `LastHealthCheck` | (查询) |
|
||||||
|
| LNRZLPG00001 | `/lnrzlpg/create` | `CreateElderlySelfCare` | `elderly_self_care` |
|
||||||
|
| LNRZLPG00003 | `/lnrzlpg/update` | `UpdateElderlySelfCare` | `elderly_self_care` |
|
||||||
|
| LNRZYTZ00001 | `/lnrzyygl/create` | `CreateTCMConstitution` | `tcm` |
|
||||||
|
| LNRZYTZ00003 | `/lnrzyygl/update` | `UpdateTCMConstitution` | `tcm` |
|
||||||
|
| (待确认) | `/auto/zyjkzd/create` | `SaveTCMGuidance` | `tcm_guidance` |
|
||||||
|
| (待确认) | `/auto/zyjkzd/update` | `UpdateTCMGuidance` | `tcm_guidance` |
|
||||||
|
| WGDZ00001 | `/auto/wgdzcx/query` | `QueryGridAddress` | (字典) |
|
||||||
|
| ZRYS00001 | `/auto/zryscx/query` | `QueryDoctors` | (字典) |
|
||||||
|
| YPML00001 | `/auto/ypmlcx/query` | `QueryDrugs` | (字典) |
|
||||||
|
| CXJG00002 | `/auto/cxjg/query` | `QueryOrgs` | (字典) |
|
||||||
|
|
||||||
|
> ⚠ "(待确认)"项以 01 文档第 7 节为准,联调回填后更新 `osi/codes.go` 的 serviceId 常量与 `pathOf` 路由表。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. 字段映射策略
|
||||||
|
|
||||||
|
### 2.1 三类字段,三种处理
|
||||||
|
|
||||||
|
1. **直传字段**:PHIS 与 OSI 同义且同形(如姓名、出生日期 `yyyy-MM-dd`)。直接拷贝,做必填/长度校验。
|
||||||
|
2. **码表字段**:枚举值需经字典转换(性别、民族、血型、职业…)。统一走 `dict.go` 双向查表,未命中报 `ValidationError`,**绝不静默置空**。
|
||||||
|
3. **结构/多选字段**:嵌套节点(既往史/家族史/手术/外伤/输血/生活环境)、逗号拼接多选。集中在各业务域映射函数里组装。
|
||||||
|
|
||||||
|
### 2.2 映射函数约定
|
||||||
|
|
||||||
|
```go
|
||||||
|
// 返回结构化校验错误,pipeline 在投递前据此拦截,避免把脏数据打到平台。
|
||||||
|
func MapHealthRecord(src phis.HealthRecord, ctx MapContext) (contract.HealthRecordCreate, []ValidationError)
|
||||||
|
```
|
||||||
|
|
||||||
|
- `MapContext` 携带机构级常量:`orgCode/userName/operateUser/regionCode` 默认值与字典快照。
|
||||||
|
- 校验项:必填(idCard/personName/sexCode/birthday/mobileNumber/regionCode/manaDoctorId/manaUnitId…)、长度上限、码表合法性、日期格式。
|
||||||
|
- 映射是**纯函数**,便于用 01 文档样例 + 联调真实样本做单测(对齐旧项目"可测试改动补最小必要测试")。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. 码表清单(`dict.go` 初始内容)
|
||||||
|
|
||||||
|
> 取值来自 docx 字段说明,建模时全部落为常量表,并加注释标"来源:OSI 文档 §字段名"。
|
||||||
|
|
||||||
|
| 维度 | 取值(节选) |
|
||||||
|
| --- | --- |
|
||||||
|
| 性别 sexCode | 0 未知 / 1 男 / 2 女 / 9 未说明 |
|
||||||
|
| 户籍 registeredPermanent | 1 户籍 / 2 非户籍 |
|
||||||
|
| 血型 bloodTypeCode | 1 A / 2 B / 3 O / 4 AB / 5 不详 |
|
||||||
|
| RH rhBloodCode | 1 阳性 / 2 阴性 / 3 不详 |
|
||||||
|
| 文化程度 educationCode | 10 研究生 / 20 本科 / 31 大专 / 41 中专 / 47 技校 / 60 高中 / 70 初中 / 80 小学 / 90 文盲 / 91 不详 |
|
||||||
|
| 职业 workCode | 0 负责人 / 1·2 专技 / 3 办事 / 4 商业服务 / 5 农林牧渔 / 9-9 生产运输 / X 军人 / Y 其他 / 8 无职业 |
|
||||||
|
| 婚姻 maritalStatusCode | 10 未婚 / 20 已婚 / 30 丧偶 / 40 离婚 / 90 未说明 |
|
||||||
|
| 医保 insuranceCode(多选) | 01 城镇职工 / 02 城乡居民 / 04 贫困救助 / 05 商业 / 06 全公费 / 07 全自费 / 99 其他 |
|
||||||
|
| 民族 nationCode | 01 汉 … 56 基诺 / 99 其他(56 项全表) |
|
||||||
|
| 人群标记 personSign | PU 普通 / GRQY 已签约 / LAO 老年 / GAO 高血压 / TANG 糖尿病 / FU 孕产妇 / ER 儿童 / FEI 肺结核 / JING 精神 / CAN 残疾 |
|
||||||
|
| 药物过敏 ywgms(多选) | 0101 无 / 0102 青霉素 / 0103 磺胺 / 0104 链霉素 / 0109 其他 |
|
||||||
|
| 既往疾病 jwsjbcode | 0201 无 / 0202 高血压 / 0203 糖尿病 / … / 0299 其他 |
|
||||||
|
| 残疾 cjqk(多选) | 1101 无 / 1102 视力 / 1103 听力 / 1104 言语 / 1105 肢体 / 1106 智力 / 1107 精神 / 1108 孤独症 / 1109 脑瘫 / 1199 其他 |
|
||||||
|
| 厨房排风 cookAirTool | 1 无 / 2 油烟机 / 3 换气扇 / 4 烟囱 / 9 其他 |
|
||||||
|
| 燃料 fuelType | 1 液化气 / 2 煤 / 3 天然气 / 4 沼气 / 5 柴火 / 9 其他 |
|
||||||
|
| 饮水 waterSourceCode | 1 自来水 / 2 净化水 / 3 井水 / 4 河湖水 / 5 塘水 / 9 其他 |
|
||||||
|
| 厕所 washroom | 1 卫生厕所 / 2 粪池式 / 3 马桶 / 4 露天粪坑 / 5 简易棚厕 / 6 其他 |
|
||||||
|
| 机构类型 organizType | A 医院 / B 社区中心(站) / C 卫生院 / D 门诊诊所村室 / D6 村卫生室 / R 市卫生局 |
|
||||||
|
|
||||||
|
> 多选字段统一用英文逗号拼接(文档示例如 `"0102,0103"`)。注意 docx 个别返回样例用了中文逗号,解析时两者都要兼容。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. 主数据依赖:字典服务先行
|
||||||
|
|
||||||
|
创建类接口的若干必填字段依赖公共服务查询结果,建议在投递前用字典缓存解析:
|
||||||
|
|
||||||
|
```
|
||||||
|
regionCode ← QueryGridAddress(parentCode) // 网格地址
|
||||||
|
manaDoctorId/operateUser ← QueryDoctors(manaUnitId) // 责任医生
|
||||||
|
manaUnitId/organizCode ← QueryOrgs(...) // 机构
|
||||||
|
```
|
||||||
|
|
||||||
|
策略:启动或定时拉取这些字典,缓存到本地(redis 可选 + 内存),映射时按 PHIS 的地址/医生/机构名称反查 OSI 码。命中失败计入校验错误,不投递。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 5. checkId 与幂等键设计
|
||||||
|
|
||||||
|
`checkId`(第三方业务唯一识别码,长度 20)是新项目幂等的核心。
|
||||||
|
|
||||||
|
### 5.1 生成规则(`mapping/checkid.go`)
|
||||||
|
|
||||||
|
要求**确定性**:同一源记录无论重试多少次,都得到同一 checkId。
|
||||||
|
|
||||||
|
```
|
||||||
|
checkId = 截断20位( 编码( 源系统标识 | dataType | 源记录主键 [ | 版本/更新时间] ) )
|
||||||
|
```
|
||||||
|
|
||||||
|
- 同一条源记录的"创建"应使用稳定 checkId → 平台据此识别为同一档案,避免重复建档。
|
||||||
|
- "更新"沿用原 checkId(走 update 接口)。
|
||||||
|
- 若源记录内容变更需视为新版本上送,可把"更新时间/版本号"纳入 checkId 计算——**取决于平台是否以 checkId 去重**(见 03 文档开放问题 5)。
|
||||||
|
|
||||||
|
### 5.2 幂等存储
|
||||||
|
|
||||||
|
- 键:`checkId`(替代旧项目 `account|idCard|dataType|sha1(saveBody)`)。
|
||||||
|
- 值:`{status: success|failed, phrId, lastTs, attempts}`。
|
||||||
|
- 命中 `success`:跳过,不重复投递。
|
||||||
|
- `failed`:允许后续重试。
|
||||||
|
- 存储:redis 优先、本地 JSON 降级(沿用旧项目 `idempotency_store` 思路)。
|
||||||
|
|
||||||
|
> 用 checkId 做幂等键的好处:与平台对账口径一致(平台也认 checkId),且不受报文字段微调影响。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 6. 校验与错误分类(与 pipeline 配合)
|
||||||
|
|
||||||
|
| 错误来源 | 分类 | 处理 |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| 映射缺必填/码表未命中/格式非法 | 不可重试(数据错) | 直接 failed,回写 PHIS,附 ValidationError 明细 |
|
||||||
|
| 网络错误 / SOCKS / EOF / 超时 | 可重试 | 退避重试,计熔断 |
|
||||||
|
| OSI `code == "405"`(服务超时) | 可重试 | 退避重试 |
|
||||||
|
| OSI `code` 非 1 非 405(业务/权限拒绝) | 不可重试 | failed,记 message 供排查 |
|
||||||
|
| HTTP 5xx / 429 | 可重试 | 退避重试 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 7. 待补字段表
|
||||||
|
|
||||||
|
以下接口的完整字段表 docx 未充分给出或存在坑点,建模时以联调样本为准并在此登记:
|
||||||
|
|
||||||
|
- [ ] 体检 `jktj/create` 全量字段(hcData/lsData/exaData/aeData,体量最大)
|
||||||
|
- [ ] 老年人自理评估 `lnrzlpg` 字段
|
||||||
|
- [ ] 中医体质辨识 `lnrzyygl` 字段
|
||||||
|
- [ ] 中医健康指导 `zyjkzd` 字段 + serviceId
|
||||||
|
- [ ] 各列表/查询接口的分页与返回数组结构
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
# 05 · 实施路线图
|
||||||
|
|
||||||
|
分阶段落地 `chis_osi`,每阶段都「可运行、可回归、可联调」,沿用旧项目"小步、可测、可交接"的节奏。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 阶段 0 · 脚手架与契约骨架
|
||||||
|
|
||||||
|
- [ ] 初始化 Go module、`cmd/server`、`cmd/deliver` 双入口、`config/`(viper)。
|
||||||
|
- [ ] 落地 `osi/sign.go`(MD5 签名)+ 单测:用文档约定的 `ts/ask` 校验 `password` 形态(32 位小写)。
|
||||||
|
- [ ] 移植旧项目 `transport.go`/`http_client.go` → `osi/transport.go`(保留 SOCKS5/超时,去 cookiejar 与拟态头)。
|
||||||
|
- [ ] `osi/client.go` 的 `Call(serviceId, body, out)`:注入头+信封+发送+判码(`code=="1"`/`405`)。
|
||||||
|
- [ ] `contract/envelope.go` + `osi/codes.go`(serviceId 常量 + `pathOf` 路由)。
|
||||||
|
- **验收**:对任一最简查询接口(如机构查询 CXJG00002)发真实请求,拿到 `code/message`。
|
||||||
|
|
||||||
|
## 阶段 1 · 字典服务打通
|
||||||
|
|
||||||
|
- [ ] 实现 `public.go` 四个查询:网格/责任医生/药品/机构。
|
||||||
|
- [ ] `mapping/dict.go` 落地全部码表(含 56 项民族)。
|
||||||
|
- [ ] 字典缓存(内存 + redis 可选),供映射层反查 `regionCode/manaDoctorId/manaUnitId`。
|
||||||
|
- **验收**:能用真实机构码查到下级网格、责任医生、机构树。
|
||||||
|
|
||||||
|
## 阶段 2 · 健康档案闭环(第一条业务线)
|
||||||
|
|
||||||
|
- [ ] `contract/jkda.go` + `mapping/health_record.go` + `mapping/checkid.go`。
|
||||||
|
- [ ] `osi/jkda.go`:Create/Update/Find/FindRqbj。
|
||||||
|
- [ ] `handler` + `router`:暴露 `/api/health-record/save`(server 模式联调用)。
|
||||||
|
- [ ] 用 01 文档样例 + 联调样本写映射单测。
|
||||||
|
- **验收**:一条 PHIS 档案 → 映射 → create → 平台返回 `code:"1"` 与 `phrId`;重复投递被幂等跳过。
|
||||||
|
|
||||||
|
## 阶段 3 · 投递流水线
|
||||||
|
|
||||||
|
- [ ] `pipeline`:`deliver.go`/`retry.go`/`idempotency.go`(checkId)/`circuit.go`/`report.go`。
|
||||||
|
- [ ] `observ`:一套 report log(redis 优先、文件降级)+ 快照 + trace。
|
||||||
|
- [ ] `cmd/deliver`:定时驱动(先用本地任务文件 mock,对齐旧项目可跑形态)。
|
||||||
|
- **验收**:批量任务跑完出批次报告(total/success/failed/skipped/retry + 失败 Top);网络不可达触发熔断且行为符合预期。
|
||||||
|
|
||||||
|
## 阶段 4 · 其余业务线
|
||||||
|
|
||||||
|
- [ ] 体检(JKTJ,字段最多,重点)、老年人自理评估、中医体质辨识、中医健康指导。
|
||||||
|
- [ ] 各自的 contract/mapping/osi 方法 + 单测。
|
||||||
|
- **验收**:四类 dataType 均能走通 create/update/query。
|
||||||
|
|
||||||
|
## 阶段 5 · PHIS 真实接入与状态回写
|
||||||
|
|
||||||
|
- [ ] `source/phis`:真实拉取接口替换 mock;任务模型对齐。
|
||||||
|
- [ ] 投递结果回写 PHIS(done/retry/failed),trace_id 用 PHIS 任务号贯穿。
|
||||||
|
- **验收**:PHIS→chis_osi→CHIS 全链路自动跑通,状态可回查。
|
||||||
|
|
||||||
|
## 阶段 6 · 加固与交接
|
||||||
|
|
||||||
|
- [ ] 完整度问题定论(见开放问题 1):若平台要求接入方算,移植旧项目 complete_level/perfection 到 `mapping/`。
|
||||||
|
- [ ] 配置/密钥走环境变量,`osi_ask` 不入库。
|
||||||
|
- [ ] `docs/` 补 `运维与排障.md`、`联调清单.md`。
|
||||||
|
- [ ] `go test ./...` 全绿。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 联调前置清单(向厂家索要)
|
||||||
|
|
||||||
|
1. 沙箱环境 `hostname` 与一组可用 `orgCode/userName/ask/deviceSN/operateUser`。
|
||||||
|
2. docx 缺漏的列表/查询接口路径与 serviceId(档案列表、自理/体质/中医指导列表与查询)。
|
||||||
|
3. 中医健康指导 zyjkzd 的字段表与 serviceId。
|
||||||
|
4. checkId 唯一性与去重规则;create/update 的幂等语义。
|
||||||
|
5. 完整度是否由平台计算。
|
||||||
|
6. 错误码字典(除 `405` 外)。
|
||||||
|
7. 每个创建接口的一组真实成功请求/响应样本(做映射回归基线)。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 风险与对策
|
||||||
|
|
||||||
|
| 风险 | 对策 |
|
||||||
|
| --- | --- |
|
||||||
|
| docx 字段表有错(serviceId 串台、字段名/类型不一致) | 以 `contract/` 为唯一修正点 + 联调样本回归(01 文档第 7 节) |
|
||||||
|
| 列表/查询接口文档缺漏 | 阶段性向厂家索要,不阻塞创建类主线 |
|
||||||
|
| 码表庞大易错(民族 56 项等) | 集中 `dict.go` + 单测覆盖 + 未命中显式报错 |
|
||||||
|
| 完整度算法不明 | 默认不算、依赖平台;联调定论后再决定是否移植旧逻辑 |
|
||||||
|
| 平台限流/超时 | 复用熔断 + `405` 重试;投递并发可配 |
|
||||||
|
| 密钥泄露 | `ask` 仅参与签名、走环境变量、不入库不入日志 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 工作量直觉(相对旧项目)
|
||||||
|
|
||||||
|
- 鉴权/会话/传输:**大幅减少**(无 SM2/登录/Cookie/Redis 会话/身份链)。
|
||||||
|
- 映射层:**与旧项目相当或略增**(但从"逆向猜"变为"照文档写",更确定、更可测)。
|
||||||
|
- 投递流水线:**基本复用**旧项目经验,少量适配。
|
||||||
|
- 净效果:总复杂度显著下降,且代码意图清晰可交接。
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
# chis_osi 对接设计文档
|
||||||
|
|
||||||
|
本目录是「广东省基层医疗机构管理系统(CHIS)统一对外服务接口(OSI)」对接项目 `chis_osi` 的设计文档集。
|
||||||
|
|
||||||
|
## 背景
|
||||||
|
|
||||||
|
省基卫厂家(和宇健康科技)已提供**官方服务端对接接口**《统一对外服务接口 API 规范文档 V1.5.7》。
|
||||||
|
本项目基于该官方接口重新对接,并参考既有项目 `chis_upload`(通过逆向 Chrome F12 网页接口实现)的工程经验,
|
||||||
|
设计一套**更简洁、更稳定、更易维护**的整体架构。
|
||||||
|
|
||||||
|
> 关键判断:官方 OSI 接口是**无状态的服务端到服务端 JSON 接口**,鉴权方式为请求头 MD5 签名。
|
||||||
|
> 这意味着 `chis_upload` 中为对付网页逆向而引入的大量复杂度(SM2 登录加密、Cookie/Redis 会话、
|
||||||
|
> 身份链路反查、Chrome 报文对齐补齐)在新项目中**可以整体删除**。新项目的核心复杂度从「如何骗过网页」
|
||||||
|
> 转移到「如何把上游 PHIS 数据正确映射成 OSI 文档约定的字段」。
|
||||||
|
|
||||||
|
## 文档索引
|
||||||
|
|
||||||
|
| 文档 | 内容 |
|
||||||
|
| --- | --- |
|
||||||
|
| [01-OSI接口规范分析.md](01-OSI接口规范分析.md) | 官方 OSI 接口的鉴权、报文约定、全量接口清单与 serviceId 映射、文档坑点 |
|
||||||
|
| [02-旧项目架构评估.md](02-旧项目架构评估.md) | `chis_upload` 的分层与组件、哪些复杂度由逆向驱动、哪些经验值得保留 |
|
||||||
|
| [03-目标架构设计.md](03-目标架构设计.md) | `chis_osi` 的整体架构、分层、目录结构、数据流与时序、关键设计决策 |
|
||||||
|
| [04-字段与接口映射.md](04-字段与接口映射.md) | OSI 接口↔内部能力映射、PHIS→OSI 字段/字典映射策略、checkId 幂等键 |
|
||||||
|
| [05-实施路线图.md](05-实施路线图.md) | 分阶段落地计划、配置项、可观测性、测试与联调清单 |
|
||||||
|
|
||||||
|
## 一句话结论
|
||||||
|
|
||||||
|
> 用一个**无状态 OSI 客户端(MD5 签名 + 统一信封)** + 一个**表驱动的 PHIS→OSI 映射层** +
|
||||||
|
> 一个**复用旧项目经验的投递流水线(校验/映射/签名/调用/分类重试/幂等/批次报告)**,
|
||||||
|
> 替代旧项目里因网页逆向而堆积的会话与补齐逻辑。
|
||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user