feat(osi): 实现公开字典查询接口(T-101)
This commit is contained in:
@@ -206,3 +206,34 @@ checkId = 截断20位( 编码( 源系统标识 | dataType | 源记录主键 [ |
|
|||||||
| `status` `createDate` | 档案状态/建档日期 | |
|
| `status` `createDate` | 档案状态/建档日期 | |
|
||||||
|
|
||||||
> 建模提示:`data` 用数组承接(可能多档案/多版本);`insuranceType`、`otherPersonGroup`、`checkId` 等实测可为 `null`,结构体用指针或 `omitempty`,映射层不得因 null 报错。
|
> 建模提示:`data` 用数组承接(可能多档案/多版本);`insuranceType`、`otherPersonGroup`、`checkId` 等实测可为 `null`,结构体用指针或 `omitempty`,映射层不得因 null 报错。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 9. 公共字典查询 · 联调实测契约
|
||||||
|
|
||||||
|
> 来源:2026-07-07 沙箱验证。使用本地未入库凭据和一条已存在档案反查主数据后验证;下方只记录脱敏事实,不记录真实身份证、机构码、医生姓名或密钥。
|
||||||
|
|
||||||
|
### 9.1 请求信封
|
||||||
|
|
||||||
|
四个公共查询接口与 JKDA 查询一致,仍走统一信封:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"serviceId": "<公共查询serviceId>",
|
||||||
|
"uploadinfo": {
|
||||||
|
"baseInfo": { "...": "..." },
|
||||||
|
"manageInfo": { "DSFMC": "<userName>", "operateUser": "<operateUser>", "operateUnit": "<orgCode-18位>" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 9.2 已验证结果
|
||||||
|
|
||||||
|
| 接口 | serviceId | 真实验证结论 | 备注 |
|
||||||
|
| --- | --- | --- | --- |
|
||||||
|
| 网格地址查询 | `WGDZ00001` | `code="01"` 且返回数组非空 | 用已存在档案的区划/网格码派生 `parentCode` 候选值验证 |
|
||||||
|
| 责任医生查询 | `ZRYS00001` | `code="01"` 且返回数组非空 | 入参 `manaUnitId` 使用档案返回的 9 位机构码 |
|
||||||
|
| 机构查询 | `CXJG00002` | `code="01"` 且返回数组非空 | 入参 `organizCode` 使用档案返回的 9 位机构码 |
|
||||||
|
| 药品目录查询 | `YPML00001` | 未做真实联调 | T-101 已完成客户端封装与单测;后续有药品关键字/拼音码时补样本 |
|
||||||
|
|
||||||
|
> T-103 字典缓存可以先依赖网格、责任医生、机构三类真实验证结果。药品目录与健康档案创建闭环无直接依赖,不阻塞 T-103/T-206。
|
||||||
|
|||||||
+1
-2
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
## 阶段 1 · 字典服务打通
|
## 阶段 1 · 字典服务打通
|
||||||
|
|
||||||
- [ ] 实现 `public.go` 四个查询:网格/责任医生/药品/机构。
|
- [x] 实现 `public.go` 四个查询:网格/责任医生/药品/机构。
|
||||||
- [x] `mapping/dict.go` 落地全部码表(含 56 项民族)。
|
- [x] `mapping/dict.go` 落地全部码表(含 56 项民族)。
|
||||||
- [ ] 字典缓存(内存 + redis 可选),供映射层反查 `regionCode/manaDoctorId/manaUnitId`。
|
- [ ] 字典缓存(内存 + redis 可选),供映射层反查 `regionCode/manaDoctorId/manaUnitId`。
|
||||||
- **验收**:能用真实机构码查到下级网格、责任医生、机构树。
|
- **验收**:能用真实机构码查到下级网格、责任医生、机构树。
|
||||||
@@ -94,4 +94,3 @@
|
|||||||
- 映射层:**与旧项目相当或略增**(但从"逆向猜"变为"照文档写",更确定、更可测)。
|
- 映射层:**与旧项目相当或略增**(但从"逆向猜"变为"照文档写",更确定、更可测)。
|
||||||
- 投递流水线:**基本复用**旧项目经验,少量适配。
|
- 投递流水线:**基本复用**旧项目经验,少量适配。
|
||||||
- 净效果:总复杂度显著下降,且代码意图清晰可交接。
|
- 净效果:总复杂度显著下降,且代码意图清晰可交接。
|
||||||
|
|
||||||
|
|||||||
+15
-13
@@ -6,14 +6,14 @@
|
|||||||
## 当前快照
|
## 当前快照
|
||||||
|
|
||||||
- 日期:2026-07-07
|
- 日期:2026-07-07
|
||||||
- 阶段:**Phase M 映射层基线完成**;T-001~T-006、T-201、T-203、T-102、T-202、T-205 已验收,下一步进入 Phase D 的 T-101
|
- 阶段:**Phase D 公开字典查询完成**;T-001~T-006、T-201、T-203、T-102、T-202、T-205、T-101 已验收,下一步进入 T-103 字典缓存
|
||||||
- 技术栈:Go 1.24 单二进制;`main.go -mode server|deliver`;配置读取使用 viper,支持环境变量覆盖;OSI 客户端已具备签名、信封、传输、基础判码、JKDA00002 Find 与 JKDA00005 FindRqbj
|
- 技术栈:Go 1.24 单二进制;`main.go -mode server|deliver`;配置读取使用 viper,支持环境变量覆盖;OSI 客户端已具备签名、信封、传输、基础判码、JKDA00002 Find、JKDA00005 FindRqbj,以及 WGDZ/ZRYS/YPML/CXJG 四个公开查询薄封装
|
||||||
- 生产代码:已有 `main.go`、`config/`、`contract/envelope.go`、`osi/sign.go`、`osi/transport.go`、`osi/codes.go`、`osi/client.go`、`verify_jkda.go`、`go.mod`/`go.sum`;`contract/jkda.go` 已建立 JKDA00002 查询响应契约;`osi/jkda.go` 已实现 Find/FindRqbj;`mapping/dict.go` 已建立码表基线;`mapping/health_record.go` 与 `mapping/checkid.go` 已建立映射纯函数基线;映射样本回归基线已完成;`pipeline/` 等业务模块仍待后续任务建立
|
- 生产代码:已有 `main.go`、`config/`、`contract/envelope.go`、`contract/jkda.go`、`osi/sign.go`、`osi/transport.go`、`osi/codes.go`、`osi/client.go`、`osi/jkda.go`、`osi/public.go`、`verify_jkda.go`、`go.mod`/`go.sum`;`mapping/dict.go`、`mapping/health_record.go`、`mapping/checkid.go` 已建立映射纯函数与码表基线;`pipeline/` 等业务模块仍待后续任务建立
|
||||||
- 联调现实:**JKDA00002 个人档案查询已用 Go 侧真实请求打通**,返回 `code="01" message="操作成功" data_count=1`;Python 联调样本和实测契约沉淀在 `docs/04 §8`
|
- 联调现实:**JKDA00002 个人档案查询已用 Go 侧真实请求打通**,返回 `code="01" message="操作成功" data_count=1`;**公开查询 WGDZ00001/ZRYS00001/CXJG00002 已用真实档案主数据验证通过**,均返回 `code="01"` 且数组非空;药品目录 YPML00001 已完成客户端封装和单测,尚未做真实药品关键字样本验证
|
||||||
- 测试:`go test ./...` 通过;当前测试覆盖 mode 解析、配置加载与环境变量覆盖、MD5 签名、请求头组装、JSON POST 传输、头名大小写保留、identity 响应编码声明、超时配置、SOCKS5 代理地址校验、信封结构、serviceId 路由、成功/重试判码、Client.Call 请求与响应解析、JKDA00002 查询响应契约、JKDA00002 Find、JKDA00005 FindRqbj、映射码表双向查找、民族 01~56 完整性、健康档案映射必填/码表校验、checkId 确定性、docx/联调风格映射样本基线、JKDA00002 验证入口
|
- 测试:`go test ./...` 通过;当前测试覆盖 mode 解析、配置加载与环境变量覆盖、MD5 签名、请求头组装、JSON POST 传输、头名大小写保留、identity 响应编码声明、超时配置、SOCKS5 代理地址校验、信封结构、serviceId 路由、成功/重试判码、Client.Call 请求与响应解析、JKDA00002 查询响应契约、JKDA00002 Find、JKDA00005 FindRqbj、公开查询四接口、映射码表双向查找、民族 01~56 完整性、健康档案映射必填/码表校验、checkId 确定性、docx/联调风格映射样本基线、JKDA00002 验证入口
|
||||||
- 标准启动路径:`./init.sh` 已配置三步:依赖下载、`go test ./...`、`go run . -mode server -config config.yaml.example`
|
- 标准启动路径:`./init.sh` 已配置三步:依赖下载、`go test ./...`、`go run . -mode server -config config.yaml.example`
|
||||||
- 标准验证路径:`go test ./...`、`go build ./...`
|
- 标准验证路径:`go test ./...`、`go build ./...`
|
||||||
- 当前 blocker:无硬 blocker。软限制:当前机器从 Git Bash 启动 Go 会出现标准库路径/构建缓存权限异常,`init.sh` 无法完整跑完;PowerShell 下等价 Go 命令和真实请求通过。厂家侧 B1/B2/B4/B5 契约缺口只影响阶段 4,不阻塞 Phase Q/M/D 的当前路径
|
- 当前 blocker:无硬 blocker。软限制:当前机器从 Git Bash 启动 Go 会出现标准库路径/构建缓存权限异常,`init.sh` 无法完整跑完;PowerShell 下等价 Go 命令和真实请求通过。厂家侧 B1/B2/B4/B5 契约缺口只影响阶段 4,不阻塞当前 T-103/T-206 路径
|
||||||
|
|
||||||
## 当前目录要点
|
## 当前目录要点
|
||||||
|
|
||||||
@@ -22,19 +22,20 @@
|
|||||||
| `main.go` | 已有 | 单入口,解析 `-mode server|deliver`、`-config`,并提供 `-verify-jkda` 联调验证入口(身份证从 `OSI_VERIFY_ID_CARD` 读取) |
|
| `main.go` | 已有 | 单入口,解析 `-mode server|deliver`、`-config`,并提供 `-verify-jkda` 联调验证入口(身份证从 `OSI_VERIFY_ID_CARD` 读取) |
|
||||||
| `config/` | 已有 | viper 配置加载,支持 `OSI_*` 等环境变量覆盖 |
|
| `config/` | 已有 | viper 配置加载,支持 `OSI_*` 等环境变量覆盖 |
|
||||||
| `contract/envelope.go` | 已有 | OSI 通用信封与 `manageInfo` 结构 |
|
| `contract/envelope.go` | 已有 | OSI 通用信封与 `manageInfo` 结构 |
|
||||||
| `contract/jkda.go` | 已有 | JKDA00002 查询响应结构体;创建/更新请求结构体留到 T-206 |
|
| `contract/jkda.go` | 已有 | JKDA00002 查询响应结构体;创建请求结构体留到 T-206 |
|
||||||
| `osi/sign.go` | 已有 | OSI 请求头 MD5 签名与 headers 组装 |
|
| `osi/sign.go` | 已有 | OSI 请求头 MD5 签名与 headers 组装 |
|
||||||
| `osi/transport.go` | 已有 | OSI JSON POST 传输层;原始 HTTP/1.1 写请求以保留头名大小写;支持超时与可选 SOCKS5 |
|
| `osi/transport.go` | 已有 | OSI JSON POST 传输层;原始 HTTP/1.1 写请求以保留头名大小写;支持超时与可选 SOCKS5 |
|
||||||
| `osi/codes.go` | 已有 | serviceId 路由与成功/可重试判码 |
|
| `osi/codes.go` | 已有 | serviceId 路由与成功/可重试判码;已包含 JKDA 与四个公开查询 serviceId |
|
||||||
| `osi/client.go` | 已有 | 通用 `Call`:签名头 + 信封 + 传输 + 响应解析 |
|
| `osi/client.go` | 已有 | 通用 `Call`:签名头 + 信封 + 传输 + 响应解析 |
|
||||||
| `osi/jkda.go` | 已有 | JKDA00002 Find 与 JKDA00005 FindRqbj |
|
| `osi/jkda.go` | 已有 | JKDA00002 Find 与 JKDA00005 FindRqbj |
|
||||||
|
| `osi/public.go` | 已有 | WGDZ00001 网格、ZRYS00001 责任医生、YPML00001 药品、CXJG00002 机构查询 |
|
||||||
| `verify_jkda.go` | 已有 | JKDA00002 真实请求验证辅助入口 |
|
| `verify_jkda.go` | 已有 | JKDA00002 真实请求验证辅助入口 |
|
||||||
| `config.yaml.example` | 已有 | 占位配置,不含真实凭据 |
|
| `config.yaml.example` | 已有 | 占位配置,不含真实凭据 |
|
||||||
| `go.mod` `go.sum` | 已有 | module `chis_osi`,依赖 viper 与 `golang.org/x/net/proxy` |
|
| `go.mod` `go.sum` | 已有 | module `chis_osi`,依赖 viper 与 `golang.org/x/net/proxy` |
|
||||||
| `docs/` | 已有 | 设计文档集 01~06 + 本快照;`账号.txt` 本地留存不入库 |
|
| `docs/` | 已有 | 设计文档集 01~06 + 本快照;`docs/04 §9` 记录公开查询脱敏实测契约;`账号.txt` 本地留存不入库 |
|
||||||
| `tasks.md` `progress.md` | 已有 | 任务看板 / 执行流水(根目录) |
|
| `tasks.md` `progress.md` | 已有 | 任务看板 / 执行流水(根目录) |
|
||||||
| `scripts/` | 已有·不入库 | Python 联调脚本(硬编码真实凭据与身份证,勿提交) |
|
| `scripts/` | 已有·不入库 | Python 联调脚本(硬编码真实凭据与身份证,勿提交) |
|
||||||
| `config.yaml` | 已有·不入库 | 真实凭据;不要提交 |
|
| `config.yaml` | 已有·不入库 | 真实/本地配置;不要提交 |
|
||||||
| `mapping/dict.go` | 已有 | 性别/民族/血型/RH/文化程度/职业/婚姻/医保/personSign 双向码表;未命中返回 `ValidationError` |
|
| `mapping/dict.go` | 已有 | 性别/民族/血型/RH/文化程度/职业/婚姻/医保/personSign 双向码表;未命中返回 `ValidationError` |
|
||||||
| `mapping/health_record.go` `mapping/checkid.go` | 已有 | 健康档案映射草稿、字典快照注入、必填/码表校验、确定性 checkId |
|
| `mapping/health_record.go` `mapping/checkid.go` | 已有 | 健康档案映射草稿、字典快照注入、必填/码表校验、确定性 checkId |
|
||||||
| `mapping/health_record_baseline_test.go` | 已有 | docx 风格、联调风格、脏数据映射回归样本 |
|
| `mapping/health_record_baseline_test.go` | 已有 | docx 风格、联调风格、脏数据映射回归样本 |
|
||||||
@@ -43,13 +44,14 @@
|
|||||||
## 已验证事实(写代码时直接依赖)
|
## 已验证事实(写代码时直接依赖)
|
||||||
|
|
||||||
- 签名:`password = md5("ts=<13位毫秒ts>&ask=<ask>")` 32 位小写,与 Python `hashlib` 向量一致。
|
- 签名:`password = md5("ts=<13位毫秒ts>&ask=<ask>")` 32 位小写,与 Python `hashlib` 向量一致。
|
||||||
- 信封:查询也走 `{"serviceId", "uploadinfo": {"baseInfo", "manageInfo"}}`,见 `docs/04 §8`。
|
- 信封:查询也走 `{"serviceId", "uploadinfo": {"baseInfo", "manageInfo"}}`,见 `docs/04 §8` 与 `docs/04 §9`。
|
||||||
- 成功码:字符串 `"01"`(判定按去前导零 == `"1"`),见 `docs/01 §1`。
|
- 成功码:字符串 `"01"`(判定按去前导零 == `"1"`),见 `docs/01 §1`。
|
||||||
- `405` 为平台服务调用超时,可重试。
|
- `405` 为平台服务调用超时,可重试。
|
||||||
- 机构码分层:请求头 `orgCode`=18 位统信码 ≠ `manaUnitId`=9 位机构码 ≠ 12 位区划码,见 `docs/01 §3`。
|
- 机构码分层:请求头 `orgCode`=18 位统信码 ≠ `manaUnitId`=9 位机构码 ≠ 12 位区划码,见 `docs/01 §3`。
|
||||||
- `deviceSN` 查询可空。
|
- `deviceSN` 查询可空。
|
||||||
- OSI 代理语义:`socks5_proxy` 非空即只对 OSI 客户端走 SOCKS5,不做直连回退。
|
- OSI 代理语义:`socks5_proxy` 非空即只对 OSI 客户端走 SOCKS5,不做直连回退。
|
||||||
- 真实平台/代理对请求头名大小写敏感;Go `net/http` 会规范化头名,OSI 传输层需保留 `orgCode/deviceSN/userName` 的原始大小写。裸写传输层默认请求 `Accept-Encoding: identity`,避免收到未解压压缩响应。
|
- 真实平台/代理对请求头名大小写敏感;Go `net/http` 会规范化头名,OSI 传输层需保留 `orgCode/deviceSN/userName` 的原始大小写。裸写传输层默认请求 `Accept-Encoding: identity`,避免收到未解压压缩响应。
|
||||||
|
- 公开字典查询返回 `data` 数组;网格、责任医生、机构三类已真实验证可作为 T-103 字典缓存来源。
|
||||||
|
|
||||||
## 当前可运行内容
|
## 当前可运行内容
|
||||||
|
|
||||||
@@ -73,8 +75,8 @@ python3 scripts/query_health_record.py
|
|||||||
|
|
||||||
## 下一步
|
## 下一步
|
||||||
|
|
||||||
1. T-101:`osi/public.go` 四个字典查询(网格/责任医生/药品/机构)。
|
1. T-103:字典缓存(内存 + redis 可选),把 T-101 的网格/责任医生/机构查询结果转为映射层可用的 `regionCode/manaDoctorId/manaUnitId` 快照。
|
||||||
2. T-103:字典缓存(T-101 后)。
|
2. T-206:字典缓存就绪后补健康档案 Create/Update 与创建请求契约。
|
||||||
|
|
||||||
## 维护规则
|
## 维护规则
|
||||||
|
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import (
|
|||||||
const (
|
const (
|
||||||
ServiceIDJKDAFind = "JKDA00002"
|
ServiceIDJKDAFind = "JKDA00002"
|
||||||
ServiceIDJKDAFindRqbj = "JKDA00005"
|
ServiceIDJKDAFindRqbj = "JKDA00005"
|
||||||
|
ServiceIDGridAddress = "WGDZ00001"
|
||||||
|
ServiceIDDoctors = "ZRYS00001"
|
||||||
|
ServiceIDDrugs = "YPML00001"
|
||||||
|
ServiceIDOrgs = "CXJG00002"
|
||||||
)
|
)
|
||||||
|
|
||||||
const codeRetryableTimeout = "405"
|
const codeRetryableTimeout = "405"
|
||||||
@@ -15,6 +19,10 @@ const codeRetryableTimeout = "405"
|
|||||||
var servicePaths = map[string]string{
|
var servicePaths = map[string]string{
|
||||||
ServiceIDJKDAFind: "/auto/jkda/find",
|
ServiceIDJKDAFind: "/auto/jkda/find",
|
||||||
ServiceIDJKDAFindRqbj: "/jkda/findrqbj",
|
ServiceIDJKDAFindRqbj: "/jkda/findrqbj",
|
||||||
|
ServiceIDGridAddress: "/auto/wgdzcx/query",
|
||||||
|
ServiceIDDoctors: "/auto/zryscx/query",
|
||||||
|
ServiceIDDrugs: "/auto/ypmlcx/query",
|
||||||
|
ServiceIDOrgs: "/auto/cxjg/query",
|
||||||
}
|
}
|
||||||
|
|
||||||
func PathOf(serviceID string) (string, error) {
|
func PathOf(serviceID string) (string, error) {
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
package osi
|
||||||
|
|
||||||
|
import "context"
|
||||||
|
|
||||||
|
type GridAddressQuery struct {
|
||||||
|
ParentCode string `json:"parentCode,omitempty"`
|
||||||
|
PageNo int `json:"pageNo,omitempty"`
|
||||||
|
OperateUser string `json:"operateUser,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type GridAddress struct {
|
||||||
|
RegionCode string `json:"regionCode"`
|
||||||
|
RegionName string `json:"regionName"`
|
||||||
|
IsFamily string `json:"isFamily"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DoctorQuery struct {
|
||||||
|
ManaUnitID string `json:"manaUnitId,omitempty"`
|
||||||
|
OperateUser string `json:"operateUser,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Doctor struct {
|
||||||
|
PersonID string `json:"personId"`
|
||||||
|
PersonName string `json:"personName"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type DrugQuery struct {
|
||||||
|
PageNo int `json:"pageNo,omitempty"`
|
||||||
|
YPMC string `json:"ypmc,omitempty"`
|
||||||
|
PYM string `json:"pym,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Drug struct {
|
||||||
|
YPMC string `json:"ypmc"`
|
||||||
|
YPDW string `json:"ypdw"`
|
||||||
|
YPGG string `json:"ypgg"`
|
||||||
|
JLDW string `json:"jldw"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type OrgQuery struct {
|
||||||
|
OrganizCode string `json:"organizCode,omitempty"`
|
||||||
|
ParentID string `json:"parentId,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Org struct {
|
||||||
|
OrganizCode string `json:"organizCode"`
|
||||||
|
OrganizName string `json:"organizName"`
|
||||||
|
OrganizType string `json:"organizType"`
|
||||||
|
ParentID string `json:"parentId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) QueryGridAddress(ctx context.Context, query GridAddressQuery) ([]GridAddress, Result, error) {
|
||||||
|
var rows []GridAddress
|
||||||
|
result, err := c.Call(ctx, ServiceIDGridAddress, query, &rows)
|
||||||
|
return rows, result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) QueryDoctors(ctx context.Context, query DoctorQuery) ([]Doctor, Result, error) {
|
||||||
|
var rows []Doctor
|
||||||
|
result, err := c.Call(ctx, ServiceIDDoctors, query, &rows)
|
||||||
|
return rows, result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) QueryDrugs(ctx context.Context, query DrugQuery) ([]Drug, Result, error) {
|
||||||
|
var rows []Drug
|
||||||
|
result, err := c.Call(ctx, ServiceIDDrugs, query, &rows)
|
||||||
|
return rows, result, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) QueryOrgs(ctx context.Context, query OrgQuery) ([]Org, Result, error) {
|
||||||
|
var rows []Org
|
||||||
|
result, err := c.Call(ctx, ServiceIDOrgs, query, &rows)
|
||||||
|
return rows, result, err
|
||||||
|
}
|
||||||
@@ -0,0 +1,163 @@
|
|||||||
|
package osi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQueryGridAddressCallsWGDZ00001(t *testing.T) {
|
||||||
|
var seenPath string
|
||||||
|
var seenPayload map[string]any
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
seenPath = r.URL.Path
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&seenPayload); err != nil {
|
||||||
|
t.Fatalf("decode request: %v", err)
|
||||||
|
}
|
||||||
|
_, _ = w.Write([]byte(`{"code":"01","message":"操作成功","data":[{"regionCode":"441625000000","regionName":"测试网格","isFamily":"1"}]}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := newTestJKDAClient(t, server.URL)
|
||||||
|
rows, result, err := client.QueryGridAddress(context.Background(), GridAddressQuery{
|
||||||
|
ParentCode: "441625",
|
||||||
|
PageNo: 1,
|
||||||
|
OperateUser: "712041",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("QueryGridAddress: %v", err)
|
||||||
|
}
|
||||||
|
if !result.Success || result.Code != "01" {
|
||||||
|
t.Fatalf("result = %#v", result)
|
||||||
|
}
|
||||||
|
if seenPath != "/osi/api/auto/wgdzcx/query" {
|
||||||
|
t.Fatalf("path = %q", seenPath)
|
||||||
|
}
|
||||||
|
if seenPayload["serviceId"] != ServiceIDGridAddress {
|
||||||
|
t.Fatalf("serviceId = %v", seenPayload["serviceId"])
|
||||||
|
}
|
||||||
|
baseInfo := seenPayload["uploadinfo"].(map[string]any)["baseInfo"].(map[string]any)
|
||||||
|
if baseInfo["parentCode"] != "441625" || baseInfo["pageNo"] != float64(1) || baseInfo["operateUser"] != "712041" {
|
||||||
|
t.Fatalf("baseInfo = %#v", baseInfo)
|
||||||
|
}
|
||||||
|
if len(rows) != 1 || rows[0].RegionCode != "441625000000" || rows[0].RegionName != "测试网格" || rows[0].IsFamily != "1" {
|
||||||
|
t.Fatalf("rows = %#v", rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryDoctorsCallsZRYS00001(t *testing.T) {
|
||||||
|
var seenPath string
|
||||||
|
var seenPayload map[string]any
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
seenPath = r.URL.Path
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&seenPayload); err != nil {
|
||||||
|
t.Fatalf("decode request: %v", err)
|
||||||
|
}
|
||||||
|
_, _ = w.Write([]byte(`{"code":"01","message":"操作成功","data":[{"personId":"doc-001","personName":"测试医生"}]}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := newTestJKDAClient(t, server.URL)
|
||||||
|
rows, result, err := client.QueryDoctors(context.Background(), DoctorQuery{
|
||||||
|
ManaUnitID: "441625001",
|
||||||
|
OperateUser: "712041",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("QueryDoctors: %v", err)
|
||||||
|
}
|
||||||
|
if !result.Success || result.Code != "01" {
|
||||||
|
t.Fatalf("result = %#v", result)
|
||||||
|
}
|
||||||
|
if seenPath != "/osi/api/auto/zryscx/query" {
|
||||||
|
t.Fatalf("path = %q", seenPath)
|
||||||
|
}
|
||||||
|
if seenPayload["serviceId"] != ServiceIDDoctors {
|
||||||
|
t.Fatalf("serviceId = %v", seenPayload["serviceId"])
|
||||||
|
}
|
||||||
|
baseInfo := seenPayload["uploadinfo"].(map[string]any)["baseInfo"].(map[string]any)
|
||||||
|
if baseInfo["manaUnitId"] != "441625001" || baseInfo["operateUser"] != "712041" {
|
||||||
|
t.Fatalf("baseInfo = %#v", baseInfo)
|
||||||
|
}
|
||||||
|
if len(rows) != 1 || rows[0].PersonID != "doc-001" || rows[0].PersonName != "测试医生" {
|
||||||
|
t.Fatalf("rows = %#v", rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryDrugsCallsYPML00001(t *testing.T) {
|
||||||
|
var seenPath string
|
||||||
|
var seenPayload map[string]any
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
seenPath = r.URL.Path
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&seenPayload); err != nil {
|
||||||
|
t.Fatalf("decode request: %v", err)
|
||||||
|
}
|
||||||
|
_, _ = w.Write([]byte(`{"code":"01","message":"操作成功","data":[{"ypmc":"测试药品","ypdw":"盒","ypgg":"10mg","jldw":"片"}]}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := newTestJKDAClient(t, server.URL)
|
||||||
|
rows, result, err := client.QueryDrugs(context.Background(), DrugQuery{
|
||||||
|
PageNo: 1,
|
||||||
|
YPMC: "测试",
|
||||||
|
PYM: "CS",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("QueryDrugs: %v", err)
|
||||||
|
}
|
||||||
|
if !result.Success || result.Code != "01" {
|
||||||
|
t.Fatalf("result = %#v", result)
|
||||||
|
}
|
||||||
|
if seenPath != "/osi/api/auto/ypmlcx/query" {
|
||||||
|
t.Fatalf("path = %q", seenPath)
|
||||||
|
}
|
||||||
|
if seenPayload["serviceId"] != ServiceIDDrugs {
|
||||||
|
t.Fatalf("serviceId = %v", seenPayload["serviceId"])
|
||||||
|
}
|
||||||
|
baseInfo := seenPayload["uploadinfo"].(map[string]any)["baseInfo"].(map[string]any)
|
||||||
|
if baseInfo["pageNo"] != float64(1) || baseInfo["ypmc"] != "测试" || baseInfo["pym"] != "CS" {
|
||||||
|
t.Fatalf("baseInfo = %#v", baseInfo)
|
||||||
|
}
|
||||||
|
if len(rows) != 1 || rows[0].YPMC != "测试药品" || rows[0].YPDW != "盒" || rows[0].YPGG != "10mg" || rows[0].JLDW != "片" {
|
||||||
|
t.Fatalf("rows = %#v", rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryOrgsCallsCXJG00002(t *testing.T) {
|
||||||
|
var seenPath string
|
||||||
|
var seenPayload map[string]any
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
seenPath = r.URL.Path
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&seenPayload); err != nil {
|
||||||
|
t.Fatalf("decode request: %v", err)
|
||||||
|
}
|
||||||
|
_, _ = w.Write([]byte(`{"code":"01","message":"操作成功","data":[{"organizCode":"441625001","organizName":"测试机构","organizType":"1","parentId":"441625"}]}`))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
client := newTestJKDAClient(t, server.URL)
|
||||||
|
rows, result, err := client.QueryOrgs(context.Background(), OrgQuery{
|
||||||
|
OrganizCode: "441625001",
|
||||||
|
ParentID: "441625",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("QueryOrgs: %v", err)
|
||||||
|
}
|
||||||
|
if !result.Success || result.Code != "01" {
|
||||||
|
t.Fatalf("result = %#v", result)
|
||||||
|
}
|
||||||
|
if seenPath != "/osi/api/auto/cxjg/query" {
|
||||||
|
t.Fatalf("path = %q", seenPath)
|
||||||
|
}
|
||||||
|
if seenPayload["serviceId"] != ServiceIDOrgs {
|
||||||
|
t.Fatalf("serviceId = %v", seenPayload["serviceId"])
|
||||||
|
}
|
||||||
|
baseInfo := seenPayload["uploadinfo"].(map[string]any)["baseInfo"].(map[string]any)
|
||||||
|
if baseInfo["organizCode"] != "441625001" || baseInfo["parentId"] != "441625" {
|
||||||
|
t.Fatalf("baseInfo = %#v", baseInfo)
|
||||||
|
}
|
||||||
|
if len(rows) != 1 || rows[0].OrganizCode != "441625001" || rows[0].OrganizName != "测试机构" || rows[0].OrganizType != "1" || rows[0].ParentID != "441625" {
|
||||||
|
t.Fatalf("rows = %#v", rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
+12
@@ -147,3 +147,15 @@
|
|||||||
- 验证:`go test ./mapping -count=1` 通过;`go test ./...` 通过。
|
- 验证:`go test ./mapping -count=1` 通过;`go test ./...` 通过。
|
||||||
- 决策:样本全部脱敏,不提交本地真实身份证/姓名/电话;T-205 只建立映射测试基线,不扩展创建请求结构体。
|
- 决策:样本全部脱敏,不提交本地真实身份证/姓名/电话;T-205 只建立映射测试基线,不扩展创建请求结构体。
|
||||||
- 下一步:T-101(`osi/public.go` 四个字典查询)。
|
- 下一步:T-101(`osi/public.go` 四个字典查询)。
|
||||||
|
|
||||||
|
## 2026-07-07 T-101 公开字典查询
|
||||||
|
|
||||||
|
- 变更:新增 `osi/public.go`,实现 `QueryGridAddress`、`QueryDoctors`、`QueryDrugs`、`QueryOrgs` 四个公开查询薄封装;补充 `WGDZ00001`、`ZRYS00001`、`YPML00001`、`CXJG00002` serviceId 路由。
|
||||||
|
- 测试:先补 `osi/public_test.go`,覆盖四个接口的 path、serviceId、baseInfo 组装与 data 解码。
|
||||||
|
- RED:`go test ./osi -run "TestQuery(GridAddress|Doctors|Drugs|Orgs)" -count=1` 初次失败,缺少公开查询类型、serviceId 常量与方法。
|
||||||
|
- GREEN:`go test ./osi -run "TestQuery(GridAddress|Doctors|Drugs|Orgs)" -count=1` 通过。
|
||||||
|
- 验证:`go test ./osi -count=1` 通过。
|
||||||
|
- 验证:`go test ./...` 通过。
|
||||||
|
- 验证:`go build ./...` 通过。
|
||||||
|
- 联调:使用本地未入库脚本中的沙箱凭据和身份证,仅在临时 Go 程序内读取;先 JKDA00002 取一条档案主数据,再调用公开查询。结果:Find `code=01 count=1`;责任医生 `code=01 count>0`;机构 `code=01 count>0`;网格 `code=01 count>0`。未输出真实身份证、机构码、医生姓名或密钥。
|
||||||
|
- 说明:药品目录查询本次完成客户端封装和单测,T-101 验收要求未要求真实药品目录联调;后续需要用药品关键字/拼音码时再补真实样本。
|
||||||
|
|||||||
@@ -57,7 +57,7 @@
|
|||||||
|
|
||||||
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
| ID | 任务 | 依赖 | 验收要点 | 状态 |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| T-101 | `osi/public.go` 四个字典查询(网格/责任医生/药品/机构) | T-005 | 真实机构码能查到下级网格、责任医生、机构树 | TODO |
|
| T-101 | `osi/public.go` 四个字典查询(网格/责任医生/药品/机构) | T-005 | 真实机构码能查到下级网格、责任医生、机构树 | DONE |
|
||||||
| T-103 | 字典缓存(内存 + redis 可选) | T-101, T-102 | 映射层能反查 `regionCode/manaDoctorId/manaUnitId`;redis 不可用不阻断 | TODO |
|
| T-103 | 字典缓存(内存 + redis 可选) | T-101, T-102 | 映射层能反查 `regionCode/manaDoctorId/manaUnitId`;redis 不可用不阻断 | TODO |
|
||||||
| T-206 | `osi/jkda.go`:**Create/Update** + `contract/jkda.go` 补创建请求结构体 | T-201, T-202, T-103 | 一条档案经映射(真实字典快照)→ create → 平台返回成功码与 `phrId`;Update 沿用 checkId | 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 |
|
| T-204 | `handler`+`router`:`/api/health-record/save` | T-206 | server 模式起服务,curl 全链路返回投递结果 | TODO |
|
||||||
@@ -84,4 +84,3 @@
|
|||||||
| T-007 | 传输层支持 https/TLS | **生产环境地址确认为 https 时必须先做**(沙箱是 http,不阻塞当前开发) | `osi/transport.go` 裸 HTTP/1.1 写目前仅支持 http(明文 TCP、`scheme != "http"` 直接报错)。需在 TLS 下同样保留 `orgCode/deviceSN/userName` 头名大小写策略;对 https 目标能发起真实请求并拿到 `code`;单测覆盖 https 路径 | TODO(生产前 gate) |
|
| T-007 | 传输层支持 https/TLS | **生产环境地址确认为 https 时必须先做**(沙箱是 http,不阻塞当前开发) | `osi/transport.go` 裸 HTTP/1.1 写目前仅支持 http(明文 TCP、`scheme != "http"` 直接报错)。需在 TLS 下同样保留 `orgCode/deviceSN/userName` 头名大小写策略;对 https 目标能发起真实请求并拿到 `code`;单测覆盖 https 路径 | TODO(生产前 gate) |
|
||||||
|
|
||||||
> 来源:`docs/review/2026-07-06-phase0-review.md` P1-2。向厂家确认生产地址协议(见 `docs/06` A10)后,若为 https 则本任务提升为阻断项;若确认生产仍是 http,则关闭本任务并在传输层注释说明。
|
> 来源:`docs/review/2026-07-06-phase0-review.md` P1-2。向厂家确认生产地址协议(见 `docs/06` A10)后,若为 https 则本任务提升为阻断项;若确认生产仍是 http,则关闭本任务并在传输层注释说明。
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user