Implement CHIS login flow

This commit is contained in:
ila
2026-07-05 11:01:30 +08:00
parent aaf35f2d7a
commit 07e6c2b9a3
13 changed files with 450 additions and 17 deletions
+5 -4
View File
@@ -9,9 +9,9 @@
| 运行时 | 当前系统 Python 3.8 | 已定 | 用户指定,不创建虚拟环境 |
| Web 框架 | Flask 3.0.3 + application factory | 已定 | 用户指定 Flask 版本;factory 模式便于配置注入和测试 |
| 数据校验 | 待定,建议 Pydantic v1 或 Marshmallow | 待定 | Python 3.8 下需注意版本兼容 |
| HTTP 客户端 | 待定,建议 `requests.Session` + SOCKS 支持 | 待定 | CHIS 会话 cookie 管理简单稳定;`CHIS_PROXY` 有值时走 SOCKS5 代理 |
| HTTP 客户端 | `requests[socks]` 2.32.4 | 已定 | CHIS 会话 cookie 管理简单稳定;`CHIS_PROXY` 有值时走 SOCKS5 代理 |
| Redis 客户端 | 待定,建议 `redis-py` | 待定 | 保存 CHIS 会话、幂等和短期状态 |
| SM2 加密 | 待定,建议 `gmssl` | 待定 | 需兼容 `hans_chis.sm2.sm2_encrypt` 的 `CryptSM2(mode=0)` 行为 |
| SM2 加密 | `gmssl` 3.2.2 | 已定 | 兼容 `hans_chis.sm2.sm2_encrypt` 的 `CryptSM2(mode=0)` 行为 |
| 日志 | Python logging + TimedRotatingFileHandler | 已定 | `logs/` 每天一个综合日志文件,保留 1 年 |
| 请求归档 | 本地 JSON 文件 | 已定 | `archives/` 每个接口请求一个完整归档文件,部署在内网前置机,不脱敏 |
| 数据库 | 待定 | 待定 | MVP 可先只用 Redis;上报流水可能需要 MySQL / SQLite / PostgreSQL |
@@ -28,9 +28,10 @@
- 运行日志保存到 `logs/`,每天一个综合日志文件,包含 INFO/WARNING/ERROR/EXCEPTION 等级摘要,保留 1 年。
- 接口请求归档保存到 `archives/`,一个 API 请求一个 JSON archive 文件,文件内容保存完整 API 与 CHIS 请求 / 响应,不脱敏;该目录仅用于内网前置机本地审计排查,不提交 git。
- CHIS public key 从配置文件或环境变量读取;当前不依赖 `/chis/logon/publicKey` 动态获取。
- `CHIS_BASE_URL` 支持完整地址如 `http://host:port/chis`,也兼容旧项目的 host 写法如 `host:port`,代码会补齐为 `http://host:port/chis`。
- 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。
- `CHIS_PROXY` 推荐格式为 `socks5h://127.0.0.1:1080`;`requests[socks]==2.32.4` 已固定到依赖,保证 SOCKS5 可用。
- CHIS 登录代码来自 Django 项目参考实现,迁移到 Flask 时只复用登录链路、SM2 算法和请求形状,不复用 Django model/cache;当前登录实现位于 `app/chis/auth.py`,SM2 实现位于 `app/chis/crypto.py`。
- 当前不引入异步队列,先跑通同步单条上报闭环;批量和重试队列放到 V2。
- 当前不引入管理后台,先保证 API、会话和转换稳定。
+2 -1
View File
@@ -83,6 +83,7 @@ API / service / CHIS client
- 负责 Redis 会话缓存、清理和重登。
- 登录链路参考 `D:\hans\chupd\chis\login_client_v2.py`,但不能原样依赖 Django model/cache。
- CHIS public key 从 Flask 配置或环境变量读取,不从代码硬编码。
- 当前 T-101 已落地 `app/chis/auth.py:ChisLoginClient`:负责 `myRoles`、角色选择、`myApps`、JSESSIONID 提取和 cookie 组装;Redis 会话缓存与有效性验证留给 T-102 / T-103。
**CHIS client**
@@ -285,7 +286,7 @@ archives/
| 难点 | 说明 | 应对 |
| --- | --- | --- |
| CHIS 登录链路 | 可能涉及公钥、加密、角色、应用、机构上下文 | 等用户提供现有代码后接入,先写 auth 边界 |
| CHIS 登录链路 | 涉及公钥、SM2 加密、角色、应用、JSESSIONID 和 cookie 组装 | T-101 已迁移为 `ChisLoginClient`,后续用真实账号做联调 |
| CHIS 网络代理 | 部署在内网前置机时,访问 CHIS 可能必须走 SOCKS5 | 使用可选 `CHIS_PROXY` 配置;空值直连,有值时为 `http` / `https` 同时设置 requests proxies |
| 会话有效性 | cookie 存在不代表 CHIS 会话仍有效 | 用账号信息查询接口确认,失败则清理缓存并重登 |
| 只传体检 id 查询详情 | `getHMNIListOfHTML` 还需要 `phrId` 和 `idCard` | 先要求调用方传全参数,后续验证反查链路 |
+1 -1
View File
@@ -26,7 +26,7 @@
| ID | 任务 | 依赖 | 验收要点 | 状态 |
| --- | --- | --- | --- | --- |
| T-101 | 迁移 CHIS 登录链路 | T-002 | 参考 `D:\hans\chupd\chis\login_client_v2.py`;登录逻辑进入 `app/chis/auth.py` 或等价模块;public key 从配置读取;`CHIS_PROXY` 有值时登录请求走代理,空值直连;账号密码不落日志;失败返回明确错误 | TODO |
| T-101 | 迁移 CHIS 登录链路 | T-002 | 参考 `D:\hans\chupd\chis\login_client_v2.py`;登录逻辑进入 `app/chis/auth.py` 或等价模块;public key 从配置读取;`CHIS_PROXY` 有值时登录请求走代理,空值直连;账号密码不落日志;失败返回明确错误 | DONE |
| T-102 | 接入账号信息查询验证会话 | T-101 | 能用已有 cookie 查询当前账号信息;失败可判断会话无效 | TODO |
| T-103 | 实现 Redis 会话缓存 | T-102 | 会话对象包含 cookies、账号、角色/机构、过期时间;有效会话复用;无效会话清理 | TODO |
| T-104 | 实现失效重登策略 | T-103 | CHIS 返回未登录时清缓存、重登一次、重试一次;不会无限重试 | TODO |
+32
View File
@@ -146,6 +146,38 @@
## 内部模块合约
### `ChisLoginClient.from_config(config).login(username, password)`
输入:
```python
username: str
password: str
```
输出:
```python
ChisLoginSession(
cookie=str,
jsessionid=str,
role_id=str,
role_name=str,
user_name=str,
roles_result=dict,
apps_result=dict,
)
```
职责:
- 从 `CHIS_BASE_URL`、`CHIS_PUBLIC_KEY`、`CHIS_PROXY` 创建登录客户端。
- 调用 `logon/myRoles`,使用 SM2 加密密码和时间戳 `d`。
- 优先选择 `责任医生助理` 或 `责任医生` 角色。
- 调用 `logon/myApps` 获取应用上下文。
- 从 `Set-Cookie` 提取 `JSESSIONID`,组装 CHIS cookie。
- 登录失败、缺少允许角色、缺少 cookie 时抛出 `ChisLoginError`,错误对象包含稳定 `code`。
### `ChisSessionManager.ensure_session(account_ref)`
输入:
+11 -9
View File
@@ -6,9 +6,9 @@
- 日期:2026-07-04
- 阶段:MVP 起步 / Phase 0 地基已完成
- 技术栈:当前系统 Python 3.8、Flask 3.0.3 application factory、pytest 8.3.5;不创建虚拟环境
- 生产代码:`app/__init__.py`、`app/api/health.py`、`app/config.py`、`run.py`,以及 `app/middleware/`、`app/services/`、`app/mappers/`、`app/chis/`、`app/repositories/`、`app/validators/` 包目录
- 测试:`tests/test_app_factory.py`、`tests/test_config_and_structure.py`、`tests/test_project_setup.py`
- 技术栈:当前系统 Python 3.8、Flask 3.0.3 application factory、pytest 8.3.5、requests[socks] 2.32.4、gmssl 3.2.2;不创建虚拟环境
- 生产代码:`app/__init__.py`、`app/api/health.py`、`app/config.py`、`run.py`、`app/chis/auth.py`、`app/chis/crypto.py`,以及 `app/middleware/`、`app/services/`、`app/mappers/`、`app/chis/`、`app/repositories/`、`app/validators/` 包目录
- 测试:`tests/test_app_factory.py`、`tests/test_config_and_structure.py`、`tests/test_project_setup.py`、`tests/test_chis_auth.py`
- 数据 / 资料:`reverse_file/` 下已有 CHIS HAR、前端脚本和 schema;`D:\hans\chupd\chis\login_client_v2.py` 可作为登录链路参考
- 标准启动路径:`python run.py`
- 标准验证路径:`python -m pytest`
@@ -23,7 +23,7 @@
| `reverse_file/20260704_query_health_check.har` | 已有 | 体检详情只读查询 HAR,包含 `getHMNIListOfHTML` 和 `getCheckInfoDetail` |
| `D:\hans\chupd\chis\login_client_v2.py` | 外部参考 | CHIS 登录、SM2 加密、角色选择、cookie 拼接、`getLanderInfo` 会话验证 |
| `app/` | 已有 | Flask application factory 和 health check API |
| `app/chis/` | 已有 | CHIS integration layer 包目录 |
| `app/chis/` | 已有 | CHIS integration layer;T-101 已包含登录客户端与 SM2 加密 |
| `app/mappers/` | 已有 | 数据转换层包目录 |
| `app/middleware/` | 已有 | 中间件包目录 |
| `app/repositories/` | 已有 | 存储 / 缓存包目录 |
@@ -31,28 +31,30 @@
| `app/validators/` | 已有 | 请求校验包目录 |
| `tests/` | 已有 | pytest 可收集的测试 |
| `pytest.ini` | 已有 | pytest 测试入口配置,限制测试目录为 `tests` |
| `requirements.txt` | 已有 | Python 依赖,固定 `Flask==3.0.3` 和 `pytest==8.3.5` |
| `requirements.txt` | 已有 | Python 依赖,固定 `Flask==3.0.3`、`pytest==8.3.5`、`requests[socks]==2.32.4`、`gmssl==3.2.2` |
| `.env.example` | 已有 | 环境变量占位示例,不含真实密钥 |
## 任务看板状态
任务状态以 [06-tasks.md](06-tasks.md) 为准。
- 已完成:DOC-001 建立 harness coding 项目文档;DOC-002 更新 CHIS 登录与只读查询验证路线;DOC-003 明确使用系统 Python 3.8;DOC-004 明确 Flask factory 模式;DOC-005 明确日志与请求归档方案;DOC-006 明确 CHIS SOCKS5 代理配置;T-001 初始化 Flask 项目骨架;T-002 建立基础配置与目录;T-003 建立最小测试框架。详见 [../progress.md](../progress.md)。
- 已完成:DOC-001 建立 harness coding 项目文档;DOC-002 更新 CHIS 登录与只读查询验证路线;DOC-003 明确使用系统 Python 3.8;DOC-004 明确 Flask factory 模式;DOC-005 明确日志与请求归档方案;DOC-006 明确 CHIS SOCKS5 代理配置;T-001 初始化 Flask 项目骨架;T-002 建立基础配置与目录;T-003 建立最小测试框架;T-101 迁移 CHIS 登录链路。详见 [../progress.md](../progress.md)。
- 正在进行:无。
- 下一个可领取任务:T-101 迁移 CHIS 登录链路。
- 下一个可领取任务:T-102 接入账号信息查询验证会话。
## 已确认技术事实
- CHIS public key 可通过配置文件或环境变量提供。
- 使用当前系统 Python 3.8 环境,不创建虚拟环境;T-001 创建 `requirements.txt` 固定依赖。
- pytest 8.3.5 已固定到 `requirements.txt`;标准测试命令为 `python -m pytest`。
- requests[socks] 2.32.4 和 gmssl 3.2.2 已固定到 `requirements.txt`,用于 CHIS 登录请求、SOCKS5 代理和 SM2 加密。
- Flask 应用使用 application factory 模式,`app/__init__.py` 暴露 `create_app(config_object=None)`。
- 基础配置通过环境变量读取:`CHIS_BASE_URL`、`CHIS_PUBLIC_KEY`、`CHIS_PROXY`、`REDIS_URL`、`LOG_DIR`、`ARCHIVE_DIR`。
- `CHIS_BASE_URL` 兼容完整地址和旧项目 host 写法;未带 scheme 时按 `http://<host>/chis` 处理。
- 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` 前缀密文。
- 登录链路已迁移到 `app/chis/auth.py`:`myRoles` -> 选择 `责任医生助理` / `责任医生` -> `myApps` -> 提取 `JSESSIONID` -> 拼接 cookie。
- SM2 加密已迁移到 `app/chis/crypto.py`,兼容 `hans_chis.sm2.sm2_encrypt`:`gmssl.sm2.CryptSM2(mode=0)`,返回带 `04` 前缀密文。
- 会话验证可用 `chis.myPageService / getLanderInfo`。
- 体检详情查询第一版可用 `healthCheck + phrId + idCard` 调 `getHMNIListOfHTML`,用 `idCard + checkDate` 调 `getCheckInfoDetail`。
- 当前不能证明只传 `healthCheck` 就能查完整详情。