diff --git a/docs/07-本项目HTTP接口.md b/docs/07-本项目HTTP接口.md index cc06c6b..a9a7d07 100644 --- a/docs/07-本项目HTTP接口.md +++ b/docs/07-本项目HTTP接口.md @@ -4,6 +4,7 @@ > 这是本项目**自己**对外的接口的单一事实来源;调用**上游厂家 OSI** 的接口契约看 `docs/01`、`docs/04`。 > 新增/修改端点后同步本文(与 `handler/`、`server.go` 保持一致)。 +> 机器可读 OpenAPI 文档见 [`openapi.yaml`](openapi.yaml)。 --- @@ -63,7 +64,8 @@ GET /api/health-record/crowd - 上游:`JKDA00005` `/auto/jkda/findrqbj` - 返回:`data` 为对象,含 `personSign`、`idCard`、`phrId`(见 docs/04 §12) - `personSign` 是人群分类码,可能为逗号分隔多值;码表见 docs/04 §3 -### 2. 最近一次体检 + +### 3. 最近一次体检 ``` GET /api/health-check/last diff --git a/docs/README.md b/docs/README.md index 2769a66..17b5321 100644 --- a/docs/README.md +++ b/docs/README.md @@ -24,6 +24,7 @@ | [05-实施路线图.md](05-实施路线图.md) | 分阶段落地计划、配置项、可观测性、测试与联调清单 | | [06-厂家联调清单.md](06-厂家联调清单.md) | 向厂家索要的凭据/环境/契约/样本清单,带回填状态,可直接发对接人 | | [07-本项目HTTP接口.md](07-本项目HTTP接口.md) | 本项目 server 模式对外提供的 HTTP 查询端点(我方接口的事实来源)| +| [openapi.yaml](openapi.yaml) | 本项目 server 模式 HTTP 查询端点的 OpenAPI 3.0 机器可读接口文档 | | [current-state.md](current-state.md) | **当前实现状态快照**(可覆盖):仓库现实、已验证事实、可运行命令、blocker | ## 执行工件(根目录) diff --git a/docs/current-state.md b/docs/current-state.md index 1cf3065..3a6d320 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -6,9 +6,9 @@ ## 当前快照 - 日期:2026-07-09 -- 阶段:**Phase D 创建/更新代码基线完成,真实写入验收受阻**;T-001~T-006、T-201、T-203、T-102、T-202、T-205、T-101、T-103 已验收;T-206 已完成本地契约/客户端/映射组装代码,但未执行真实 `JKDA00001` create +- 阶段:**Phase D 创建/更新代码基线完成,真实写入验收受阻;准备 T-210 公开查询 HTTP API**;T-001~T-006、T-201、T-203、T-102、T-202、T-205、T-101、T-103、T-209 已验收;T-206 已完成本地契约/客户端/映射组装代码,但未执行真实 `JKDA00001` create - 技术栈:Go 1.24 单二进制;`main.go -mode server|deliver`;配置读取使用 viper,支持环境变量覆盖;OSI 客户端已具备签名、信封、传输、基础判码、JKDA00002 Find、JKDA00005 FindRqbj(人群分类,已按实测 auto 路径校准)、JKDA00001 Create、JKDA00003 Update,以及 WGDZ/ZRYS/YPML/CXJG 四个公开查询薄封装 -- 生产代码:已有 `main.go`、`config/`、`contract/envelope.go`、`contract/jkda.go`、`osi/` 薄客户端、`cache/dictionary.go`、`verify_jkda.go`、`go.mod`/`go.sum`;`mapping/dict.go`、`mapping/health_record.go`、`mapping/checkid.go` 已建立映射纯函数、码表基线、主数据反查接入点和创建请求组装;`handler/health_record.go`+`server.go` 提供 server 模式档案查询与人群分类查询端点;`pipeline/` 等业务模块仍待后续任务建立 +- 生产代码:已有 `main.go`、`config/`、`contract/envelope.go`、`contract/jkda.go`、`osi/` 薄客户端、`cache/dictionary.go`、`verify_jkda.go`、`go.mod`/`go.sum`;`mapping/dict.go`、`mapping/health_record.go`、`mapping/checkid.go` 已建立映射纯函数、码表基线、主数据反查接入点和创建请求组装;`handler/health_record.go`+`server.go` 提供 server 模式档案查询与人群分类查询端点;网格地址/责任医生/药品目录/机构查询已有 `osi` 客户端,待 T-210 暴露 HTTP API;`pipeline/` 等业务模块仍待后续任务建立 - 联调现实:**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)、公开查询四接口、字典缓存快照与持久化失败不阻断、映射码表双向查找、民族 01~56 完整性、健康档案映射必填/码表校验、主数据名称反查、创建请求组装、checkId 确定性、docx/联调风格映射样本基线、JKDA00002 验证入口 - 标准启动路径:`./init.sh` 已配置三步:依赖下载、`go test ./...`、`go run . -mode server -config config.yaml.example` @@ -94,9 +94,10 @@ python3 scripts/query_health_record.py > 读先行调整(2026-07-08):写入(T-206/T-204)被厂家授权锁死,先推其余业务线查询。 1. ✅ T-301/T-305 体检查询(最近一次 + 名单)、T-302 体检 HTTP 端点 —— 均已完成。 -2. **T-303 老年人查询**(自理/体质,serviceId 部分待 B4)——起 Python 探针试探。 -3. 并行催厂家(docs/06 ★ 优先催办):写入授权+测试档案、缺失单查/老年人 serviceId、checkId/完整度规则。 -4. 授权到位后回到 T-206 真实 create 验收 → T-204。 +2. **T-210 公开查询 HTTP API**:把人群分类、网格地址、责任医生、药品目录、机构查询暴露成 server 模式 HTTP API,并同步 `docs/07` 与 `docs/openapi.yaml`。 +3. T-303 老年人查询(自理/体质,serviceId 部分待 B4)——起 Python 探针试探。 +4. 并行催厂家(docs/06 ★ 优先催办):写入授权+测试档案、缺失单查/老年人 serviceId、checkId/完整度规则。 +5. 授权到位后回到 T-206 真实 create 验收 → T-204。 ## 维护规则 diff --git a/docs/openapi.yaml b/docs/openapi.yaml new file mode 100644 index 0000000..aa1551c --- /dev/null +++ b/docs/openapi.yaml @@ -0,0 +1,412 @@ +openapi: 3.0.3 +info: + title: chis_osi Server API + version: "0.1.0" + description: | + chis_osi server 模式对外提供的本机 HTTP 查询接口。 + + 本文档描述的是本项目自己的 HTTP API,不是上游 CHIS OSI 平台接口。 + 当前端点会原样透传上游平台返回的完整 JSON,响应中可能包含身份证、档案号、 + 体检记录等个人敏感信息。默认只应监听 127.0.0.1;如需对外暴露,必须先补鉴权、 + 访问日志脱敏与限流。 +servers: + - url: http://127.0.0.1:8080 + description: 本地默认 server 模式地址 +tags: + - name: health-record + description: 健康档案查询 + - name: health-check + description: 健康体检查询 +paths: + /api/health-record/find: + get: + tags: + - health-record + summary: 查询个人健康档案 + description: | + 查询个人健康档案,原样返回上游 JKDA00002 `/auto/jkda/find` 响应。 + + `idCard`、`phrid`、`personName`、`empiId` 四个查询参数必须且只能提供一个。 + `data` 为数组,内容为平台返回的完整档案聚合对象。 + operationId: findHealthRecord + parameters: + - $ref: "#/components/parameters/idCard" + - $ref: "#/components/parameters/phrid" + - $ref: "#/components/parameters/personName" + - $ref: "#/components/parameters/empiId" + responses: + "200": + description: 平台完整响应,成功和平台业务错误均原样透传。 + content: + application/json: + schema: + $ref: "#/components/schemas/HealthRecordFindResponse" + examples: + success: + value: + code: "01" + message: 操作成功 + data: + - phrId: PHRID_PLACEHOLDER + idCard: "440000********0000" + "400": + $ref: "#/components/responses/BadRequest" + "405": + $ref: "#/components/responses/MethodNotAllowed" + "502": + $ref: "#/components/responses/BadGateway" + /api/health-record/crowd: + get: + tags: + - health-record + summary: 查询人群分类/子档案标记 + description: | + 查询居民人群分类与子档案标记,原样返回上游 JKDA00005 + `/auto/jkda/findrqbj` 响应。 + + `idCard`、`phrid` 两个查询参数必须且只能提供一个。`personSign` 可能为逗号分隔 + 多值,码表见 docs/04 §3。 + operationId: findHealthRecordCrowd + parameters: + - $ref: "#/components/parameters/idCard" + - $ref: "#/components/parameters/phrid" + responses: + "200": + description: 平台完整响应,成功和平台业务错误均原样透传。 + content: + application/json: + schema: + $ref: "#/components/schemas/CrowdResponse" + examples: + success: + value: + code: "01" + message: 操作成功 + data: + personSign: "01,04" + idCard: "440000********0000" + phrId: PHRID_PLACEHOLDER + "400": + $ref: "#/components/responses/BadRequest" + "405": + $ref: "#/components/responses/MethodNotAllowed" + "502": + $ref: "#/components/responses/BadGateway" + /api/health-check/last: + get: + tags: + - health-check + summary: 查询最近一次体检 + description: | + 查询某人最近一次健康体检,原样返回上游 JKTJLSJL00002 + `/auto/jktjlscx/query` 响应。 + + `idCard`、`phrid`、`empiId` 三个查询参数必须且只能提供一个。 + `data` 为单个完整体检对象。 + operationId: findLastHealthCheck + parameters: + - $ref: "#/components/parameters/idCard" + - $ref: "#/components/parameters/phrid" + - $ref: "#/components/parameters/empiId" + responses: + "200": + description: 平台完整响应,成功和平台业务错误均原样透传。 + content: + application/json: + schema: + $ref: "#/components/schemas/HealthCheckLastResponse" + examples: + success: + value: + code: "01" + message: 操作成功 + data: + checkId: CHECK_ID_PLACEHOLDER + checkDate: "2025-01-01" + idCard: "440000********0000" + "400": + $ref: "#/components/responses/BadRequest" + "405": + $ref: "#/components/responses/MethodNotAllowed" + "502": + $ref: "#/components/responses/BadGateway" + /api/health-check/all: + get: + tags: + - health-check + summary: 查询某人全部体检 + description: | + 查询某人的全部健康体检历史,原样返回上游 JKTJ00002 `/auto/jktj/query` 响应。 + + `idCard`、`phrid`、`empiId` 三个查询参数必须且只能提供一个。 + `data` 为数组;历史记录的 `checkId` 可能为空,调用方应按 `checkDate` 等字段辅助区分。 + operationId: findAllHealthChecks + parameters: + - $ref: "#/components/parameters/idCard" + - $ref: "#/components/parameters/phrid" + - $ref: "#/components/parameters/empiId" + responses: + "200": + description: 平台完整响应,成功和平台业务错误均原样透传。 + content: + application/json: + schema: + $ref: "#/components/schemas/HealthCheckAllResponse" + examples: + success: + value: + code: "01" + message: 操作成功 + data: + - checkId: CHECK_ID_PLACEHOLDER + checkDate: "2025-01-01" + idCard: "440000********0000" + "400": + $ref: "#/components/responses/BadRequest" + "405": + $ref: "#/components/responses/MethodNotAllowed" + "502": + $ref: "#/components/responses/BadGateway" + /api/health-check/list: + get: + tags: + - health-check + summary: 查询年度已检/未检人员名单 + description: | + 查询年度体检人员名单,原样返回上游 JKTJLIST00002 `/auto/jktjlist/query` 响应。 + + `data` 为人员名单和 `checkType` 状态,不是体检明细。 + operationId: listHealthCheckPeople + parameters: + - $ref: "#/components/parameters/checkYear" + - $ref: "#/components/parameters/idCard" + - $ref: "#/components/parameters/checkType" + - $ref: "#/components/parameters/page" + - $ref: "#/components/parameters/rows" + responses: + "200": + description: 平台完整响应,成功和平台业务错误均原样透传。 + content: + application/json: + schema: + $ref: "#/components/schemas/HealthCheckListResponse" + examples: + success: + value: + code: "01" + message: 操作成功 + data: + - idCard: "440000********0000" + checkYear: "2025" + checkType: "0" + "400": + $ref: "#/components/responses/BadRequest" + "405": + $ref: "#/components/responses/MethodNotAllowed" + "502": + $ref: "#/components/responses/BadGateway" +components: + parameters: + idCard: + name: idCard + in: query + required: false + description: 身份证号。该字段属于个人敏感信息,日志和截图中应脱敏。 + schema: + type: string + phrid: + name: phrid + in: query + required: false + description: 健康档案编号。 + schema: + type: string + personName: + name: personName + in: query + required: false + description: 居民姓名,仅健康档案查询支持。 + schema: + type: string + empiId: + name: empiId + in: query + required: false + description: EMPI 主索引 ID。 + schema: + type: string + checkYear: + name: checkYear + in: query + required: true + description: 体检年度,如 2025。 + schema: + type: string + pattern: "^[0-9]{4}$" + example: "2025" + checkType: + name: checkType + in: query + required: false + description: 年度名单过滤条件:0 已检,1 未检,2 全部。 + schema: + type: string + enum: + - "0" + - "1" + - "2" + page: + name: page + in: query + required: false + description: 页码;未传时后端默认 1。 + schema: + type: string + default: "1" + rows: + name: rows + in: query + required: false + description: 每页条数;未传时后端默认 10。 + schema: + type: string + default: "10" + responses: + BadRequest: + description: 请求参数错误。 + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + examples: + missingQueryKey: + value: + error: "provide one of: idCard, phrid, empiId" + MethodNotAllowed: + description: HTTP 方法不支持。 + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + examples: + methodNotAllowed: + value: + error: only GET is supported + BadGateway: + description: 未拿到上游平台响应,通常为网络、代理、签名或平台不可用错误。 + content: + application/json: + schema: + $ref: "#/components/schemas/ErrorResponse" + examples: + upstreamError: + value: + error: empty response from OSI + schemas: + ErrorResponse: + type: object + required: + - error + properties: + error: + type: string + OSIBaseResponse: + type: object + required: + - code + - message + properties: + code: + type: string + description: 平台业务码。成功实测为字符串 "01";平台业务错误也会原样透传。 + example: "01" + message: + type: string + example: 操作成功 + additionalProperties: true + HealthRecordFindResponse: + allOf: + - $ref: "#/components/schemas/OSIBaseResponse" + - type: object + properties: + data: + type: array + description: 平台完整健康档案聚合对象数组。 + items: + type: object + additionalProperties: true + CrowdResponse: + allOf: + - $ref: "#/components/schemas/OSIBaseResponse" + - type: object + properties: + data: + $ref: "#/components/schemas/CrowdData" + CrowdData: + type: object + properties: + personSign: + type: string + description: 人群分类码,可能为逗号分隔多值。 + example: "01,04" + idCard: + type: string + description: 身份证号。 + phrId: + type: string + description: 健康档案编号。 + additionalProperties: true + HealthCheckLastResponse: + allOf: + - $ref: "#/components/schemas/OSIBaseResponse" + - type: object + properties: + data: + $ref: "#/components/schemas/HealthCheckRecord" + HealthCheckAllResponse: + allOf: + - $ref: "#/components/schemas/OSIBaseResponse" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/HealthCheckRecord" + HealthCheckListResponse: + allOf: + - $ref: "#/components/schemas/OSIBaseResponse" + - type: object + properties: + data: + type: array + items: + $ref: "#/components/schemas/HealthCheckPerson" + HealthCheckRecord: + type: object + description: 平台完整体检对象;字段很多,未在本项目裁剪。 + properties: + checkId: + type: string + nullable: true + description: 体检幂等/定位 ID,历史记录可能为空。 + checkDate: + type: string + description: 体检日期,平台返回格式以原始响应为准。 + idCard: + type: string + description: 身份证号。 + additionalProperties: true + HealthCheckPerson: + type: object + description: 年度已检/未检人员名单项,非体检明细。 + properties: + idCard: + type: string + description: 身份证号。 + checkYear: + type: string + description: 体检年度。 + checkType: + type: string + description: 0 已检,1 未检,2 全部或平台返回状态。 + additionalProperties: true diff --git a/progress.md b/progress.md index 50a72df..cdfff6e 100644 --- a/progress.md +++ b/progress.md @@ -266,3 +266,10 @@ - 验证:`go build ./...` 通过。 - 验证:`git diff --check` 通过。 - 决策:HTTP 端点继续采用查询类统一策略,直接回写 `Result.Raw`,避免裁掉平台后续新增字段;真实样本 JSON 仍只本地留存,不提交。 +## 2026-07-09 登记 T-210 公开查询 HTTP API + +- 状态:DONE(看板+文档维护) +- 变更:`tasks.md` 新增 T-210,范围为把人群分类、网格地址、责任医生、药品目录、机构查询统一暴露为 server 模式 HTTP API;同步登记 `docs/openapi.yaml` 作为本项目 HTTP API 的机器可读文档。 +- 决策:T-210 依赖 T-101(四类公开查询 OSI 客户端已完成)与 T-209(人群分类 HTTP 端点已存在)。实现时不再重复写 OSI 客户端,只补 handler/router、HTTP 测试、`docs/07` 与 OpenAPI。项目后续以内网部署为目标,但接口仍返回 PII/主数据,需在文档中明确绑定地址和鉴权边界。 +- 验证:文档登记阶段运行 `git diff --check`;代码实现阶段再跑 `go test ./...`、`go build ./...`。 +- 下一步:开始 T-210,实现公开查询 HTTP API。 diff --git a/tasks.md b/tasks.md index 6b62155..fcb67de 100644 --- a/tasks.md +++ b/tasks.md @@ -65,6 +65,7 @@ | --- | --- | --- | --- | --- | | T-101 | `osi/public.go` 四个字典查询(网格/责任医生/药品/机构) | T-005 | 真实机构码能查到下级网格、责任医生、机构树 | DONE | | T-103 | 字典缓存(内存 + redis 可选) | T-101, T-102 | 映射层能反查 `regionCode/manaDoctorId/manaUnitId`;redis 不可用不阻断 | DONE | +| T-210 | 公开查询 HTTP API(人群分类、网格地址、责任医生、药品目录、机构) | T-101, T-209 | server 模式暴露人群分类与四类公开查询 HTTP API;原样回写平台 JSON;同步 `docs/07` 与 `docs/openapi.yaml`;按内网部署场景明确绑定地址/鉴权边界 | TODO | | T-206 | `osi/jkda.go`:**Create/Update** + `contract/jkda.go` 补创建请求结构体 | T-201, T-202, T-103 | 一条档案经映射(真实字典快照)→ create → 平台返回成功码与 `phrId`;Update 沿用 checkId | BLOCKED(代码完成;待写入授权/测试档案,排在 Phase Q2 之后) | | T-204 | `handler`+`router`:`/api/health-record/save` | T-206 | server 模式起服务,curl 全链路返回投递结果 | TODO(待 T-206 解阻塞) |