# 设计文档:Monorepo 迁移 ## 概述 本设计将现有单一 ETL 仓库(`FQ-ETL`)迁移为 Monorepo 单体仓库(`NeoZQYY`),采用一次性搬迁策略。核心设计原则: 1. **最小破坏性**:ETL 整体平移,保持内部结构不变,仅调整外部引用 2. **分层隔离**:通过 uv workspace 实现 Python 包依赖隔离,通过 `.env` 分层实现配置隔离 3. **数据库重组**:从现有 4 个 schema(billiards_ods/billiards_dwd/billiards_dws/etl_admin)重组为 6 层 schema(meta/ods/dwd/core/dws/app) 4. **渐进式扩展**:第一阶段只建必要骨架,未来扩展点记录在 Roadmap 中 ## 架构 ### 整体架构 ```mermaid graph TB subgraph "NeoZQYY Monorepo" subgraph "apps/" ETL["apps/etl/pipelines/feiqiu/"] Backend["apps/backend/ (FastAPI)"] Mini["apps/miniprogram/ (Donut+TDesign)"] Admin["apps/admin-web/ (未来)"] end subgraph "packages/" Shared["packages/shared/"] end subgraph "gui/" GUI["gui/ (PySide6,过渡期)"] end subgraph "db/" ETLDB["db/etl_feiqiu/"] AppDB["db/zqyy_app/"] FDW["db/fdw/"] end end ETL --> Shared Backend --> Shared GUI --> Shared Backend --> AppDB ETL --> ETLDB AppDB -.->|postgres_fdw 只读| ETLDB ``` ### 数据流架构 ```mermaid graph LR API["上游 SaaS API"] --> ODS["ods (原始数据)"] ODS --> DWD["dwd (main+EX 明细)"] DWD --> Core["core (统一最小字段集)"] DWD --> DWS["dws (汇总/工资)"] Core --> DWS DWS --> App["app (视图+RLS)"] App -.->|FDW 只读映射| ZqyyApp["zqyy_app DB"] ZqyyApp --> FastAPI["FastAPI 后端"] FastAPI --> MiniApp["微信小程序"] subgraph "etl_feiqiu DB" Meta["meta (调度/游标)"] ODS DWD Core DWS App end ``` ## 组件与接口 ### 1. 目录结构生成器(Scaffold) 负责创建 Monorepo 完整目录结构和基础配置文件。 **输入**:目标路径 `C:\NeoZQYY\` **输出**:完整目录树 + README.md + 配置文件 **关键行为**: - 创建所有一级和二级目录 - 为每个一级目录生成 README.md(作用 + 结构 + Roadmap) - 生成 `.gitignore`、`.kiroignore`、`.env.template` - 初始化 Git 仓库 ### 2. uv Workspace 配置 **根 `pyproject.toml`**: ```toml [project] name = "neozqyy" version = "0.1.0" requires-python = ">=3.10" [tool.uv.workspace] members = [ "apps/etl/pipelines/feiqiu", "apps/backend", "packages/shared", "gui", ] ``` **子项目 `pyproject.toml` 模式**(以 ETL 为例): ```toml [project] name = "etl-feiqiu" version = "0.1.0" requires-python = ">=3.10" dependencies = [ "psycopg2-binary>=2.9.0", "requests>=2.28.0", "python-dateutil>=2.8.0", "tzdata>=2023.0", "python-dotenv", "openpyxl>=3.1.0", "neozqyy-shared", ] [tool.uv.sources] neozqyy-shared = { workspace = true } ``` ### 3. 配置隔离机制 **分层加载顺序**: ``` 根 .env(公共配置)→ 应用 .env.local(私有覆盖)→ 环境变量 → CLI 参数 ``` **实现方式**: - 现有 `AppConfig` 的 `DEFAULTS < ENV < CLI` 模式保持不变 - 新增:在 `load_env_overrides()` 中先加载根 `.env`,再加载应用级 `.env.local` - 冲突策略:应用级优先(后加载覆盖先加载) - 缺失检测:在 `_validate()` 中检查必需项,报告缺失项名称 ### 4. ETL 平移策略 **平移范围**: | 源路径 | 目标路径 | 说明 | |--------|----------|------| | `api/` | `apps/etl/pipelines/feiqiu/api/` | API 客户端 | | `cli/` | `apps/etl/pipelines/feiqiu/cli/` | CLI 入口 | | `config/` | `apps/etl/pipelines/feiqiu/config/` | 配置 | | `loaders/` | `apps/etl/pipelines/feiqiu/loaders/` | 加载器 | | `models/` | `apps/etl/pipelines/feiqiu/models/` | 模型 | | `orchestration/` | `apps/etl/pipelines/feiqiu/orchestration/` | 调度 | | `scd/` | `apps/etl/pipelines/feiqiu/scd/` | SCD2 | | `tasks/` | `apps/etl/pipelines/feiqiu/tasks/` | 任务 | | `utils/` | `apps/etl/pipelines/feiqiu/utils/` | 工具 | | `quality/` | `apps/etl/pipelines/feiqiu/quality/` | 质量检查 | | `tests/` | `apps/etl/pipelines/feiqiu/tests/` | 测试 | | `database/*.sql` | `db/etl_feiqiu/schemas/` | DDL | | `database/migrations/` | `db/etl_feiqiu/migrations/` | 迁移脚本 | | `database/seed_*.sql` | `db/etl_feiqiu/seeds/` | 种子数据 | | `gui/` | `gui/` | GUI(顶层) | **import 路径策略**: - ETL 内部使用相对 import(`from .config.settings import AppConfig`)或保持现有绝对 import - `pyproject.toml` 中设置 `pythonpath`,使 `apps/etl/pipelines/feiqiu/` 为 Python 路径根 - `pytest.ini` 同步更新 `pythonpath = .` - 目标:ETL 内部代码零修改或最小修改 ### 5. 小程序平移策略 **平移范围**: | 源路径 | 目标路径 | |--------|----------| | `C:\ZQYY\XCX\`(除 Prototype) | `apps/miniprogram/` | | `C:\ZQYY\XCX\Prototype\` | `docs/h5_ui/` | 小程序为独立前端项目(Donut + TDesign),不涉及 Python 依赖管理,直接复制即可。 ### 6. 数据库 Schema 重组(etl_feiqiu) **现有 → 新 schema 映射**: | 现有 Schema | 新 Schema | 说明 | |-------------|-----------|------| | `etl_admin` | `meta` | 调度、游标、运行记录 | | `billiards_ods` | `ods` | ODS 原始数据,结构不变 | | `billiards_dwd` | `dwd` | DWD 明细,保留 main+EX 拆分 | | (新增) | `core` | 统一维度/事实最小字段集 | | `billiards_dws` | `dws` | DWS 汇总,结构不变 | | (新增) | `app` | 面向外部的视图/函数 + RLS | **core schema 设计原则**: - 仅包含跨系统共享的最小字段集(如会员 ID、姓名、手机号、状态) - 维度表从 DWD 维度表提取核心字段 - 事实表从 DWD 事实表提取核心度量 - 第一版保持精简,后续按需扩展 **app schema 设计原则**: - 以视图(VIEW)封装 DWS/Core 层数据 - 所有视图启用 RLS,以 `site_id` 过滤 - 提供函数接口供 FDW 映射使用 - 不存储实际数据,仅做访问层 **RLS 实现方案**: ```sql -- 创建应用角色 CREATE ROLE app_reader; -- 在 app schema 的视图上启用 RLS ALTER TABLE app.v_member_summary ENABLE ROW LEVEL SECURITY; -- 创建策略:根据会话变量 app.current_site_id 过滤 CREATE POLICY site_isolation ON app.v_member_summary FOR SELECT TO app_reader USING (site_id = current_setting('app.current_site_id')::bigint); ``` ### 7. 业务数据库设计(zqyy_app) **核心表**: - `users`:用户账户(微信 OpenID、手机号、角色) - `roles` / `permissions`:RBAC 权限模型 - `user_roles`:用户-角色关联 - `tasks`:任务管理(审批流) - `approvals`:审批记录 **FDW 映射**: ```sql -- 在 zqyy_app 中创建外部服务器 CREATE SERVER etl_feiqiu_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'localhost', dbname 'etl_feiqiu', port '5432'); -- 创建用户映射 CREATE USER MAPPING FOR app_user SERVER etl_feiqiu_server OPTIONS (user 'app_reader', password '***'); -- 导入 app schema 的外部表 IMPORT FOREIGN SCHEMA app FROM SERVER etl_feiqiu_server INTO fdw_etl; ``` **约束**:FDW 映射为只读,`zqyy_app` 不存储 ETL 数据副本。 ### 8. FastAPI 后端骨架 **项目结构**: ``` apps/backend/ ├── app/ │ ├── __init__.py │ ├── main.py # FastAPI 入口 │ ├── config.py # 配置加载 │ ├── database.py # 数据库连接 │ ├── routers/ # 路由模块 │ │ └── __init__.py │ ├── middleware/ # 中间件 │ │ └── __init__.py │ └── schemas/ # Pydantic 模型 │ └── __init__.py ├── tests/ │ └── __init__.py ├── pyproject.toml └── README.md ``` **关键配置**: - 连接 `zqyy_app` 数据库(通过 FDW 访问 ETL 数据) - OpenAPI 文档自动生成(FastAPI 内置) - 依赖 `packages/shared` 获取通用工具 ### 9. 共享包(packages/shared) **模块划分**: ``` packages/shared/ ├── src/ │ └── neozqyy_shared/ │ ├── __init__.py │ ├── enums.py # 字段枚举定义 │ ├── money.py # 金额精度工具(CNY, numeric(2)) │ └── datetime_utils.py # 时间处理工具 ├── tests/ │ └── __init__.py ├── pyproject.toml └── README.md ``` **提取来源**: - `enums.py`:从 ETL 的 `models/` 中提取通用枚举 - `money.py`:金额四舍五入、格式化(`Decimal` + `ROUND_HALF_UP`,scale=2) - `datetime_utils.py`:时区转换、日期范围计算(从 `utils/` 提取) ### 10. .kiro 迁移 **迁移内容**: - 复制 `.kiro/steering/` 到 Monorepo - 更新 `product.md`:从单一 ETL 视角扩展为 Monorepo 全局视角 - 更新 `tech.md`:新增 FastAPI、uv workspace、Donut+TDesign 等技术栈 - 更新 `structure-lite.md`:反映 Monorepo 目录结构和模块边界 - 更新路径引用:所有 steering 文件中的路径适配新结构 ## 数据模型 ### etl_feiqiu 数据库(六层 Schema) ```mermaid erDiagram META { bigint run_id PK text task_code timestamptz started_at timestamptz ended_at text status jsonb result_summary } META ||--o{ ODS : "调度触发" ODS { bigint id PK text content_hash PK jsonb payload text source_endpoint timestamptz fetched_at } ODS ||--o{ DWD : "清洗装载" DWD { bigint id PK timestamptz scd2_start_time timestamptz scd2_end_time int scd2_is_current int scd2_version } DWD ||--o{ CORE : "提取最小字段集" CORE { bigint id PK text name bigint site_id } DWD ||--o{ DWS : "汇总聚合" CORE ||--o{ DWS : "汇总聚合" DWS { bigint id PK date stat_date numeric amount bigint site_id } DWS ||--o{ APP : "视图封装" APP { text view_name text rls_policy } ``` ### zqyy_app 数据库 ```mermaid erDiagram USERS { bigint id PK text wx_openid UK text mobile text nickname int status timestamptz created_at } ROLES { int id PK text name UK text description } USER_ROLES { bigint user_id FK int role_id FK } PERMISSIONS { int id PK text resource text action } ROLE_PERMISSIONS { int role_id FK int permission_id FK } USERS ||--o{ USER_ROLES : "拥有" ROLES ||--o{ USER_ROLES : "分配给" ROLES ||--o{ ROLE_PERMISSIONS : "包含" PERMISSIONS ||--o{ ROLE_PERMISSIONS : "授予" FDW_ETL_VIEWS { text foreign_table_name text source_schema text mapping_type } ``` ### 配置分层模型 ``` 优先级(低 → 高): ┌─────────────────────────────┐ │ 根 .env(公共配置模板) │ DB_HOST, DB_PORT, TIMEZONE ├─────────────────────────────┤ │ 应用 .env.local(私有覆盖) │ DB_NAME, DB_PASSWORD, API_TOKEN ├─────────────────────────────┤ │ 环境变量 │ 运行时覆盖 ├─────────────────────────────┤ │ CLI 参数 │ 最高优先级 └─────────────────────────────┘ ``` ## 正确性属性 *属性是系统在所有有效执行中都应保持为真的特征或行为——本质上是关于系统应该做什么的形式化陈述。属性是人类可读规格与机器可验证正确性保证之间的桥梁。* ### Property 1: README.md 结构完整性 *对于任意* Monorepo 一级目录,其 README.md 文件应存在且包含"作用说明"、"结构描述"和"Roadmap"三个段落。 **Validates: Requirements 1.5** ### Property 2: Python 子项目配置完整性 *对于任意* uv workspace 声明的 Python 子项目成员,该子项目目录下应存在独立的 `pyproject.toml` 文件,且文件中包含 `[project]` 段落。 **Validates: Requirements 3.2** ### Property 3: 配置优先级 - .env.local 覆盖 *对于任意*配置项名称和两个不同的值,当根 `.env` 和应用 `.env.local` 都定义了该配置项时,配置加载器返回的值应等于 `.env.local` 中的值。 **Validates: Requirements 4.3** ### Property 4: 必需配置缺失检测 *对于任意*必需配置项,当所有配置层级(.env、.env.local、环境变量、CLI)均未提供该项时,配置加载器应抛出错误,且错误信息中包含该缺失配置项的名称。 **Validates: Requirements 4.4** ### Property 5: 文件迁移完整性 *对于任意*源-目标目录映射关系(ETL 业务代码、database 文件、tests 目录),源目录中的每个文件在目标目录的对应位置都应存在且内容一致。 **Validates: Requirements 5.1, 5.2, 5.3** ### Property 6: Schema 表定义迁移完整性 *对于任意*现有数据库 schema(billiards_ods、billiards_dws)中的表,新 schema(ods、dws)的 DDL 文件中应包含该表的 CREATE TABLE 定义。 **Validates: Requirements 7.3, 7.6** ### Property 7: Core schema 最小字段集 *对于任意* core schema 中的表,其字段数量应严格少于对应 dwd schema 中同名(或对应)表的字段数量。 **Validates: Requirements 7.5** ### Property 8: 测试数据库结构一致性 *对于任意*生产数据库(etl_feiqiu、zqyy_app)中的 schema 和表定义,对应的测试数据库(test_etl_feiqiu、test_zqyy_app)中应存在相同的 schema 和表结构。 **Validates: Requirements 9.1, 9.2** ### Property 9: Steering 文件路径更新 *对于任意* `.kiro/steering/` 目录下的文件,文件内容中不应包含旧仓库路径引用(如 `FQ-ETL`、`C:\ZQYY\FQ-ETL`)。 **Validates: Requirements 10.2** ### Property 10: 业务表 site_id 字段存在性 *对于任意* app schema 中的业务视图和 dws/core schema 中的业务表,其定义中应包含 `site_id` 字段。 **Validates: Requirements 13.1** ### Property 11: RLS 按 site_id 隔离 *对于任意* app schema 中启用了 RLS 的视图,当会话变量 `app.current_site_id` 设置为某个门店 ID 时,查询结果应仅包含该 `site_id` 的数据行。 **Validates: Requirements 13.2** ## 错误处理 ### 配置错误 - **缺失必需配置**:启动时立即报错,列出所有缺失项名称,不启动服务 - **配置值格式错误**:报告具体的配置项路径和期望格式 - **.env 文件不存在**:使用默认值继续,不报错(.env.template 仅为模板) ### 迁移错误 - **源文件不存在**:记录警告日志,继续迁移其他文件,最终汇总报告缺失文件列表 - **目标目录已存在**:提示用户确认是否覆盖,默认不覆盖 - **import 路径修复失败**:记录错误日志,标记需要手动修复的文件 ### 数据库错误 - **Schema 创建失败**:回滚当前 schema 的所有 DDL,报告失败原因 - **FDW 连接失败**:记录错误日志,不影响本地表的正常使用 - **RLS 策略创建失败**:回滚策略创建,报告受影响的表 ### 测试数据库错误 - **结构不一致**:提供 diff 工具比较生产与测试库结构差异 - **数据迁移失败**:回滚到迁移前状态,报告失败的表和原因 ## 测试策略 ### 测试框架 - **单元测试**:`pytest`(Python 子项目) - **属性测试**:`hypothesis`(Python 属性测试库) - 每个属性测试配置最少 100 次迭代 ### 单元测试覆盖 1. **Scaffold 测试**:验证目录创建、文件生成的具体示例 2. **配置加载器测试**:验证分层加载、冲突处理、缺失检测的边界情况 3. **迁移脚本测试**:验证文件复制、路径映射的具体场景 4. **DDL 语法测试**:验证生成的 SQL 语法正确性 ### 属性测试覆盖 每个属性测试必须引用设计文档中的属性编号: - **Feature: monorepo-migration, Property 1: README.md 结构完整性** — 验证所有一级目录 README 包含必需段落 - **Feature: monorepo-migration, Property 2: Python 子项目配置完整性** — 验证所有 workspace 成员有 pyproject.toml - **Feature: monorepo-migration, Property 3: 配置优先级** — 生成随机配置项,验证 .env.local 覆盖行为 - **Feature: monorepo-migration, Property 4: 必需配置缺失检测** — 生成随机必需项组合,验证缺失报错 - **Feature: monorepo-migration, Property 5: 文件迁移完整性** — 验证源-目标文件映射的完整性 - **Feature: monorepo-migration, Property 6: Schema 表定义迁移完整性** — 验证现有表在新 DDL 中存在 - **Feature: monorepo-migration, Property 7: Core schema 最小字段集** — 验证 core 表字段数少于 dwd - **Feature: monorepo-migration, Property 8: 测试数据库结构一致性** — 验证测试库与生产库结构相同 - **Feature: monorepo-migration, Property 9: Steering 文件路径更新** — 验证无旧路径残留 - **Feature: monorepo-migration, Property 10: 业务表 site_id 存在性** — 验证业务表包含 site_id - **Feature: monorepo-migration, Property 11: RLS 隔离** — 验证 RLS 按 site_id 过滤(集成测试) ### 集成测试 - **ETL 运行验证**:在新目录结构下运行 `pytest tests/unit`,确保所有现有测试通过 - **数据库 Schema 验证**:在测试数据库上执行 DDL,验证 schema 创建成功 - **FDW 连接验证**:验证 zqyy_app 通过 FDW 可读取 etl_feiqiu 的 app schema 数据 - **uv workspace 验证**:运行 `uv sync`,验证所有子项目依赖正确解析