From 79c57a63c5eac50b0c2494c68434456a55b4295a Mon Sep 17 00:00:00 2001 From: QiuSW Date: Sat, 4 Jul 2026 23:24:51 +0800 Subject: [PATCH] chore: add base config and package structure --- .env.example | 5 ++++ app/__init__.py | 4 +++ app/chis/__init__.py | 1 + app/config.py | 14 ++++++++++ app/mappers/__init__.py | 1 + app/middleware/__init__.py | 1 + app/repositories/__init__.py | 1 + app/services/__init__.py | 1 + app/validators/__init__.py | 1 + docs/06-tasks.md | 2 +- docs/current-state.md | 17 +++++++++--- progress.md | 9 +++++++ tasks.md | 2 +- tests/test_config_and_structure.py | 43 ++++++++++++++++++++++++++++++ 14 files changed, 96 insertions(+), 6 deletions(-) create mode 100644 .env.example create mode 100644 app/chis/__init__.py create mode 100644 app/mappers/__init__.py create mode 100644 app/middleware/__init__.py create mode 100644 app/repositories/__init__.py create mode 100644 app/services/__init__.py create mode 100644 app/validators/__init__.py create mode 100644 tests/test_config_and_structure.py diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3fe3134 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +CHIS_BASE_URL= +CHIS_PUBLIC_KEY= +REDIS_URL=redis://localhost:6379/0 +LOG_DIR=logs +ARCHIVE_DIR=archives diff --git a/app/__init__.py b/app/__init__.py index 0f84189..b338f7c 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -1,8 +1,12 @@ from flask import Flask +from app.config import BaseConfig, load_config_from_env + def create_app(config_object=None): app = Flask(__name__) + app.config.from_object(BaseConfig) + app.config.update(load_config_from_env()) if config_object: if isinstance(config_object, dict): diff --git a/app/chis/__init__.py b/app/chis/__init__.py new file mode 100644 index 0000000..8dec621 --- /dev/null +++ b/app/chis/__init__.py @@ -0,0 +1 @@ +"""CHIS integration layer.""" diff --git a/app/config.py b/app/config.py index e40e554..ad7c91c 100644 --- a/app/config.py +++ b/app/config.py @@ -1,2 +1,16 @@ +import os + + class BaseConfig: JSON_AS_ASCII = False + + +def load_config_from_env(environ=None): + source = environ or os.environ + return { + "CHIS_BASE_URL": source.get("CHIS_BASE_URL", ""), + "CHIS_PUBLIC_KEY": source.get("CHIS_PUBLIC_KEY", ""), + "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/app/mappers/__init__.py b/app/mappers/__init__.py new file mode 100644 index 0000000..082dcbe --- /dev/null +++ b/app/mappers/__init__.py @@ -0,0 +1 @@ +"""Data mapping layer.""" diff --git a/app/middleware/__init__.py b/app/middleware/__init__.py new file mode 100644 index 0000000..f25c9a4 --- /dev/null +++ b/app/middleware/__init__.py @@ -0,0 +1 @@ +"""Request middleware helpers.""" diff --git a/app/repositories/__init__.py b/app/repositories/__init__.py new file mode 100644 index 0000000..da750fd --- /dev/null +++ b/app/repositories/__init__.py @@ -0,0 +1 @@ +"""Persistence and cache repositories.""" diff --git a/app/services/__init__.py b/app/services/__init__.py new file mode 100644 index 0000000..9415f6b --- /dev/null +++ b/app/services/__init__.py @@ -0,0 +1 @@ +"""Application service layer.""" diff --git a/app/validators/__init__.py b/app/validators/__init__.py new file mode 100644 index 0000000..9cdd03c --- /dev/null +++ b/app/validators/__init__.py @@ -0,0 +1 @@ +"""Request validators.""" diff --git a/docs/06-tasks.md b/docs/06-tasks.md index fe12c5d..6e92cf4 100644 --- a/docs/06-tasks.md +++ b/docs/06-tasks.md @@ -19,7 +19,7 @@ | ID | 任务 | 依赖 | 验收要点 | 状态 | | --- | --- | --- | --- | --- | | T-001 | 初始化 Flask 项目骨架 | - | 使用当前系统 Python 3.8,不创建虚拟环境;创建 `requirements.txt` 并固定 `Flask==3.0.3`;使用 application factory 模式实现 `create_app(config_object=None)`;本地 health check API 可启动;真实命令同步到技术栈和当前状态 | DONE | -| T-002 | 建立基础配置与目录 | T-001 | 目录符合架构文档;配置从环境变量读取;无真实密钥 | TODO | +| T-002 | 建立基础配置与目录 | T-001 | 目录符合架构文档;配置从环境变量读取;无真实密钥 | DONE | | T-003 | 建立最小测试框架 | T-001 | pytest 可运行;至少有 `create_app` / health check 测试,能通过测试配置创建 Flask app | TODO | ## Phase 1 · CHIS 登录与会话 diff --git a/docs/current-state.md b/docs/current-state.md index 215bdb2..f18e506 100644 --- a/docs/current-state.md +++ b/docs/current-state.md @@ -7,8 +7,8 @@ - 日期:2026-07-04 - 阶段:MVP 起步 / Flask 地基已初始化 - 技术栈:当前系统 Python 3.8、Flask 3.0.3 application factory;不创建虚拟环境 -- 生产代码:`app/__init__.py`、`app/api/health.py`、`app/config.py`、`run.py` -- 测试:`tests/test_app_factory.py` +- 生产代码:`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` - 数据 / 资料:`reverse_file/` 下已有 CHIS HAR、前端脚本和 schema;`D:\hans\chupd\chis\login_client_v2.py` 可作为登录链路参考 - 标准启动路径:`python run.py` - 标准验证路径:`python -m unittest discover -s tests` @@ -23,22 +23,31 @@ | `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/mappers/` | 已有 | 数据转换层包目录 | +| `app/middleware/` | 已有 | 中间件包目录 | +| `app/repositories/` | 已有 | 存储 / 缓存包目录 | +| `app/services/` | 已有 | 应用服务层包目录 | +| `app/validators/` | 已有 | 请求校验包目录 | | `tests/` | 已有 | `unittest` 测试 | | `requirements.txt` | 已有 | Python 依赖,固定 `Flask==3.0.3` | +| `.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 明确日志与请求归档方案;T-001 初始化 Flask 项目骨架。详见 [../progress.md](../progress.md)。 +- 已完成:DOC-001 建立 harness coding 项目文档;DOC-002 更新 CHIS 登录与只读查询验证路线;DOC-003 明确使用系统 Python 3.8;DOC-004 明确 Flask factory 模式;DOC-005 明确日志与请求归档方案;T-001 初始化 Flask 项目骨架;T-002 建立基础配置与目录。详见 [../progress.md](../progress.md)。 - 正在进行:无。 -- 下一个可领取任务:T-002 建立基础配置与目录。 +- 下一个可领取任务:T-003 建立最小测试框架。 ## 已确认技术事实 - 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`。 +- `.env.example` 只保留占位符和非敏感默认值,不写真实密钥。 - 登录链路参考 `login_client_v2.py`:`myRoles` -> 选择 `责任医生助理` / `责任医生` -> `myApps` -> 拼接 cookie。 - SM2 加密参考 `hans_chis.sm2.sm2_encrypt`:`gmssl.sm2.CryptSM2(mode=0)`,返回带 `04` 前缀密文。 - 会话验证可用 `chis.myPageService / getLanderInfo`。 diff --git a/progress.md b/progress.md index 99e726b..5229d39 100644 --- a/progress.md +++ b/progress.md @@ -79,3 +79,12 @@ - 阻塞:无。 - 决策:T-001 采用标准库 `unittest` 做最小 factory / health check 验证;后续 T-003 再建立 pytest 测试框架。 - 下一步:T-002 建立基础配置与目录。 + +## 2026-07-04 T-002 建立基础配置与目录 + +- 状态:DONE +- 变更:新增 `app/middleware/`、`app/services/`、`app/mappers/`、`app/chis/`、`app/repositories/`、`app/validators/` 包目录;新增 `.env.example`;更新 `app/config.py` 从环境变量读取基础配置;更新 `create_app` 加载基础配置;新增 `tests/test_config_and_structure.py`;更新任务和当前状态文档。 +- 验证:先执行 `python -m unittest discover -s tests` 看到 `CHIS_BASE_URL` 缺失和架构包不可导入;实现后执行 `python -m unittest discover -s tests` 结果 `Ran 3 tests ... OK`;临时启动 `python run.py` 后请求 `/health` 返回 `{"ok":true}`;执行 `git diff --check` 未发现空白错误;检查 `.env.example` 仅包含占位符和非敏感默认值。 +- 阻塞:无。 +- 决策:T-002 只建立基础配置和包目录,不引入真实密钥、不连接 Redis、不实现 CHIS 业务逻辑。 +- 下一步:T-003 建立最小测试框架。 diff --git a/tasks.md b/tasks.md index fa0b65d..94bd8ce 100644 --- a/tasks.md +++ b/tasks.md @@ -11,7 +11,7 @@ MVP 起步。当前仓库已具备逆向资料和项目文档,尚未初始化 | ID | 任务 | 状态 | | --- | --- | --- | | T-001 | 初始化 Flask 项目骨架 | DONE | -| T-002 | 建立基础配置与目录 | TODO | +| T-002 | 建立基础配置与目录 | DONE | | T-003 | 建立最小测试框架 | TODO | | T-101 | 迁移 CHIS 登录链路 | TODO | | T-102 | 接入账号信息查询验证会话 | TODO | diff --git a/tests/test_config_and_structure.py b/tests/test_config_and_structure.py new file mode 100644 index 0000000..6cc8565 --- /dev/null +++ b/tests/test_config_and_structure.py @@ -0,0 +1,43 @@ +import importlib +import unittest +from unittest.mock import patch + +from app import create_app + + +class ConfigAndStructureTest(unittest.TestCase): + def test_create_app_loads_environment_config(self): + env = { + "CHIS_BASE_URL": "http://chis.example.test/chis", + "CHIS_PUBLIC_KEY": "PUBLIC_KEY_PLACEHOLDER", + "REDIS_URL": "redis://localhost:6379/0", + "LOG_DIR": "logs", + "ARCHIVE_DIR": "archives", + } + + with patch.dict("os.environ", env, clear=False): + app = create_app({"TESTING": True}) + + self.assertEqual(app.config["CHIS_BASE_URL"], env["CHIS_BASE_URL"]) + self.assertEqual(app.config["CHIS_PUBLIC_KEY"], env["CHIS_PUBLIC_KEY"]) + 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"]) + + def test_architecture_packages_are_importable(self): + package_names = [ + "app.middleware", + "app.services", + "app.mappers", + "app.chis", + "app.repositories", + "app.validators", + ] + + for package_name in package_names: + with self.subTest(package_name=package_name): + self.assertIsNotNone(importlib.import_module(package_name)) + + +if __name__ == "__main__": + unittest.main()