feat: P1-P3 全栈集成 — 数据库基础 + DWS 扩展 + 小程序鉴权 + 工程化体系
## P1 数据库基础 - zqyy_app: 创建 auth/biz schema、FDW 连接 etl_feiqiu - etl_feiqiu: 创建 app schema RLS 视图、商品库存预警表 - 清理 assistant_abolish 残留数据 ## P2 ETL/DWS 扩展 - 新增 DWS 助教订单贡献度表 (dws.assistant_order_contribution) - 新增 assistant_order_contribution_task 任务及 RLS 视图 - member_consumption 增加充值字段、assistant_daily 增加处罚字段 - 更新 ODS/DWD/DWS 任务文档及业务规则文档 - 更新 consistency_checker、flow_runner、task_registry 等核心模块 ## P3 小程序鉴权系统 - 新增 xcx_auth 路由/schema(微信登录 + JWT) - 新增 wechat/role/matching/application 服务层 - zqyy_app 鉴权表迁移 + 角色权限种子数据 - auth/dependencies.py 支持小程序 JWT 鉴权 ## 文档与审计 - 新增 DOCUMENTATION-MAP 文档导航 - 新增 7 份 BD_Manual 数据库变更文档 - 更新 DDL 基线快照(etl_feiqiu 6 schema + zqyy_app auth) - 新增全栈集成审计记录、部署检查清单更新 - 新增 BACKLOG 路线图、FDW→Core 迁移计划 ## Kiro 工程化 - 新增 5 个 Spec(P1/P2/P3/全栈集成/核心业务) - 新增审计自动化脚本(agent_on_stop/build_audit_context/compliance_prescan) - 新增 6 个 Hook(合规检查/会话日志/提交审计等) - 新增 doc-map steering 文件 ## 运维与测试 - 新增 ops 脚本:迁移验证/API 健康检查/ETL 监控/集成报告 - 新增属性测试:test_dws_contribution / test_auth_system - 清理过期 export 报告文件 - 更新 .gitignore 排除规则
This commit is contained in:
1
.kiro/specs/01-miniapp-db-foundation/.config.kiro
Normal file
1
.kiro/specs/01-miniapp-db-foundation/.config.kiro
Normal file
@@ -0,0 +1 @@
|
||||
{"generationMode": "requirements-first"}
|
||||
431
.kiro/specs/01-miniapp-db-foundation/design.md
Normal file
431
.kiro/specs/01-miniapp-db-foundation/design.md
Normal file
@@ -0,0 +1,431 @@
|
||||
# 设计文档:小程序数据库基础设施层(miniapp-db-foundation)
|
||||
|
||||
## 概述
|
||||
|
||||
本设计实现 P1 基础设施层的三大核心能力:
|
||||
|
||||
1. **业务库 Schema 划分**:在 `test_zqyy_app` 中创建 `auth`(认证)和 `biz`(业务)两个 Schema,配合权限管理
|
||||
2. **ETL 库 RLS 视图层**:在 `test_etl_feiqiu.app` Schema 中为 35 张 DWD/DWS 表创建行级安全视图,通过 `site_id` 隔离多门店数据
|
||||
3. **FDW 跨库映射**:通过 `postgres_fdw` 将 ETL 库的 RLS 视图映射为业务库的只读外部表
|
||||
|
||||
**环境变量驱动**:所有数据库名称通过 `.env` 环境变量引用,不硬编码。迁移脚本中使用占位符,验证脚本从 `PG_DSN` / `APP_DB_DSN` 解析连接信息。
|
||||
|
||||
| 环境变量 | 用途 | 示例值 |
|
||||
|---------|------|--------|
|
||||
| `PG_DSN` | ETL 库连接字符串 | `postgresql://user:pass@host:5432/test_etl_feiqiu` |
|
||||
| `APP_DB_DSN` | 业务库连接字符串 | `postgresql://user:pass@host:5432/test_zqyy_app` |
|
||||
|
||||
整体数据流向:
|
||||
|
||||
```
|
||||
ETL 库(PG_DSN) 业务库(APP_DB_DSN)
|
||||
┌─────────────────────┐ ┌─────────────────────┐
|
||||
│ dwd.dim_member │ │ auth (用户认证) │
|
||||
│ dwd.dim_assistant │ │ biz (业务数据) │
|
||||
│ dws.dws_* │ │ public (系统管理) │
|
||||
│ dws.cfg_* │ │ │
|
||||
│ │ │ │ fdw_etl │
|
||||
│ ▼ │ postgres_fdw │ ├ v_dim_member │
|
||||
│ app.v_dim_member │ ◄──────────────► │ ├ v_dim_assistant │
|
||||
│ app.v_dws_* │ IMPORT SCHEMA │ └ v_dws_* │
|
||||
│ (RLS: site_id 过滤) │ │ (外部表,只读) │
|
||||
└─────────────────────┘ └─────────────────────┘
|
||||
```
|
||||
|
||||
## 架构
|
||||
|
||||
### 分层架构
|
||||
|
||||
```mermaid
|
||||
graph TB
|
||||
subgraph "业务库(APP_DB_DSN)"
|
||||
AUTH["auth Schema<br/>用户认证、权限、映射"]
|
||||
BIZ["biz Schema<br/>业务数据"]
|
||||
PUBLIC["public Schema<br/>系统管理表(保留)"]
|
||||
FDW["fdw_etl Schema<br/>FDW 外部表(只读)"]
|
||||
end
|
||||
|
||||
subgraph "ETL 库(PG_DSN)"
|
||||
APP["app Schema<br/>RLS 视图层"]
|
||||
DWD["dwd Schema<br/>明细层(11 张表)"]
|
||||
DWS["dws Schema<br/>汇总层(24 张表)"]
|
||||
end
|
||||
|
||||
FDW -->|"postgres_fdw<br/>IMPORT FOREIGN SCHEMA"| APP
|
||||
APP -->|"WHERE site_id = current_setting(...)"| DWD
|
||||
APP -->|"WHERE site_id = current_setting(...)"| DWS
|
||||
```
|
||||
|
||||
### 执行顺序
|
||||
|
||||
迁移脚本必须按以下顺序执行:
|
||||
|
||||
1. **ETL 库**(通过 `PG_DSN` 连接):创建 `app` Schema → 创建 `app_reader` 角色 → 创建 RLS 视图 → 授权
|
||||
2. **业务库**(通过 `APP_DB_DSN` 连接):创建 `auth`/`biz` Schema → 安装 `postgres_fdw` → 创建外部服务器 → 用户映射 → 导入外部表 → 授权
|
||||
|
||||
## 组件与接口
|
||||
|
||||
### 组件 1:Schema 管理(业务库)
|
||||
|
||||
**职责**:在业务库(`APP_DB_DSN`)中创建 `auth` 和 `biz` Schema,配置权限。
|
||||
|
||||
**SQL 接口**:
|
||||
```sql
|
||||
-- 创建 Schema
|
||||
CREATE SCHEMA IF NOT EXISTS auth;
|
||||
CREATE SCHEMA IF NOT EXISTS biz;
|
||||
|
||||
-- 授权 app_user
|
||||
GRANT USAGE ON SCHEMA auth TO app_user;
|
||||
GRANT USAGE ON SCHEMA biz TO app_user;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA auth TO app_user;
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA biz TO app_user;
|
||||
|
||||
-- 未来新表自动授权
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA auth
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA biz
|
||||
GRANT SELECT, INSERT, UPDATE, DELETE ON TABLES TO app_user;
|
||||
```
|
||||
|
||||
### 组件 2:RLS 视图层(ETL 库)
|
||||
|
||||
**职责**:在 ETL 库(`PG_DSN`)的 `app` Schema 中为每张源表创建带 `site_id` 过滤的视图。
|
||||
|
||||
**视图命名规则**:`app.v_<源表名>`,例如 `app.v_dim_member`、`app.v_dws_member_consumption_summary`。
|
||||
|
||||
**视图模板**:
|
||||
```sql
|
||||
CREATE OR REPLACE VIEW app.v_<源表名> AS
|
||||
SELECT * FROM <schema>.<源表名>
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
```
|
||||
|
||||
**DWD 层视图清单(11 张)**:
|
||||
|
||||
| 视图名 | 源表 |
|
||||
|--------|------|
|
||||
| `app.v_dim_member` | `dwd.dim_member` |
|
||||
| `app.v_dim_assistant` | `dwd.dim_assistant` |
|
||||
| `app.v_dim_member_card_account` | `dwd.dim_member_card_account` |
|
||||
| `app.v_dim_table` | `dwd.dim_table` |
|
||||
| `app.v_dwd_settlement_head` | `dwd.dwd_settlement_head` |
|
||||
| `app.v_dwd_table_fee_log` | `dwd.dwd_table_fee_log` |
|
||||
| `app.v_dwd_assistant_service_log` | `dwd.dwd_assistant_service_log` |
|
||||
| `app.v_dwd_recharge_order` | `dwd.dwd_recharge_order` |
|
||||
| `app.v_dwd_store_goods_sale` | `dwd.dwd_store_goods_sale` |
|
||||
| `app.v_dim_staff` | `dwd.dim_staff` |
|
||||
| `app.v_dim_staff_ex` | `dwd.dim_staff_ex` |
|
||||
|
||||
**DWS 层视图清单(24 张)**:
|
||||
|
||||
| 视图名 | 源表 |
|
||||
|--------|------|
|
||||
| `app.v_dws_member_consumption_summary` | `dws.dws_member_consumption_summary` |
|
||||
| `app.v_dws_member_visit_detail` | `dws.dws_member_visit_detail` |
|
||||
| `app.v_dws_member_winback_index` | `dws.dws_member_winback_index` |
|
||||
| `app.v_dws_member_newconv_index` | `dws.dws_member_newconv_index` |
|
||||
| `app.v_dws_member_recall_index` | `dws.dws_member_recall_index` |
|
||||
| `app.v_dws_member_assistant_relation_index` | `dws.dws_member_assistant_relation_index` |
|
||||
| `app.v_dws_member_assistant_intimacy` | `dws.dws_member_assistant_intimacy` |
|
||||
| `app.v_dws_assistant_daily_detail` | `dws.dws_assistant_daily_detail` |
|
||||
| `app.v_dws_assistant_monthly_summary` | `dws.dws_assistant_monthly_summary` |
|
||||
| `app.v_dws_assistant_salary_calc` | `dws.dws_assistant_salary_calc` |
|
||||
| `app.v_dws_assistant_customer_stats` | `dws.dws_assistant_customer_stats` |
|
||||
| `app.v_dws_assistant_finance_analysis` | `dws.dws_assistant_finance_analysis` |
|
||||
| `app.v_dws_finance_daily_summary` | `dws.dws_finance_daily_summary` |
|
||||
| `app.v_dws_finance_income_structure` | `dws.dws_finance_income_structure` |
|
||||
| `app.v_dws_finance_recharge_summary` | `dws.dws_finance_recharge_summary` |
|
||||
| `app.v_dws_finance_discount_detail` | `dws.dws_finance_discount_detail` |
|
||||
| `app.v_dws_finance_expense_summary` | `dws.dws_finance_expense_summary` |
|
||||
| `app.v_dws_platform_settlement` | `dws.dws_platform_settlement` |
|
||||
| `app.v_dws_assistant_recharge_commission` | `dws.dws_assistant_recharge_commission` |
|
||||
| `app.v_cfg_performance_tier` | `dws.cfg_performance_tier` |
|
||||
| `app.v_cfg_assistant_level_price` | `dws.cfg_assistant_level_price` |
|
||||
| `app.v_cfg_bonus_rules` | `dws.cfg_bonus_rules` |
|
||||
| `app.v_cfg_index_parameters` | `dws.cfg_index_parameters` |
|
||||
| `app.v_dws_order_summary` | `dws.dws_order_summary` |
|
||||
|
||||
**P2 预留(注释标记,暂不创建)**:
|
||||
- `dws.dws_member_spending_power_index` → 待 P2 完成后补充
|
||||
- `dws.dws_assistant_order_contribution` → 待 P2 完成后补充
|
||||
|
||||
**`cfg_*` 表特殊处理**:配置表(`cfg_performance_tier`、`cfg_assistant_level_price`、`cfg_bonus_rules`、`cfg_index_parameters`)可能不含 `site_id` 列。对于不含 `site_id` 的配置表,视图直接 `SELECT *` 不加过滤条件。
|
||||
|
||||
**权限配置**:
|
||||
```sql
|
||||
-- 创建只读角色(如不存在)
|
||||
DO $$ BEGIN
|
||||
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'app_reader') THEN
|
||||
CREATE ROLE app_reader LOGIN;
|
||||
END IF;
|
||||
END $$;
|
||||
|
||||
GRANT USAGE ON SCHEMA app TO app_reader;
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA app TO app_reader;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA app GRANT SELECT ON TABLES TO app_reader;
|
||||
```
|
||||
|
||||
### 组件 3:FDW 跨库映射(业务库)
|
||||
|
||||
**职责**:通过 `postgres_fdw` 将 ETL 库 `app` Schema 的视图映射为业务库 `fdw_etl` Schema 的外部表。
|
||||
|
||||
**实现方式**:使用 `IMPORT FOREIGN SCHEMA` 批量导入,而非逐表定义外部表。这与现有 `db/fdw/setup_fdw_test.sql` 的模式一致。
|
||||
|
||||
**环境感知**:迁移脚本中的 `host`、`dbname`、`port`、`password` 等连接参数使用占位符 `'***'`,部署时根据环境替换。项目已有 `db/fdw/setup_fdw_test.sql`(测试环境)和 `db/fdw/setup_fdw.sql`(生产环境)的分环境模式,本次迁移脚本遵循相同模式——提供测试环境和生产环境两个版本。
|
||||
|
||||
```sql
|
||||
-- 安装扩展
|
||||
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
|
||||
|
||||
-- 创建外部服务器
|
||||
-- host / dbname / port 按环境替换,从 PG_DSN 解析 ETL 库连接信息
|
||||
CREATE SERVER IF NOT EXISTS etl_feiqiu_server
|
||||
FOREIGN DATA WRAPPER postgres_fdw
|
||||
OPTIONS (host '***', dbname '***', port '***');
|
||||
|
||||
-- 用户映射(密码按环境替换)
|
||||
CREATE USER MAPPING IF NOT EXISTS FOR app_user
|
||||
SERVER etl_feiqiu_server
|
||||
OPTIONS (user 'app_reader', password '***');
|
||||
|
||||
-- 创建目标 Schema
|
||||
CREATE SCHEMA IF NOT EXISTS fdw_etl;
|
||||
|
||||
-- 批量导入
|
||||
IMPORT FOREIGN SCHEMA app
|
||||
FROM SERVER etl_feiqiu_server
|
||||
INTO fdw_etl;
|
||||
|
||||
-- 授权
|
||||
GRANT USAGE ON SCHEMA fdw_etl TO app_user;
|
||||
GRANT SELECT ON ALL TABLES IN SCHEMA fdw_etl TO app_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_etl GRANT SELECT ON TABLES TO app_user;
|
||||
```
|
||||
|
||||
**设计决策**:
|
||||
1. 使用 `IMPORT FOREIGN SCHEMA` 而非逐表 `CREATE FOREIGN TABLE`——自动匹配列定义,避免手动维护列类型不一致的风险
|
||||
2. 新增 RLS 视图后只需重新执行 `IMPORT` 即可同步
|
||||
3. 与现有 `db/fdw/setup_fdw_test.sql` 保持一致
|
||||
4. 服务器名使用通用名 `etl_feiqiu_server`(不含环境前缀),通过连接参数区分环境
|
||||
|
||||
### 组件 4:验证脚本
|
||||
|
||||
**职责**:自动化检查所有数据库对象是否正确创建。
|
||||
|
||||
**文件位置**:`scripts/ops/validate_p1_db_foundation.py`
|
||||
|
||||
**接口**:
|
||||
```python
|
||||
def validate_p1_db_foundation() -> dict:
|
||||
"""
|
||||
返回验证结果字典:
|
||||
{
|
||||
"schemas": {"auth": bool, "biz": bool, "app": bool, "fdw_etl": bool},
|
||||
"rls_views": {"app.v_dim_member": bool, ...},
|
||||
"fdw_tables": {"fdw_etl.v_dim_member": bool, ...},
|
||||
"rls_filtering": bool,
|
||||
"permissions": {"app_user": bool, "app_reader": bool},
|
||||
"errors": [str, ...]
|
||||
}
|
||||
"""
|
||||
```
|
||||
|
||||
**环境变量依赖**(强制从 `.env` 加载,缺失时 `RuntimeError` 终止):
|
||||
- `PG_DSN`:ETL 库连接字符串(从中解析 host、port、dbname)
|
||||
- `APP_DB_DSN`:业务库连接字符串(从中解析 host、port、dbname)
|
||||
- 脚本通过 `load_dotenv()` 加载根 `.env`,禁止硬编码任何数据库名称或连接参数
|
||||
|
||||
## 数据模型
|
||||
|
||||
### Schema 拓扑
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
test_zqyy_app_auth {
|
||||
string schema_name "auth"
|
||||
string purpose "用户认证、权限、映射"
|
||||
}
|
||||
test_zqyy_app_biz {
|
||||
string schema_name "biz"
|
||||
string purpose "业务数据(任务、备注、AI、Excel)"
|
||||
}
|
||||
test_zqyy_app_fdw_etl {
|
||||
string schema_name "fdw_etl"
|
||||
string purpose "FDW 外部表(只读)"
|
||||
}
|
||||
test_zqyy_app_public {
|
||||
string schema_name "public"
|
||||
string purpose "系统管理表(保留)"
|
||||
}
|
||||
test_etl_feiqiu_app {
|
||||
string schema_name "app"
|
||||
string purpose "RLS 视图层"
|
||||
}
|
||||
test_etl_feiqiu_dwd {
|
||||
string schema_name "dwd"
|
||||
string purpose "明细层(11 张表)"
|
||||
}
|
||||
test_etl_feiqiu_dws {
|
||||
string schema_name "dws"
|
||||
string purpose "汇总层(24 张表)"
|
||||
}
|
||||
|
||||
test_etl_feiqiu_app ||--o{ test_etl_feiqiu_dwd : "视图引用"
|
||||
test_etl_feiqiu_app ||--o{ test_etl_feiqiu_dws : "视图引用"
|
||||
test_zqyy_app_fdw_etl ||--|| test_etl_feiqiu_app : "postgres_fdw 映射"
|
||||
```
|
||||
|
||||
### RLS 视图数据流
|
||||
|
||||
对于含 `site_id` 的表:
|
||||
```
|
||||
源表数据(全量)→ RLS 视图(site_id 过滤)→ FDW 外部表(只读访问)
|
||||
```
|
||||
|
||||
对于不含 `site_id` 的配置表(`cfg_*`):
|
||||
```
|
||||
源表数据(全量)→ 直通视图(无过滤)→ FDW 外部表(只读访问)
|
||||
```
|
||||
|
||||
### 迁移脚本清单
|
||||
|
||||
| 序号 | 目标库 | 文件名 | 内容 |
|
||||
|------|--------|--------|------|
|
||||
| 1 | ETL 库(`PG_DSN`) | `YYYY-MM-DD__p1_create_app_schema_rls_views.sql` | 创建 app Schema + 全部 RLS 视图 + app_reader 权限 |
|
||||
| 2 | 业务库(`APP_DB_DSN`) | `YYYY-MM-DD__p1_create_auth_biz_schemas.sql` | 创建 auth/biz Schema + app_user 权限 |
|
||||
| 3 | 业务库(`APP_DB_DSN`) | `YYYY-MM-DD__p1_setup_fdw_etl.sql` | FDW 扩展 + 外部服务器 + 用户映射 + 导入外部表 |
|
||||
|
||||
|
||||
## 正确性属性(Correctness Properties)
|
||||
|
||||
*属性是系统在所有有效执行中都应保持为真的特征或行为——本质上是关于系统应该做什么的形式化陈述。属性是人类可读规格与机器可验证正确性保证之间的桥梁。*
|
||||
|
||||
### Property 1:默认权限自动授予
|
||||
|
||||
*For any* 在 `auth` 或 `biz` Schema 中新创建的表,`app_user` 角色都应自动获得 SELECT、INSERT、UPDATE、DELETE 权限,无需额外手动授权。
|
||||
|
||||
**Validates: Requirements 1.5**
|
||||
|
||||
### Property 2:public Schema 不变量
|
||||
|
||||
*For any* 迁移脚本执行前后,`test_zqyy_app.public` Schema 中的表集合应保持不变——迁移不应删除、重命名或修改 `public` 中的现有表。
|
||||
|
||||
**Validates: Requirements 1.6**
|
||||
|
||||
### Property 3:RLS 视图定义包含 site_id 过滤
|
||||
|
||||
*For any* `test_etl_feiqiu.app` Schema 中的 RLS 视图(含 `site_id` 列的源表对应的视图),其视图定义 SQL 中都应包含 `current_setting('app.current_site_id')` 过滤条件。
|
||||
|
||||
**Validates: Requirements 2.4**
|
||||
|
||||
### Property 4:RLS 过滤正确性
|
||||
|
||||
*For any* 含 `site_id` 列的 RLS 视图和任意有效的 `site_id` 值,设置 `app.current_site_id` 后查询该视图,返回结果中所有行的 `site_id` 都应等于设置的值。
|
||||
|
||||
**Validates: Requirements 2.5**
|
||||
|
||||
### Property 5:未设置 site_id 时 RLS 视图拒绝访问
|
||||
|
||||
*For any* 含 `site_id` 过滤的 RLS 视图,在未设置 `app.current_site_id` 的会话中执行查询,应抛出错误而非返回数据。
|
||||
|
||||
**Validates: Requirements 2.6**
|
||||
|
||||
### Property 6:FDW 外部表完整性与数据一致性
|
||||
|
||||
*For any* `test_etl_feiqiu.app` Schema 中的视图,`test_zqyy_app.fdw_etl` Schema 中都应存在对应的可查询外部表,且在相同 `site_id` 条件下,外部表返回的数据与 RLS 视图返回的数据一致。
|
||||
|
||||
**Validates: Requirements 3.4, 3.5, 3.6**
|
||||
|
||||
### Property 7:迁移脚本结构合规性
|
||||
|
||||
*For any* `db/etl_feiqiu/migrations/` 或 `db/zqyy_app/migrations/` 中本次新增的迁移脚本文件,文件名应匹配 `YYYY-MM-DD__*.sql` 模式,且文件内容中应包含回滚语句(以注释形式)。
|
||||
|
||||
**Validates: Requirements 4.3, 4.4**
|
||||
|
||||
### Property 8:迁移脚本幂等性
|
||||
|
||||
*For any* 本次新增的迁移脚本,连续执行两次的结果应与执行一次相同——第二次执行不应产生错误。
|
||||
|
||||
**Validates: Requirements 4.5**
|
||||
|
||||
### Property 9:环境变量缺失时验证脚本报错
|
||||
|
||||
*For any* 必需环境变量(`PG_DSN`、`APP_DB_DSN`)的缺失组合,验证脚本应立即抛出错误终止,而非静默使用默认值或空字符串。
|
||||
|
||||
**Validates: Requirements 5.8**
|
||||
|
||||
## 错误处理
|
||||
|
||||
### 迁移脚本错误处理
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|---------|
|
||||
| Schema 已存在 | `CREATE SCHEMA IF NOT EXISTS` 幂等跳过 |
|
||||
| 视图已存在 | `CREATE OR REPLACE VIEW` 覆盖更新 |
|
||||
| 角色不存在 | `DO $$ ... IF NOT EXISTS ... END $$` 条件创建 |
|
||||
| 源表不存在(P2 待建表) | 以注释形式预留,不创建视图 |
|
||||
| FDW 服务器已存在 | `CREATE SERVER IF NOT EXISTS` 幂等跳过 |
|
||||
| 用户映射已存在 | `CREATE USER MAPPING IF NOT EXISTS` 幂等跳过 |
|
||||
| `IMPORT FOREIGN SCHEMA` 表已存在 | 先 `DROP SCHEMA fdw_etl CASCADE` 再重新导入(脚本中提供选项) |
|
||||
|
||||
### 验证脚本错误处理
|
||||
|
||||
| 场景 | 处理方式 |
|
||||
|------|---------|
|
||||
| 环境变量缺失 | `RuntimeError` 立即终止,输出缺失变量名 |
|
||||
| 数据库连接失败 | 捕获 `psycopg2.OperationalError`,输出连接参数(脱敏)和错误信息 |
|
||||
| Schema/视图/外部表不存在 | 记录为失败项,继续检查其余项目 |
|
||||
| RLS 过滤验证无数据 | 标记为 SKIP(无法验证),不标记为失败 |
|
||||
| 权限查询失败 | 记录具体错误,继续检查 |
|
||||
|
||||
### `current_setting` 未设置时的行为
|
||||
|
||||
PostgreSQL 中 `current_setting('app.current_site_id')` 在未设置时会抛出 `ERROR: unrecognized configuration parameter "app.current_site_id"`。这是期望行为(需求 2.6),确保不会意外返回全量数据。
|
||||
|
||||
如果需要更友好的错误信息,可以使用 `current_setting('app.current_site_id', true)` 返回 NULL,然后在视图中用 `CASE` 处理。但当前设计选择让 PostgreSQL 原生报错,因为:
|
||||
1. 更安全——不可能绕过
|
||||
2. 后端代码必须显式设置 `SET app.current_site_id = ...`,这是一个强制约束
|
||||
|
||||
## 测试策略
|
||||
|
||||
### 属性测试(Property-Based Testing)
|
||||
|
||||
使用 Python `hypothesis` 框架,测试目录:`tests/`(Monorepo 级属性测试目录)。
|
||||
|
||||
每个属性测试至少运行 100 次迭代。每个测试用注释标注对应的设计属性编号。
|
||||
|
||||
标注格式:`# Feature: miniapp-db-foundation, Property N: <属性标题>`
|
||||
|
||||
**属性测试清单**:
|
||||
|
||||
| 属性 | 测试方法 | 生成器 |
|
||||
|------|---------|--------|
|
||||
| P1 默认权限 | 生成随机表名,在 auth/biz 中创建表,验证 app_user 权限 | `hypothesis.strategies.text` 生成合法 SQL 标识符 |
|
||||
| P3 视图定义过滤 | 遍历所有 app schema 视图,检查定义 SQL | 无需生成器,遍历所有视图 |
|
||||
| P4 RLS 过滤正确性 | 生成随机 site_id,设置后查询视图,验证结果 | `hypothesis.strategies.integers` 生成 site_id |
|
||||
| P5 未设置 site_id 报错 | 遍历所有 RLS 视图,在新会话中查询 | 无需生成器,遍历所有视图 |
|
||||
| P7 脚本结构合规 | 遍历所有新增迁移脚本,验证命名和内容 | 无需生成器,遍历文件 |
|
||||
| P8 幂等性 | 对每个迁移脚本执行两次,验证无错误 | 无需生成器 |
|
||||
| P9 环境变量缺失 | 生成环境变量缺失组合,验证报错 | `hypothesis.strategies.sampled_from` 生成缺失组合 |
|
||||
|
||||
**注意**:P2(public schema 不变量)和 P6(FDW 数据一致性)需要真实数据库环境,作为集成测试在验证脚本中实现,而非 hypothesis 属性测试。
|
||||
|
||||
### 单元测试
|
||||
|
||||
单元测试聚焦于验证脚本的逻辑正确性:
|
||||
|
||||
- 验证脚本在 Schema 缺失时正确报告失败
|
||||
- 验证脚本在权限不足时正确报告
|
||||
- 验证脚本的输出格式正确(JSON 结构)
|
||||
- 环境变量缺失时的错误消息包含变量名
|
||||
|
||||
### 集成测试
|
||||
|
||||
集成测试通过验证脚本 `scripts/ops/validate_p1_db_foundation.py` 实现,覆盖:
|
||||
|
||||
- 全部 Schema 存在性检查
|
||||
- 全部 RLS 视图存在性和过滤正确性
|
||||
- 全部 FDW 外部表存在性和可查询性
|
||||
- 权限配置完整性
|
||||
- FDW 数据与 RLS 视图数据一致性
|
||||
88
.kiro/specs/01-miniapp-db-foundation/requirements.md
Normal file
88
.kiro/specs/01-miniapp-db-foundation/requirements.md
Normal file
@@ -0,0 +1,88 @@
|
||||
# 需求文档:小程序数据库基础设施层(miniapp-db-foundation)
|
||||
|
||||
## 简介
|
||||
|
||||
P1 基础设施层是整个小程序系统的第一个 SPEC,无前置依赖,是所有后续 SPEC 的硬依赖。本 SPEC 负责在业务库 `test_zqyy_app` 中建立清晰的 Schema 划分(`auth` + `biz`),在 ETL 库 `test_etl_feiqiu` 中为数据依赖矩阵列出的所有 DWD/DWS 表创建 RLS 视图(按 `site_id` 隔离),并通过 `postgres_fdw` 将 RLS 视图映射为业务库的外部表,使后端无需直连 ETL 库即可读取汇总/维度数据。
|
||||
|
||||
## 术语表
|
||||
|
||||
- **Schema_Manager**:负责在 PostgreSQL 数据库中创建和管理 Schema、权限配置的迁移脚本系统
|
||||
- **RLS_View_Layer**:在 `test_etl_feiqiu.app` Schema 中创建的一组视图,通过 `current_setting('app.current_site_id')` 按 `site_id` 过滤数据,实现行级安全隔离
|
||||
- **FDW_Bridge**:通过 `postgres_fdw` 扩展在 `test_zqyy_app.fdw_etl` Schema 中创建的外部表集合,只读映射 ETL 库 `app` Schema 的 RLS 视图
|
||||
- **Migration_Script**:存放在 `db/zqyy_app/migrations/` 或 `db/etl_feiqiu/migrations/` 中的纯 SQL 迁移脚本,以日期前缀命名
|
||||
- **Validation_Script**:用于验证数据库对象是否正确创建、权限是否配置正确、数据是否可查询的 Python 脚本
|
||||
- **site_id**:门店标识符,类型为 `BIGINT`,用于多门店数据隔离
|
||||
- **app_reader**:ETL 库侧的只读角色,供 FDW 用户映射使用
|
||||
- **app_user**:业务库侧的应用连接角色,通过 FDW 读取 ETL 数据
|
||||
|
||||
## 需求
|
||||
|
||||
### 需求 1:业务库 Schema 划分与权限配置
|
||||
|
||||
**用户故事:** 作为后端开发者,我需要 `test_zqyy_app` 中有清晰的 Schema 划分(`auth` + `biz`),以便按功能组织业务表。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN Migration_Script 执行完成, THE Schema_Manager SHALL 在 `test_zqyy_app` 中创建 `auth` Schema
|
||||
2. WHEN Migration_Script 执行完成, THE Schema_Manager SHALL 在 `test_zqyy_app` 中创建 `biz` Schema
|
||||
3. WHEN `auth` Schema 创建完成, THE Schema_Manager SHALL 授予 `app_user` 角色对 `auth` Schema 的 USAGE 权限和对其中所有表的 SELECT、INSERT、UPDATE、DELETE 权限
|
||||
4. WHEN `biz` Schema 创建完成, THE Schema_Manager SHALL 授予 `app_user` 角色对 `biz` Schema 的 USAGE 权限和对其中所有表的 SELECT、INSERT、UPDATE、DELETE 权限
|
||||
5. WHEN 新表在 `auth` 或 `biz` Schema 中创建, THE Schema_Manager SHALL 通过 ALTER DEFAULT PRIVILEGES 自动授予 `app_user` 角色相应权限
|
||||
6. THE Migration_Script SHALL 保留 `public` Schema 中现有的系统管理表(`admin_users`、`roles`、`permissions` 等)不受影响
|
||||
|
||||
### 需求 2:ETL 库 RLS 视图层创建
|
||||
|
||||
**用户故事:** 作为系统管理员,我需要 RLS 视图按 `site_id` 隔离数据,以便多门店数据安全。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN Migration_Script 执行完成, THE Schema_Manager SHALL 在 `test_etl_feiqiu` 中创建 `app` Schema(如不存在)
|
||||
2. WHEN `app` Schema 创建完成, THE RLS_View_Layer SHALL 为数据依赖矩阵中列出的每张 DWD 表创建对应的 RLS 视图(共 11 张:`dim_member`、`dim_assistant`、`dim_member_card_account`、`dim_table`、`dwd_settlement_head`、`dwd_table_fee_log`、`dwd_assistant_service_log`、`dwd_recharge_order`、`dwd_store_goods_sale`、`dim_staff`、`dim_staff_ex`)
|
||||
3. WHEN `app` Schema 创建完成, THE RLS_View_Layer SHALL 为数据依赖矩阵中列出的每张 DWS 表创建对应的 RLS 视图(共 24 张,包含 `dws_*` 和 `cfg_*` 表)
|
||||
4. THE RLS_View_Layer 中每个视图 SHALL 包含 `WHERE site_id = current_setting('app.current_site_id')::bigint` 过滤条件
|
||||
5. WHEN 设置 `app.current_site_id` 为某门店 ID 后查询 RLS 视图, THE RLS_View_Layer SHALL 仅返回该门店的数据
|
||||
6. WHEN 未设置 `app.current_site_id` 时查询 RLS 视图, THE RLS_View_Layer SHALL 抛出错误而非返回全部数据
|
||||
7. THE Schema_Manager SHALL 授予 `app_reader` 角色对 `app` Schema 的 USAGE 权限和对其中所有视图的 SELECT 权限
|
||||
8. THE RLS_View_Layer SHALL 为 P2 待建表(`dws_member_spending_power_index`、`dws_assistant_order_contribution`)在迁移脚本中以注释形式预留位置
|
||||
|
||||
### 需求 3:FDW 外部表映射
|
||||
|
||||
**用户故事:** 作为后端开发者,我需要通过 FDW 从 `test_zqyy_app` 读取 ETL 库的 DWS/DWD 数据,以便小程序页面展示 ETL 计算结果。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN Migration_Script 执行完成, THE FDW_Bridge SHALL 在 `test_zqyy_app` 中安装 `postgres_fdw` 扩展
|
||||
2. WHEN `postgres_fdw` 扩展安装完成, THE FDW_Bridge SHALL 创建指向 `test_etl_feiqiu` 的外部服务器 `test_etl_feiqiu_server`
|
||||
3. WHEN 外部服务器创建完成, THE FDW_Bridge SHALL 创建 `app_user` 到 `app_reader` 的用户映射
|
||||
4. WHEN 用户映射创建完成, THE FDW_Bridge SHALL 在 `fdw_etl` Schema 中通过 `IMPORT FOREIGN SCHEMA app` 导入所有外部表
|
||||
5. WHEN 外部表导入完成, THE FDW_Bridge SHALL 对 `fdw_etl` Schema 中的每张外部表执行 `SELECT` 查询验证可读性
|
||||
6. WHEN 外部表查询成功, THE FDW_Bridge 返回的数据 SHALL 与 ETL 库 `app` Schema 中对应 RLS 视图的数据一致
|
||||
7. THE FDW_Bridge SHALL 授予 `app_user` 角色对 `fdw_etl` Schema 的 USAGE 权限和对其中所有外部表的 SELECT 权限
|
||||
|
||||
### 需求 4:迁移脚本管理
|
||||
|
||||
**用户故事:** 作为后端开发者,我需要所有数据库变更都有对应的迁移脚本,以便变更可追溯、可重放、可回滚。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. THE Migration_Script SHALL 将 ETL 库变更(RLS 视图创建、`app` Schema 权限)存放在 `db/etl_feiqiu/migrations/` 目录中
|
||||
2. THE Migration_Script SHALL 将业务库变更(Schema 创建、FDW 配置)存放在 `db/zqyy_app/migrations/` 目录中
|
||||
3. THE Migration_Script SHALL 使用日期前缀命名(格式:`YYYY-MM-DD__<描述>.sql`)
|
||||
4. THE Migration_Script SHALL 在每个脚本中包含回滚语句(以注释形式)
|
||||
5. THE Migration_Script SHALL 使用 `IF NOT EXISTS` / `OR REPLACE` 等幂等语法,确保重复执行不会报错
|
||||
6. THE Migration_Script SHALL 使用 UTF-8 编码,纯 SQL(非 ORM)
|
||||
|
||||
### 需求 5:端到端验证
|
||||
|
||||
**用户故事:** 作为后端开发者,我需要一个自动化验证脚本,确认所有数据库对象正确创建且数据可访问。
|
||||
|
||||
#### 验收标准
|
||||
|
||||
1. WHEN Validation_Script 执行时, THE Validation_Script SHALL 检查 `test_zqyy_app` 中 `auth` 和 `biz` Schema 是否存在
|
||||
2. WHEN Validation_Script 执行时, THE Validation_Script SHALL 检查 `test_etl_feiqiu.app` Schema 中所有 RLS 视图是否存在
|
||||
3. WHEN Validation_Script 执行时, THE Validation_Script SHALL 检查 `test_zqyy_app.fdw_etl` 中所有外部表是否存在
|
||||
4. WHEN Validation_Script 执行时, THE Validation_Script SHALL 对每张外部表执行 `SELECT count(*)` 验证可查询性
|
||||
5. WHEN Validation_Script 执行时, THE Validation_Script SHALL 设置 `app.current_site_id` 后验证 RLS 视图正确过滤数据
|
||||
6. WHEN Validation_Script 执行时, THE Validation_Script SHALL 验证 `app_user` 和 `app_reader` 角色的权限配置正确
|
||||
7. WHEN 验证发现异常, THE Validation_Script SHALL 输出具体的失败项和错误信息
|
||||
8. THE Validation_Script SHALL 从 `.env` 加载数据库连接参数(`PG_DSN`、`APP_DB_DSN`),缺失时立即报错终止
|
||||
108
.kiro/specs/01-miniapp-db-foundation/tasks.md
Normal file
108
.kiro/specs/01-miniapp-db-foundation/tasks.md
Normal file
@@ -0,0 +1,108 @@
|
||||
# 实现计划:小程序数据库基础设施层(miniapp-db-foundation)
|
||||
|
||||
## 概述
|
||||
|
||||
按照"ETL 库先行 → 业务库跟进 → FDW 桥接 → 验证收尾"的顺序,将设计拆分为可增量执行的编码任务。每个迁移脚本使用幂等语法,所有连接参数通过环境变量驱动。
|
||||
|
||||
## 任务
|
||||
|
||||
- [x] 1. ETL 库:创建 app Schema 与 RLS 视图
|
||||
- [x] 1.1 编写迁移脚本 `db/etl_feiqiu/migrations/YYYY-MM-DD__p1_create_app_schema_rls_views.sql`
|
||||
- 创建 `app` Schema(`CREATE SCHEMA IF NOT EXISTS app`)
|
||||
- 创建 `app_reader` 角色(条件创建,`DO $$ ... IF NOT EXISTS ... END $$`)
|
||||
- 为 11 张 DWD 表创建 RLS 视图(`CREATE OR REPLACE VIEW app.v_<表名> AS SELECT * FROM dwd.<表名> WHERE site_id = current_setting('app.current_site_id')::bigint`)
|
||||
- 为 24 张 DWS 表创建 RLS 视图(同上模式;`cfg_*` 配置表若无 `site_id` 列则直接 `SELECT *` 不加过滤)
|
||||
- 在脚本末尾以注释形式预留 P2 待建表位置(`dws_member_spending_power_index`、`dws_assistant_order_contribution`)
|
||||
- 授予 `app_reader` 对 `app` Schema 的 USAGE + SELECT 权限 + ALTER DEFAULT PRIVILEGES
|
||||
- 包含回滚语句(注释形式)
|
||||
- _Requirements: 2.1, 2.2, 2.3, 2.4, 2.7, 2.8, 4.1, 4.3, 4.4, 4.5, 4.6_
|
||||
|
||||
- [x] 1.2 编写属性测试:RLS 视图定义包含 site_id 过滤
|
||||
- **Property 3: RLS 视图定义包含 site_id 过滤**
|
||||
- 遍历 `app` Schema 所有视图,查询 `pg_views.definition`,验证含 `site_id` 列的源表对应视图包含 `current_setting` 过滤条件
|
||||
- **Validates: Requirements 2.4**
|
||||
|
||||
- [x] 1.3 编写属性测试:未设置 site_id 时 RLS 视图拒绝访问
|
||||
- **Property 5: 未设置 site_id 时 RLS 视图拒绝访问**
|
||||
- 遍历所有含 `site_id` 过滤的 RLS 视图,在新会话(未设置 `app.current_site_id`)中执行 `SELECT`,验证抛出错误
|
||||
- **Validates: Requirements 2.6**
|
||||
|
||||
- [x] 2. 业务库:创建 auth/biz Schema 与权限
|
||||
- [x] 2.1 编写迁移脚本 `db/zqyy_app/migrations/YYYY-MM-DD__p1_create_auth_biz_schemas.sql`
|
||||
- 创建 `auth` Schema(`CREATE SCHEMA IF NOT EXISTS auth`)
|
||||
- 创建 `biz` Schema(`CREATE SCHEMA IF NOT EXISTS biz`)
|
||||
- 授予 `app_user` 对 `auth`/`biz` 的 USAGE + CRUD 权限
|
||||
- 设置 ALTER DEFAULT PRIVILEGES 自动授权未来新表
|
||||
- 包含回滚语句(注释形式)
|
||||
- _Requirements: 1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 4.2, 4.3, 4.4, 4.5, 4.6_
|
||||
|
||||
- [x] 2.2 编写属性测试:默认权限自动授予
|
||||
- **Property 1: 默认权限自动授予**
|
||||
- 使用 hypothesis 生成随机合法表名,在 `auth`/`biz` 中创建临时表,验证 `app_user` 自动获得 SELECT/INSERT/UPDATE/DELETE 权限,测试后清理
|
||||
- **Validates: Requirements 1.5**
|
||||
|
||||
- [x] 3. 业务库:配置 FDW 跨库映射
|
||||
- [x] 3.1 编写迁移脚本 `db/zqyy_app/migrations/YYYY-MM-DD__p1_setup_fdw_etl.sql`
|
||||
- 安装 `postgres_fdw` 扩展
|
||||
- 创建外部服务器 `etl_feiqiu_server`(host/dbname/port 使用占位符 `'***'`,按环境替换)
|
||||
- 创建 `app_user` → `app_reader` 用户映射
|
||||
- 创建 `fdw_etl` Schema
|
||||
- 执行 `IMPORT FOREIGN SCHEMA app FROM SERVER etl_feiqiu_server INTO fdw_etl`
|
||||
- 授予 `app_user` 对 `fdw_etl` 的 USAGE + SELECT 权限 + ALTER DEFAULT PRIVILEGES
|
||||
- 包含回滚语句(注释形式)
|
||||
- _Requirements: 3.1, 3.2, 3.3, 3.4, 3.7, 4.2, 4.3, 4.4, 4.5, 4.6_
|
||||
|
||||
- [x] 3.2 编写属性测试:FDW 外部表完整性与数据一致性
|
||||
- **Property 6: FDW 外部表完整性与数据一致性**
|
||||
- 遍历 ETL 库 `app` Schema 所有视图,验证 `fdw_etl` 中存在对应外部表且可查询;在相同 `site_id` 下对比数据一致性
|
||||
- **Validates: Requirements 3.4, 3.5, 3.6**
|
||||
|
||||
- [x] 4. Checkpoint — 迁移脚本验证
|
||||
- 确保三个迁移脚本均可在测试环境中成功执行(按顺序:ETL 库 → 业务库 Schema → 业务库 FDW)
|
||||
- 验证重复执行不报错(幂等性)
|
||||
- 如有问题请告知用户
|
||||
|
||||
- [x] 5. 编写端到端验证脚本
|
||||
- [x] 5.1 创建 `scripts/ops/validate_p1_db_foundation.py`
|
||||
- 通过 `load_dotenv()` 加载根 `.env`,读取 `PG_DSN` 和 `APP_DB_DSN`,缺失时 `RuntimeError` 终止
|
||||
- 检查业务库中 `auth`、`biz` Schema 存在性
|
||||
- 检查 ETL 库中 `app` Schema 及所有 RLS 视图存在性(对照设计文档中的 35 张视图清单)
|
||||
- 检查业务库中 `fdw_etl` Schema 及所有外部表存在性
|
||||
- 对每张外部表执行 `SELECT count(*)` 验证可查询性
|
||||
- 设置 `app.current_site_id` 后验证 RLS 视图过滤正确性
|
||||
- 验证 `app_user` 和 `app_reader` 角色权限配置
|
||||
- 输出结构化验证结果(通过/失败/跳过),失败项附带错误信息
|
||||
- _Requirements: 5.1, 5.2, 5.3, 5.4, 5.5, 5.6, 5.7, 5.8_
|
||||
|
||||
- [x] 5.2 编写属性测试:RLS 过滤正确性
|
||||
- **Property 4: RLS 过滤正确性**
|
||||
- 使用 hypothesis 生成随机 `site_id`,设置 `app.current_site_id` 后查询含 `site_id` 的 RLS 视图,验证返回结果中所有行的 `site_id` 等于设置值
|
||||
- **Validates: Requirements 2.5**
|
||||
|
||||
- [x] 5.3 编写属性测试:环境变量缺失报错
|
||||
- **Property 9: 环境变量缺失时验证脚本报错**
|
||||
- 使用 hypothesis 生成 `PG_DSN`/`APP_DB_DSN` 的缺失组合,验证脚本抛出 `RuntimeError`
|
||||
- **Validates: Requirements 5.8**
|
||||
|
||||
- [x] 5.4 编写属性测试:迁移脚本结构合规性
|
||||
- **Property 7: 迁移脚本结构合规性**
|
||||
- 遍历本次新增的迁移脚本文件,验证文件名匹配 `YYYY-MM-DD__*.sql` 模式,且内容包含回滚语句注释
|
||||
- **Validates: Requirements 4.3, 4.4**
|
||||
|
||||
- [x] 5.5 编写属性测试:迁移脚本幂等性
|
||||
- **Property 8: 迁移脚本幂等性**
|
||||
- 对每个迁移脚本连续执行两次,验证第二次执行无错误
|
||||
- **Validates: Requirements 4.5**
|
||||
|
||||
- [x] 6. Final checkpoint — 全量验证
|
||||
- 运行验证脚本 `python scripts/ops/validate_p1_db_foundation.py`,确认所有检查项通过
|
||||
- 运行属性测试 `cd C:\NeoZQYY && pytest tests/ -v -k p1`,确认所有属性测试通过
|
||||
- 如有问题请告知用户
|
||||
|
||||
## 说明
|
||||
|
||||
- 标记 `*` 的子任务为可选,可跳过以加速 MVP
|
||||
- 每个任务引用具体的需求编号,确保可追溯
|
||||
- Checkpoint 确保增量验证
|
||||
- 属性测试使用 Python hypothesis 框架,测试文件放在根目录 `tests/` 下
|
||||
- 迁移脚本中的数据库连接参数(host/dbname/port/password)均使用占位符,按环境替换
|
||||
Reference in New Issue
Block a user