# 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 客户端**和一个**厚字段映射层**。