From 3405e0c6bb755e135ea6ecff24e13d429f44f218 Mon Sep 17 00:00:00 2001 From: QiuSW Date: Sat, 4 Jul 2026 23:30:53 +0800 Subject: [PATCH] docs: define chis proxy config --- .env.example | 1 + README.md | 5 +++-- app/config.py | 1 + docs/02-requirements.md | 3 +++ docs/03-tech-stack.md | 5 ++++- docs/04-architecture.md | 5 ++++- docs/05-coding-rules.md | 2 ++ docs/06-tasks.md | 4 ++-- docs/api.md | 1 + docs/current-state.md | 5 +++-- progress.md | 9 +++++++++ tasks.md | 2 +- tests/test_config_and_structure.py | 2 ++ 13 files changed, 36 insertions(+), 9 deletions(-) diff --git a/.env.example b/.env.example index 3fe3134..c831705 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,6 @@ CHIS_BASE_URL= CHIS_PUBLIC_KEY= +CHIS_PROXY= REDIS_URL=redis://localhost:6379/0 LOG_DIR=logs ARCHIVE_DIR=archives diff --git a/README.md b/README.md index 13c8dd4..de59751 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ 基卫 CHIS 上报数据项目。项目目标是提供稳定的后端 API,供第三方系统提交体检数据;`chisup` 负责校验、转换、维护 CHIS 登录会话,并把符合 CHIS 接口格式的数据提交到基卫 CHIS。 -当前仓库处于 MVP 起步阶段:已有 `reverse_file/` 中的 CHIS 逆向资料,还未初始化 Flask 生产代码。当前已确认可参考 `D:\hans\chupd\chis\login_client_v2.py` 迁移 CHIS 登录链路,public key 由配置提供;保存上报前先实现只读体检详情查询来验证登录、会话、加密和通用请求。 +当前仓库处于 MVP 起步阶段:已有 Flask application factory 地基、health check、基础配置和架构包目录。当前已确认可参考 `D:\hans\chupd\chis\login_client_v2.py` 迁移 CHIS 登录链路,public key 由配置提供;访问 CHIS 支持可选 `CHIS_PROXY` SOCKS5 代理;保存上报前先实现只读体检详情查询来验证登录、会话、加密和通用请求。 ## 文档入口 @@ -31,9 +31,10 @@ - Flask 结构:使用 application factory 模式,T-001 实现 `create_app(config_object=None)`。 - 依赖管理:T-001 创建 `requirements.txt`,使用 `python -m pip install -r requirements.txt` 安装到当前系统 Python 3.8 环境。 - 计划使用 Redis 保存 CHIS 会话缓存。 +- 访问 CHIS 支持可选 `CHIS_PROXY`:为空直连,有值时 CHIS 登录、查询和保存请求走 SOCKS5 代理。 - 当前已有资料:`reverse_file/` 下的 CHIS 前端脚本、schema 和 HAR。 - 当前已有查询 HAR:`reverse_file/20260704_query_health_check.har`,包含 `getHMNIListOfHTML` 和 `getCheckInfoDetail`。 -- 当前尚未确认:最终项目目录、依赖文件、数据库、部署方式、第三方鉴权方式、仅凭 `healthCheck` 反查查询详情所需参数的链路。 +- 当前尚未确认:数据库、部署方式、第三方鉴权方式、仅凭 `healthCheck` 反查查询详情所需参数的链路。 ## 推荐开工方式 diff --git a/app/config.py b/app/config.py index ad7c91c..ef37da1 100644 --- a/app/config.py +++ b/app/config.py @@ -10,6 +10,7 @@ def load_config_from_env(environ=None): return { "CHIS_BASE_URL": source.get("CHIS_BASE_URL", ""), "CHIS_PUBLIC_KEY": source.get("CHIS_PUBLIC_KEY", ""), + "CHIS_PROXY": source.get("CHIS_PROXY", ""), "REDIS_URL": source.get("REDIS_URL", "redis://localhost:6379/0"), "LOG_DIR": source.get("LOG_DIR", "logs"), "ARCHIVE_DIR": source.get("ARCHIVE_DIR", "archives"), diff --git a/docs/02-requirements.md b/docs/02-requirements.md index 1da7e7d..822cf40 100644 --- a/docs/02-requirements.md +++ b/docs/02-requirements.md @@ -31,6 +31,7 @@ | 体检详情只读查询 | 根据体检主键和必要关联参数查询 CHIS 体检详情 | P0 | | 体检数据转换 | 将第三方输入转换为 CHIS `hcData` 等保存结构 | P0 | | 通用 CHIS request | 统一处理超时、错误码、登录失效、日志摘要和请求归档 | P0 | +| CHIS 代理配置 | 访问 CHIS 时可按配置走 SOCKS5 代理,未配置时直连 | P0 | | 日志与请求归档 | `logs/` 每天滚动保留 1 年;`archives/` 每请求一个完整原始归档文件 | P0 | | 幂等控制 | 第三方重试不会重复创建体检记录 | P0 | @@ -63,6 +64,7 @@ - 体检详情查询:给定 `healthCheck + phrId + idCard + checkDate` 时,能查询 `getHMNIListOfHTML` 和 `getCheckInfoDetail`,且不产生写入副作用。 - 数据转换:最小体检样例能生成 CHIS 保存请求所需的 `hcData` 和相关数据块。 - CHIS 提交:能通过通用 request 提交到 CHIS 的目标接口,并返回 CHIS 业务结果。 +- CHIS 代理:`CHIS_PROXY` 为空时所有 CHIS 请求直连;有值时 CHIS 登录、会话验证、查询和保存请求均通过 SOCKS5 代理访问 CHIS。 - 幂等:相同幂等键重复提交不会重复创建记录。 - 日志:`logs/` 中每天一个综合日志文件,包含不同级别摘要日志,保留 1 年;日志包含 trace_id、接口、耗时、结果,不包含明文密码、Cookie、完整身份证号。 - 请求归档:`archives/` 中每个 API 请求生成一个 archive JSON 文件,文件名包含日期、trace_id、request_id 等唯一字段;内容包含 `api` 和 `chis` 请求 / 响应数组;archive 不脱敏,仅用于内网前置机本地受控排查。 @@ -81,6 +83,7 @@ - CHIS 登录细节:已有 `D:\hans\chupd\chis\login_client_v2.py` 可参考;该代码依赖 Django,迁移到 Flask 时只复用登录链路和加密算法。 - 会话有效性接口:已有 `getLanderInfo` 代码可参考,仍需在 Flask 项目中实现并验证。 - CHIS public key:由配置文件或环境变量提供,不从代码硬编码。 +- CHIS SOCKS5 代理:已确认需要可选配置;真实代理地址不写入仓库,后续通用 CHIS client 实现时验证代理可用性。 - 体检详情查询:已有 `reverse_file/20260704_query_health_check.har`;第一版查询需要调用方传 `healthCheck + phrId + idCard + checkDate`。 - 仅凭体检 id 查询:缺少从 `healthCheck` 反查 `phrId/idCard/checkDate` 的已验证链路,需后续验证。 - 第三方请求体:字段、字典、幂等键、账号引用方式待定。 diff --git a/docs/03-tech-stack.md b/docs/03-tech-stack.md index 4e85660..4e2be93 100644 --- a/docs/03-tech-stack.md +++ b/docs/03-tech-stack.md @@ -9,7 +9,7 @@ | 运行时 | 当前系统 Python 3.8 | 已定 | 用户指定,不创建虚拟环境 | | Web 框架 | Flask 3.0.3 + application factory | 已定 | 用户指定 Flask 版本;factory 模式便于配置注入和测试 | | 数据校验 | 待定,建议 Pydantic v1 或 Marshmallow | 待定 | Python 3.8 下需注意版本兼容 | -| HTTP 客户端 | 待定,建议 `requests.Session` | 待定 | CHIS 会话 cookie 管理简单稳定 | +| HTTP 客户端 | 待定,建议 `requests.Session` + SOCKS 支持 | 待定 | CHIS 会话 cookie 管理简单稳定;`CHIS_PROXY` 有值时走 SOCKS5 代理 | | Redis 客户端 | 待定,建议 `redis-py` | 待定 | 保存 CHIS 会话、幂等和短期状态 | | SM2 加密 | 待定,建议 `gmssl` | 待定 | 需兼容 `hans_chis.sm2.sm2_encrypt` 的 `CryptSM2(mode=0)` 行为 | | 日志 | Python logging + TimedRotatingFileHandler | 已定 | `logs/` 每天一个综合日志文件,保留 1 年 | @@ -28,6 +28,8 @@ - 运行日志保存到 `logs/`,每天一个综合日志文件,包含 INFO/WARNING/ERROR/EXCEPTION 等级摘要,保留 1 年。 - 接口请求归档保存到 `archives/`,一个 API 请求一个 JSON archive 文件,文件内容保存完整 API 与 CHIS 请求 / 响应,不脱敏;该目录仅用于内网前置机本地审计排查,不提交 git。 - CHIS public key 从配置文件或环境变量读取;当前不依赖 `/chis/logon/publicKey` 动态获取。 +- CHIS 外呼请求支持可选代理:`CHIS_PROXY` 为空时直连;有值时只作用于访问 CHIS 的 HTTP client,不影响第三方调用 `chisup` 的入站请求。 +- `CHIS_PROXY` 推荐格式为 `socks5h://127.0.0.1:1080`;后续实现 `requests.Session` 时需同步加入 `requests[socks]` 或 `PySocks` 依赖,保证 SOCKS5 可用。 - CHIS 登录代码来自 Django 项目参考实现,迁移到 Flask 时只复用登录链路、SM2 算法和请求形状,不复用 Django model/cache。 - 当前不引入异步队列,先跑通同步单条上报闭环;批量和重试队列放到 V2。 - 当前不引入管理后台,先保证 API、会话和转换稳定。 @@ -55,5 +57,6 @@ Get-ChildItem -Recurse -File # PowerShell - 新增依赖前先说明用途、替代方案和 Python 3.8 兼容性。 - 不创建 `.venv`、`venv` 或其他项目虚拟环境目录;如需隔离,必须先更新本文并取得确认。 - 和 CHIS 登录、SM2/RSA/AES 加密相关的依赖必须由真实登录代码驱动,不要凭 HAR 猜;当前 SM2 参考 `hans_chis.sm2.sm2_encrypt`。 +- CHIS HTTP 代理只从 `CHIS_PROXY` 读取;测试和文档示例不得写真实代理地址、账号或密码。 - 不允许在代码或 `.env.example` 中写真实 CHIS 地址、账号、密码、Cookie。 - `logs/` 和 `archives/` 必须加入 `.gitignore`;archive 文件包含敏感原始数据,不得进入代码仓库或测试快照。 diff --git a/docs/04-architecture.md b/docs/04-architecture.md index c775333..0d75e73 100644 --- a/docs/04-architecture.md +++ b/docs/04-architecture.md @@ -25,7 +25,7 @@ Mapper / transformer v CHIS client + auth manager | - | cookies / session cache + | cookies / session cache / optional CHIS_PROXY v Redis | @@ -88,6 +88,8 @@ API / service / CHIS client - 封装 `*.jsonRequest` 和其他 CHIS HTTP 请求。 - 自动携带会话 cookie 和必要 header。 +- 读取 `CHIS_PROXY`:配置为空时直连;配置有值时对 CHIS 登录、账号信息查询和 `*.jsonRequest` 统一使用代理。 +- 代理只作用于出站访问 CHIS,不代理第三方系统访问本项目的入站请求。 - 处理超时、重试、登录失效、错误码、日志脱敏。 - 先用 `getLanderInfo`、`getEncryType`、`getHMNIListOfHTML` 等只读接口验证通道,再接入保存接口。 @@ -284,6 +286,7 @@ archives/ | 难点 | 说明 | 应对 | | --- | --- | --- | | CHIS 登录链路 | 可能涉及公钥、加密、角色、应用、机构上下文 | 等用户提供现有代码后接入,先写 auth 边界 | +| CHIS 网络代理 | 部署在内网前置机时,访问 CHIS 可能必须走 SOCKS5 | 使用可选 `CHIS_PROXY` 配置;空值直连,有值时为 `http` / `https` 同时设置 requests proxies | | 会话有效性 | cookie 存在不代表 CHIS 会话仍有效 | 用账号信息查询接口确认,失败则清理缓存并重登 | | 只传体检 id 查询详情 | `getHMNIListOfHTML` 还需要 `phrId` 和 `idCard` | 先要求调用方传全参数,后续验证反查链路 | | 体检字段转换 | CHIS 前端保存逻辑复杂,数据块多 | 先做最小样例 mapper,逐步补字段测试 | diff --git a/docs/05-coding-rules.md b/docs/05-coding-rules.md index 2beafd3..af35085 100644 --- a/docs/05-coding-rules.md +++ b/docs/05-coding-rules.md @@ -41,6 +41,8 @@ - CHIS 登录失败、会话失效、权限不足、参数错误、网络超时要有不同错误码。 - CHIS 返回未登录时最多自动重登一次,避免无限递归。 - 任何失败响应都要带 trace_id。 +- 访问 CHIS 的外部请求必须统一经过 CHIS client;不得在 auth、service 或 mapper 中绕过 client 单独发请求,避免遗漏 timeout、cookie、proxy、日志和 archive 逻辑。 +- `CHIS_PROXY` 为空时必须直连;有值时 CHIS 登录、会话验证、只读查询和保存请求都必须走同一代理配置。 - archive 写入失败不能改变业务接口结果,但必须写入 `logs/` 运行日志。 - archive 文件必须先写临时文件再 rename,避免半截 JSON。 diff --git a/docs/06-tasks.md b/docs/06-tasks.md index 6e92cf4..a2ed466 100644 --- a/docs/06-tasks.md +++ b/docs/06-tasks.md @@ -26,7 +26,7 @@ | ID | 任务 | 依赖 | 验收要点 | 状态 | | --- | --- | --- | --- | --- | -| T-101 | 迁移 CHIS 登录链路 | T-002 | 参考 `D:\hans\chupd\chis\login_client_v2.py`;登录逻辑进入 `app/chis/auth.py` 或等价模块;public key 从配置读取;账号密码不落日志;失败返回明确错误 | TODO | +| T-101 | 迁移 CHIS 登录链路 | T-002 | 参考 `D:\hans\chupd\chis\login_client_v2.py`;登录逻辑进入 `app/chis/auth.py` 或等价模块;public key 从配置读取;`CHIS_PROXY` 有值时登录请求走代理,空值直连;账号密码不落日志;失败返回明确错误 | TODO | | T-102 | 接入账号信息查询验证会话 | T-101 | 能用已有 cookie 查询当前账号信息;失败可判断会话无效 | TODO | | T-103 | 实现 Redis 会话缓存 | T-102 | 会话对象包含 cookies、账号、角色/机构、过期时间;有效会话复用;无效会话清理 | TODO | | T-104 | 实现失效重登策略 | T-103 | CHIS 返回未登录时清缓存、重登一次、重试一次;不会无限重试 | TODO | @@ -37,7 +37,7 @@ | ID | 任务 | 依赖 | 验收要点 | 状态 | | --- | --- | --- | --- | --- | -| T-201 | 实现通用 CHIS jsonRequest client | T-104 | 自动携带会话;处理 timeout、CHIS 错误和未登录;能调用 `getLanderInfo` 与 `getEncryType` | TODO | +| T-201 | 实现通用 CHIS jsonRequest client | T-104 | 自动携带会话;`CHIS_PROXY` 有值时为 `http` / `https` 设置 SOCKS5 代理,空值直连;处理 timeout、CHIS 错误和未登录;能调用 `getLanderInfo` 与 `getEncryType` | TODO | | T-202 | 实现体检详情查询 client | T-201 | 参考 `reverse_file/20260704_query_health_check.har`;支持 `healthCheck + phrId + idCard` 调 `getHMNIListOfHTML`;支持 `idCard + checkDate` 调 `getCheckInfoDetail` | TODO | | T-203 | 跑通只读查询 spike | T-202 | 登录后能查询体检详情;验证结果写入 `progress.md`;不产生 CHIS 写入副作用 | TODO | | T-204 | 验证仅凭体检 id 查询的前置参数来源 | T-203 | 明确是否能用 `healthCheck` 反查 `phrId/idCard/checkDate/empiId/createUser`;如不能,文档中确认第一版查询 API 需要调用方传全参数 | TODO | diff --git a/docs/api.md b/docs/api.md index 4faf4eb..8a8f0e1 100644 --- a/docs/api.md +++ b/docs/api.md @@ -219,5 +219,6 @@ ChisSession( - 发送 CHIS `*.jsonRequest`。 - 携带 cookie 和 header。 +- 根据配置处理出站代理:`CHIS_PROXY` 为空直连;有值时为 CHIS HTTP 请求设置同一个 SOCKS5 代理。 - 处理 timeout、CHIS 错误、未登录。 - 返回标准化 CHIS 响应对象。 diff --git a/docs/current-state.md b/docs/current-state.md index f18e506..4d2d574 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -12,7 +12,7 @@ - 数据 / 资料:`reverse_file/` 下已有 CHIS HAR、前端脚本和 schema;`D:\hans\chupd\chis\login_client_v2.py` 可作为登录链路参考 - 标准启动路径:`python run.py` - 标准验证路径:`python -m unittest discover -s tests` -- 当前 blocker:无硬阻塞;后续实现需要把 Django 登录代码迁移为 Flask 版本,并通过配置提供 CHIS public key +- 当前 blocker:无硬阻塞;后续实现需要把 Django 登录代码迁移为 Flask 版本,并通过配置提供 CHIS public key;访问 CHIS 时需要支持可选 SOCKS5 代理 ## 当前目录要点 @@ -46,7 +46,8 @@ - CHIS public key 可通过配置文件或环境变量提供。 - 使用当前系统 Python 3.8 环境,不创建虚拟环境;T-001 创建 `requirements.txt` 固定依赖。 - Flask 应用使用 application factory 模式,`app/__init__.py` 暴露 `create_app(config_object=None)`。 -- 基础配置通过环境变量读取:`CHIS_BASE_URL`、`CHIS_PUBLIC_KEY`、`REDIS_URL`、`LOG_DIR`、`ARCHIVE_DIR`。 +- 基础配置通过环境变量读取:`CHIS_BASE_URL`、`CHIS_PUBLIC_KEY`、`CHIS_PROXY`、`REDIS_URL`、`LOG_DIR`、`ARCHIVE_DIR`。 +- CHIS 外呼支持可选 SOCKS5 代理配置:`CHIS_PROXY` 为空直连;有值时后续 CHIS client 对登录、会话验证、查询和保存请求统一走代理。 - `.env.example` 只保留占位符和非敏感默认值,不写真实密钥。 - 登录链路参考 `login_client_v2.py`:`myRoles` -> 选择 `责任医生助理` / `责任医生` -> `myApps` -> 拼接 cookie。 - SM2 加密参考 `hans_chis.sm2.sm2_encrypt`:`gmssl.sm2.CryptSM2(mode=0)`,返回带 `04` 前缀密文。 diff --git a/progress.md b/progress.md index 5229d39..a57d392 100644 --- a/progress.md +++ b/progress.md @@ -88,3 +88,12 @@ - 阻塞:无。 - 决策:T-002 只建立基础配置和包目录,不引入真实密钥、不连接 Redis、不实现 CHIS 业务逻辑。 - 下一步:T-003 建立最小测试框架。 + +## 2026-07-04 DOC-006 明确 CHIS SOCKS5 代理配置 + +- 状态:DONE +- 变更:新增 `CHIS_PROXY` 配置读取和 `.env.example` 占位;更新 `README.md`、`tasks.md`、`docs/02-requirements.md`、`docs/03-tech-stack.md`、`docs/04-architecture.md`、`docs/05-coding-rules.md`、`docs/06-tasks.md`、`docs/api.md`、`docs/current-state.md`。 +- 验证:先执行 `python -m unittest discover -s tests -p test_config_and_structure.py` 看到 `KeyError: 'CHIS_PROXY'`;实现后执行 `python -m unittest discover -s tests` 结果 `Ran 3 tests ... OK`;执行 `rg "CHIS_PROXY|SOCKS|proxy|代理" README.md .env.example docs app tests` 确认文档和配置覆盖代理规则。 +- 阻塞:无。 +- 决策:CHIS 出站请求支持可选 SOCKS5 代理;`CHIS_PROXY` 为空时直连,有值时 CHIS 登录、会话验证、查询和保存请求统一走代理;代理只作用于访问 CHIS 的出站请求,不影响第三方访问 `chisup` 的入站请求。 +- 下一步:T-003 建立最小测试框架;后续 T-101 / T-201 实现登录和通用 CHIS client 时落地代理调用。 diff --git a/tasks.md b/tasks.md index 94bd8ce..2f966db 100644 --- a/tasks.md +++ b/tasks.md @@ -4,7 +4,7 @@ ## 当前阶段 -MVP 起步。当前仓库已具备逆向资料和项目文档,尚未初始化 Flask 代码。 +MVP 起步。当前仓库已具备逆向资料、项目文档、Flask application factory 地基、health check 和基础配置。 ## 近期任务 diff --git a/tests/test_config_and_structure.py b/tests/test_config_and_structure.py index 6cc8565..d460c0c 100644 --- a/tests/test_config_and_structure.py +++ b/tests/test_config_and_structure.py @@ -13,6 +13,7 @@ class ConfigAndStructureTest(unittest.TestCase): "REDIS_URL": "redis://localhost:6379/0", "LOG_DIR": "logs", "ARCHIVE_DIR": "archives", + "CHIS_PROXY": "socks5h://127.0.0.1:1080", } with patch.dict("os.environ", env, clear=False): @@ -23,6 +24,7 @@ class ConfigAndStructureTest(unittest.TestCase): self.assertEqual(app.config["REDIS_URL"], env["REDIS_URL"]) self.assertEqual(app.config["LOG_DIR"], env["LOG_DIR"]) self.assertEqual(app.config["ARCHIVE_DIR"], env["ARCHIVE_DIR"]) + self.assertEqual(app.config["CHIS_PROXY"], env["CHIS_PROXY"]) def test_architecture_packages_are_importable(self): package_names = [