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:
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 视图数据一致性
|
||||
Reference in New Issue
Block a user