From 4a5134093bfc561346832d035a6789e27e580dbd Mon Sep 17 00:00:00 2001 From: ila Date: Mon, 6 Jul 2026 21:52:56 +0800 Subject: [PATCH] =?UTF-8?q?docs:=20=E5=90=8C=E6=AD=A5=20T-002=20=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E7=8A=B6=E6=80=81=E5=B9=B6=E9=87=8D=E6=8E=92=E7=9C=8B?= =?UTF-8?q?=E6=9D=BF=E4=B8=BA=E6=9F=A5=E8=AF=A2=E5=85=88=E8=A1=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - T-001/T-002 标记 DONE,current-state 更新 Go 骨架现实 - 看板重排 Phase 1/2 → Q(查询)/M(映射)/D(字典+创建),查询档案不依赖字典 - osi/jkda.go 拆为 T-203(Find)/T-206(Create),显式化 Create 对字典缓存的依赖 - docs/05 加执行顺序注记 Co-Authored-By: Claude Opus 4.8 --- docs/05-实施路线图.md | 9 ++++++-- docs/current-state.md | 16 +++++++------- progress.md | 17 ++++++++++++++- tasks.md | 49 ++++++++++++++++++++++++++++--------------- 4 files changed, 64 insertions(+), 27 deletions(-) diff --git a/docs/05-实施路线图.md b/docs/05-实施路线图.md index 9a38228..7a735dc 100644 --- a/docs/05-实施路线图.md +++ b/docs/05-实施路线图.md @@ -7,12 +7,16 @@ ## 阶段 0 · 脚手架与契约骨架 - [x] 初始化 Go module、单 `main.go`(`-mode server|deliver`)、`config/`(viper)。 -- [ ] 落地 `osi/sign.go`(MD5 签名)+ 单测:用文档约定的 `ts/ask` 校验 `password` 形态(32 位小写)。 +- [x] 落地 `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)`:注入头+信封+发送+判码(成功码实测 `"01"`,按去前导零判定;`405` 可重试,见 docs/01 §1)。 - [ ] `contract/envelope.go` + `osi/codes.go`(serviceId 常量 + `pathOf` 路由)。 - **验收**:对任一最简查询接口(如机构查询 CXJG00002)发真实请求,拿到 `code/message`。 +> **执行顺序注记**:`tasks.md` 已按"查询先行"重排为 Q(查询)→ M(映射)→ D(字典+创建)。 +> 原因:查询档案(Find)不依赖字典、映射单测只需码表+假字典快照,只有真实创建闭环才需字典反查主数据。 +> 下面阶段 1/2 是能力全景,实际领取顺序以 `tasks.md` 为准。 + ## 阶段 1 · 字典服务打通 - [ ] 实现 `public.go` 四个查询:网格/责任医生/药品/机构。 @@ -87,4 +91,5 @@ - 映射层:**与旧项目相当或略增**(但从"逆向猜"变为"照文档写",更确定、更可测)。 - 投递流水线:**基本复用**旧项目经验,少量适配。 - 净效果:总复杂度显著下降,且代码意图清晰可交接。 - + + diff --git a/docs/current-state.md b/docs/current-state.md index 8d06e9c..1e336ea 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -6,11 +6,11 @@ ## 当前快照 - 日期:2026-07-06 -- 阶段:**Phase 0 已起步**;T-001 已完成,Go module 与 CLI/config 骨架已建立 +- 阶段:**Phase 0 进行中**;T-001/T-002 已完成,Go module、CLI/config 骨架与 MD5 签名已建立 - 技术栈:Go 1.24 单二进制;`main.go -mode server|deliver`;配置读取使用 viper -- 生产代码:已有 `main.go`、`config/`、`go.mod`/`go.sum`;`osi/ contract/ mapping/` 等业务目录仍待后续任务建立 +- 生产代码:已有 `main.go`、`config/`、`osi/sign.go`、`go.mod`/`go.sum`;`contract/ mapping/ pipeline/` 等业务目录仍待后续任务建立 - 联调现实:**JKDA00002 个人档案查询已用 Python 脚本打通真实沙箱**(`code="01"`),实测契约沉淀在 `docs/04 §8` -- 测试:`go test ./...` 通过;当前测试覆盖 mode 解析与 `config.yaml.example` 加载 +- 测试:`go test ./...` 通过;当前测试覆盖 mode 解析、`config.yaml.example` 加载、MD5 签名和头部组装 - 标准启动路径:`./init.sh`(需要 bash/WSL 环境;当前 Windows 环境未安装 WSL,直接运行会失败) - 标准验证路径:`go test ./...`、`go build ./...` - 当前 blocker:无硬 blocker。软阻塞:厂家侧 B1/B2/B4/B5 契约缺口(见 `docs/06`),只影响阶段 4,不阻塞阶段 0~3 @@ -21,17 +21,18 @@ | --- | --- | --- | | `main.go` | 已有 | 单入口,解析 `-mode server|deliver` 与 `-config` | | `config/` | 已有 | viper 配置加载,含最小单测 | +| `osi/sign.go` | 已有 | OSI 请求头 MD5 签名与 headers 组装 | | `config.yaml.example` | 已有 | 占位配置,不含真实凭据 | | `go.mod` `go.sum` | 已有 | module `chis_osi`,依赖 viper | | `docs/` | 已有 | 设计文档集 01~06 + 本快照;`账号.txt` 本地留存不入库 | | `tasks.md` `progress.md` | 已有 | 任务看板 / 执行流水(根目录) | | `scripts/` | 已有·不入库 | Python 联调脚本(硬编码真实凭据与身份证,勿提交) | | `config.yaml` | 已有·不入库 | 真实凭据;不要提交 | -| `osi/` `contract/` `mapping/` 等 | 待建 | 路线图阶段 0~2 后续任务 | +| `contract/` `mapping/` `pipeline/` 等 | 待建 | 路线图阶段 0~2 后续任务 | ## 已验证事实(写代码时直接依赖) -- 签名:`password = md5("ts=<13位毫秒ts>&ask=")` 32 位小写,与平台一致(Python 已打通)。 +- 签名:`password = md5("ts=<13位毫秒ts>&ask=")` 32 位小写,与 Python `hashlib` 向量一致。 - 信封:查询也走 `{"serviceId", "uploadinfo": {"baseInfo", "manageInfo"}}`,见 `docs/04 §8`。 - 成功码:字符串 `"01"`(判定按去前导零 == `"1"`),见 `docs/01 §1`。 - 机构码分层:请求头 `orgCode`=18 位统信码 ≠ `manaUnitId`=9 位机构码 ≠ 12 位区划码,见 `docs/01 §3`。 @@ -43,6 +44,7 @@ # Go 骨架验证 go test ./... go build ./... +go test ./osi go run . -mode server -config config.yaml.example go run . -mode deliver -config config.yaml.example @@ -55,8 +57,8 @@ python3 scripts/query_health_record.py ## 下一步 -1. T-002:`osi/sign.go` MD5 头签名 + 单测。 -2. T-003:`osi/transport.go` 传输层骨架。 +1. T-003:`osi/transport.go` 传输层骨架,保留 SOCKS5/超时。 +2. T-004:`osi/client.go` + `contract/envelope.go` + `osi/codes.go`。 ## 维护规则 diff --git a/progress.md b/progress.md index fea1e9c..4dc48ac 100644 --- a/progress.md +++ b/progress.md @@ -56,4 +56,19 @@ - 验证:`go test ./...` 通过;`go build ./...` 通过;`go run . -mode server -config config.yaml.example` 输出 `chis_osi mode=server`;`go run . -mode deliver -config config.yaml.example` 输出 `chis_osi mode=deliver`。 - 验证补充:`bash init.sh` 在当前 Windows 环境失败,系统提示未安装 WSL;已用等价 Go 命令完成验收。 - 决策:Go module 使用本地模块名 `chis_osi`;`main.go` 只打印 mode,不打印配置值,避免泄露真实环境信息;`config.yaml.example` 只保留占位值。 -- 下一步:T-002(`osi/sign.go` MD5 头签名 + 单测)。 +- 下一步:T-002(`osi/sign.go` MD5 头签名 + 单测)。 + +## 2026-07-06 T-002 MD5 头签名 + +- 状态:DONE +- 变更:新增 `osi/sign.go` 与 `osi/sign_test.go`;`BuildHeaders` 组装 OSI 必需请求头,`SignPassword` 按 `md5("ts=&ask=")` 输出 32 位小写;`tasks.md` 标记 T-002 完成;`docs/05` 勾选阶段 0 第二项。 +- 验证:先运行 `go test ./osi` 看到缺少 `SignPassword`/`BuildHeaders`/`HeaderInput` 的预期失败;实现后 `go test ./osi` 通过;`go test ./...` 通过;`go build ./...` 通过。 +- 决策:固定测试向量 `ts=1700000000123&ask=secret-key -> 008aceff8247cb42d2a99b2c48d0ac88` 来自 Python `hashlib.md5(...).hexdigest()`,用于对齐本地联调脚本签名算法;`ask` 只参与签名,不进入 headers。 +- 下一步:T-003(`osi/transport.go` 传输层,保留 SOCKS5/超时)。 + +## 2026-07-06 看板重排(查询先行) + +- 状态:DONE(规划调整,无代码) +- 变更:`tasks.md` 把 Phase 1(字典)/Phase 2(档案) 重排为 Phase Q(查询)→M(映射)→D(字典+创建);`osi/jkda.go` 拆为 T-203(Find/FindRqbj) 与 T-206(Create/Update);`docs/05` 加执行顺序注记。任务 ID 不变。 +- 决策:查询档案(Find)不依赖字典,映射纯函数单测只需码表+注入假字典快照,只有真实创建闭环才需 T-101/T-103 字典反查——故把字典接口降到创建之前、查询之后。顺带把"Create 依赖字典缓存"从隐性依赖显式化到 T-206。 +- 下一步:仍是 T-003(Phase 0 未变),Phase 0 完成后按 Q→M→D 领取。 diff --git a/tasks.md b/tasks.md index bb521eb..5a73a91 100644 --- a/tasks.md +++ b/tasks.md @@ -26,34 +26,47 @@ | ID | 任务 | 依赖 | 验收要点 | 状态 | | --- | --- | --- | --- | --- | | T-001 | 初始化 go module + `main.go` 双子命令骨架 + `config/`(viper) | - | `go build ./...` 通过;`-mode server\|deliver` 可解析;`config.yaml.example` 占位值就绪 | DONE | -| T-002 | `osi/sign.go` MD5 头签名 + 单测 | T-001 | 单测校验 `password=md5("ts=&ask=")` 32 位小写;与 Python 脚本(scripts/,本地)产出比对一致 | TODO | +| T-002 | `osi/sign.go` MD5 头签名 + 单测 | T-001 | 单测校验 `password=md5("ts=&ask=")` 32 位小写;与 Python 脚本(scripts/,本地)产出比对一致 | DONE | | T-003 | `osi/transport.go`:从 chis_upload 移植传输层 | T-001 | 保留 SOCKS5/超时;去掉 cookiejar 与网页拟态头;单测或最小连通验证 | TODO | | T-004 | `osi/client.go` `Call` + `osi/codes.go` + `contract/envelope.go` | T-002, T-003 | 信封为 `serviceId`+`uploadinfo{baseInfo,manageInfo,...}`(docs/04 §8);成功码按去前导零 == `"1"` 判定(实测 `"01"`,docs/01 §1);`405` 归类可重试 | TODO | | T-005 | 阶段 0 验收:Go 侧真实请求打通 + 配置 `init.sh` | T-004 | 用 JKDA00002(Python 已验证的同一查询)发真实请求拿到 `code="01"`;`./init.sh` 三命令替换完成且可运行 | TODO | -## Phase 1 · 字典服务(路线图阶段 1) +> **执行顺序说明(2026-07-06 重排,查询先行)**:原路线图"阶段1字典 → 阶段2档案"的顺序假设了先建字典。 +> 但**查询档案(Find)根本不依赖字典**,**映射纯函数单测**也只需码表 + 注入假字典快照。 +> 真正需要字典查询接口(网格/责任医生/机构反查)的只有**真实创建闭环**。 +> 故按能力重排为 Q(查询)→ M(映射)→ D(字典+创建);ID 保持不变,`osi/jkda.go` 的 Find 与 Create 拆成 T-203 / T-206。 +> 字母命名的执行阶段(B/0/Q/M/D)区别于 Backlog 里的"路线图阶段 3~6"。 + +## Phase Q · 查询档案打通(Find,不依赖字典) + +| ID | 任务 | 依赖 | 验收要点 | 状态 | +| --- | --- | --- | --- | --- | +| T-201 | `contract/jkda.go`:以 docs/04 §8 实测契约建**查询响应**结构体 | T-004 | Find 响应能完整反序列化本地联调样本(`data` 数组、null 字段用指针/omitempty、`adressNumber` 坑点拼写);创建请求结构体延到 T-206 补 | TODO | +| T-203 | `osi/jkda.go`:**Find + FindRqbj** | T-201 | 用 JKDA00002 真实请求打通,拿到 `code="01"` 与档案数据,与 Python 脚本结果一致 | TODO | + +## Phase M · 映射层(码表 + 纯函数,注入假字典快照) + +| ID | 任务 | 依赖 | 验收要点 | 状态 | +| --- | --- | --- | --- | --- | +| T-102 | `mapping/dict.go` 全量码表(含 56 项民族) | T-001 | 双向查表;未命中显式 ValidationError;单测覆盖(静态枚举,不走网络) | TODO | +| T-202 | `mapping/health_record.go` + `mapping/checkid.go` | T-102, T-201 | 映射纯函数 + 结构化校验错误;主数据经 `MapContext` 字典快照注入(单测塞假快照);checkId 确定性生成单测 | TODO | +| T-205 | 映射单测基线:docx 样例 + 联调样本 | T-202 | `go test ./mapping/...` 全绿;必填/码表/格式校验生效 | TODO | + +## Phase D · 字典服务与创建闭环(真实主数据反查) | ID | 任务 | 依赖 | 验收要点 | 状态 | | --- | --- | --- | --- | --- | | T-101 | `osi/public.go` 四个字典查询(网格/责任医生/药品/机构) | T-005 | 真实机构码能查到下级网格、责任医生、机构树 | TODO | -| T-102 | `mapping/dict.go` 全量码表(含 56 项民族) | T-001 | 双向查表;未命中显式 ValidationError;单测覆盖 | TODO | | T-103 | 字典缓存(内存 + redis 可选) | T-101, T-102 | 映射层能反查 `regionCode/manaDoctorId/manaUnitId`;redis 不可用不阻断 | TODO | - -## Phase 2 · 健康档案闭环(路线图阶段 2,第一条业务线) - -| ID | 任务 | 依赖 | 验收要点 | 状态 | -| --- | --- | --- | --- | --- | -| T-201 | `contract/jkda.go`:以 docs/04 §8 实测契约为基线建结构体 | T-004 | Find 响应能完整反序列化本地联调样本(`data` 数组、null 字段用指针/omitempty、`adressNumber` 坑点拼写) | TODO | -| T-202 | `mapping/health_record.go` + `mapping/checkid.go` | T-102, T-201 | 映射纯函数 + 结构化校验错误;checkId 确定性生成单测 | TODO | -| T-203 | `osi/jkda.go`:Create/Update/Find/FindRqbj | T-201 | Find 真实请求通过;Create 待测试档案确认后联调 | TODO | -| T-204 | `handler`+`router`:`/api/health-record/save` | T-203 | server 模式起服务,curl 全链路返回投递结果 | TODO | -| T-205 | 映射单测基线:docx 样例 + 联调样本 | T-202 | `go test ./mapping/...` 全绿;必填/码表/格式校验生效 | TODO | +| T-206 | `osi/jkda.go`:**Create/Update** + `contract/jkda.go` 补创建请求结构体 | T-201, T-202, T-103 | 一条档案经映射(真实字典快照)→ create → 平台返回成功码与 `phrId`;Update 沿用 checkId | TODO | +| T-204 | `handler`+`router`:`/api/health-record/save` | T-206 | server 模式起服务,curl 全链路返回投递结果 | TODO | ## 里程碑 - M1 = T-005:Go 客户端与平台真实握手成功(签名/信封/判码全对)。 -- M2 = T-103:字典服务可反查主数据。 -- M3 = T-205:档案业务线闭环 + 幂等,映射有回归基线。 +- M2 = T-203:**Go 版查询档案打通**(Find,不碰字典)。 +- M3 = T-205:映射层就绪 + 单测基线(假字典快照)。 +- M4 = T-206:真实创建闭环 + 幂等(字典反查主数据 → create → phrId)。 ## 待办池(Backlog,按路线图阶段 3~6 展开,进入时再拆小任务) @@ -62,5 +75,7 @@ - 阶段 5:PHIS 真实接入与状态回写。 - 阶段 6:加固与交接(完整度定论、密钥环境变量化、运维文档、全绿)。 - 联调依赖跟踪见 `docs/06-厂家联调清单.md`(B 组契约缺口会阻塞阶段 4)。 - - + + + +