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:
135
docs/database/BD_Manual_app_schema_rls_views.md
Normal file
135
docs/database/BD_Manual_app_schema_rls_views.md
Normal file
@@ -0,0 +1,135 @@
|
||||
# BD_Manual:app Schema 与 RLS 视图层
|
||||
|
||||
> 目标库:`test_etl_feiqiu`(通过 `PG_DSN` 连接)
|
||||
> 迁移脚本:`db/etl_feiqiu/migrations/2026-02-24__p1_create_app_schema_rls_views.sql`
|
||||
> DDL 位置:`docs/database/ddl/etl_feiqiu__app.sql`(执行后需重新生成)
|
||||
> 关联 SPEC:`miniapp-db-foundation`(P1 基础设施层)
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 新增 Schema
|
||||
- `app`:RLS 视图层,供业务库通过 `postgres_fdw` 只读访问 ETL 数据
|
||||
|
||||
### 新增角色
|
||||
- `app_reader`:只读角色(`LOGIN`),拥有 `app` Schema 的 `USAGE` + `SELECT` 权限
|
||||
|
||||
### 新增视图(35 张)
|
||||
|
||||
**DWD 层(11 张,全部含 `site_id` 过滤):**
|
||||
|
||||
| 视图 | 源表 | 过滤条件 |
|
||||
|------|------|---------|
|
||||
| `app.v_dim_member` | `dwd.dim_member` | `site_id = current_setting('app.current_site_id')::bigint` |
|
||||
| `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 层 — 含 `site_id` 过滤(20 张):**
|
||||
|
||||
| 视图 | 源表 |
|
||||
|------|------|
|
||||
| `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_dws_order_summary` | `dws.dws_order_summary` |
|
||||
|
||||
**DWS 层 — cfg_* 配置表(4 张,无 `site_id`,直接 `SELECT *`):**
|
||||
|
||||
| 视图 | 源表 | 说明 |
|
||||
|------|------|------|
|
||||
| `app.v_cfg_performance_tier` | `dws.cfg_performance_tier` | 无 `site_id` 列,不加过滤 |
|
||||
| `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` | 同上 |
|
||||
|
||||
### 权限配置
|
||||
|
||||
| 角色 | Schema | 权限 |
|
||||
|------|--------|------|
|
||||
| `app_reader` | `app` | `USAGE` + `SELECT ON ALL TABLES` + `ALTER DEFAULT PRIVILEGES` |
|
||||
|
||||
### P2 预留(注释形式,暂不创建)
|
||||
- `dws.dws_member_spending_power_index` → `app.v_dws_member_spending_power_index`
|
||||
- `dws.dws_assistant_order_contribution` → `app.v_dws_assistant_order_contribution`
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL 任务 | 无影响。视图仅读取 DWD/DWS 表,不影响 ETL 写入流程 |
|
||||
| 后端 API | 前置依赖。后端通过 FDW 读取 `app` Schema 视图,本脚本是 FDW 配置的前提 |
|
||||
| 小程序 | 无直接影响。小程序通过后端 API 间接访问 |
|
||||
| 管理后台 | 无直接影响 |
|
||||
| 现有 `app` Schema | 已有 7 个视图将被 `CREATE OR REPLACE` 覆盖更新,新增 28 个视图 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
迁移脚本末尾已包含注释形式的回滚语句,按逆序执行:
|
||||
|
||||
```sql
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA app REVOKE SELECT ON TABLES FROM app_reader;
|
||||
REVOKE SELECT ON ALL TABLES IN SCHEMA app FROM app_reader;
|
||||
REVOKE USAGE ON SCHEMA app FROM app_reader;
|
||||
DROP SCHEMA IF EXISTS app CASCADE; -- 会删除所有视图
|
||||
DROP ROLE IF EXISTS app_reader;
|
||||
```
|
||||
|
||||
注意:`DROP SCHEMA app CASCADE` 会级联删除所有视图和依赖的 FDW 外部表,需先回滚 FDW 配置。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 app Schema 存在
|
||||
SELECT schema_name FROM information_schema.schemata WHERE schema_name = 'app';
|
||||
|
||||
-- 2. 验证视图数量(应为 35 张)
|
||||
SELECT count(*) FROM information_schema.views WHERE table_schema = 'app';
|
||||
|
||||
-- 3. 验证 app_reader 角色存在且有 app Schema 权限
|
||||
SELECT has_schema_privilege('app_reader', 'app', 'USAGE') AS has_usage;
|
||||
|
||||
-- 4. 验证含 site_id 的视图定义包含 current_setting 过滤
|
||||
SELECT table_name, view_definition
|
||||
FROM information_schema.views
|
||||
WHERE table_schema = 'app'
|
||||
AND view_definition LIKE '%current_setting%'
|
||||
ORDER BY table_name;
|
||||
|
||||
-- 5. 验证 cfg_* 视图不含 current_setting 过滤
|
||||
SELECT table_name, view_definition
|
||||
FROM information_schema.views
|
||||
WHERE table_schema = 'app'
|
||||
AND table_name LIKE 'v_cfg_%'
|
||||
AND view_definition NOT LIKE '%current_setting%';
|
||||
```
|
||||
87
docs/database/BD_Manual_auth_biz_schemas.md
Normal file
87
docs/database/BD_Manual_auth_biz_schemas.md
Normal file
@@ -0,0 +1,87 @@
|
||||
# BD_Manual:auth/biz Schema 与权限配置
|
||||
|
||||
> 目标库:`test_zqyy_app`(通过 `APP_DB_DSN` 连接)
|
||||
> 迁移脚本:`db/zqyy_app/migrations/2026-02-24__p1_create_auth_biz_schemas.sql`
|
||||
> 关联 SPEC:`miniapp-db-foundation`(P1 基础设施层)
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 新增 Schema
|
||||
|
||||
| Schema | 用途 |
|
||||
|--------|------|
|
||||
| `auth` | 用户认证、权限、微信 OpenID 映射等 |
|
||||
| `biz` | 业务数据(任务、备注、AI 分析、Excel 导出等) |
|
||||
|
||||
### 权限配置
|
||||
|
||||
| 角色 | Schema | 权限 |
|
||||
|------|--------|------|
|
||||
| `app_user` | `auth` | `USAGE` + `SELECT, INSERT, UPDATE, DELETE ON ALL TABLES` + `ALTER DEFAULT PRIVILEGES` |
|
||||
| `app_user` | `biz` | `USAGE` + `SELECT, INSERT, UPDATE, DELETE ON ALL TABLES` + `ALTER DEFAULT PRIVILEGES` |
|
||||
|
||||
### 未操作的 Schema
|
||||
- `public`:保留现有系统管理表(`admin_users`、`roles`、`permissions` 等)不受影响,脚本不包含任何对 `public` Schema 的操作
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL 任务 | 无影响。本脚本仅操作业务库,不涉及 ETL 库 |
|
||||
| 后端 API | 前置依赖。后续业务表将创建在 `auth`/`biz` Schema 中,后端需使用 `auth.` / `biz.` 前缀或设置 `search_path` |
|
||||
| 小程序 | 无直接影响。小程序通过后端 API 间接访问 |
|
||||
| 管理后台 | 无直接影响 |
|
||||
| FDW 配置 | 无影响。`fdw_etl` Schema 独立于 `auth`/`biz` |
|
||||
| `public` Schema | 无影响。脚本不包含任何对 `public` 的操作 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
迁移脚本末尾已包含注释形式的回滚语句,按逆序执行:
|
||||
|
||||
```sql
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA biz REVOKE SELECT, INSERT, UPDATE, DELETE ON TABLES FROM app_user;
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA auth REVOKE SELECT, INSERT, UPDATE, DELETE ON TABLES FROM app_user;
|
||||
REVOKE SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA biz FROM app_user;
|
||||
REVOKE USAGE ON SCHEMA biz FROM app_user;
|
||||
REVOKE SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA auth FROM app_user;
|
||||
REVOKE USAGE ON SCHEMA auth FROM app_user;
|
||||
DROP SCHEMA IF EXISTS biz CASCADE;
|
||||
DROP SCHEMA IF EXISTS auth CASCADE;
|
||||
```
|
||||
|
||||
注意:`DROP SCHEMA CASCADE` 会级联删除 Schema 内所有表和依赖对象。如果 `auth`/`biz` 中已有业务表,需先备份数据再执行回滚。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 auth 和 biz Schema 存在
|
||||
SELECT schema_name
|
||||
FROM information_schema.schemata
|
||||
WHERE schema_name IN ('auth', 'biz')
|
||||
ORDER BY schema_name;
|
||||
|
||||
-- 2. 验证 app_user 对 auth Schema 有 USAGE 权限
|
||||
SELECT has_schema_privilege('app_user', 'auth', 'USAGE') AS auth_usage,
|
||||
has_schema_privilege('app_user', 'biz', 'USAGE') AS biz_usage;
|
||||
|
||||
-- 3. 验证 ALTER DEFAULT PRIVILEGES 已设置(查询 pg_default_acl)
|
||||
SELECT n.nspname AS schema_name,
|
||||
d.defaclacl AS default_acl
|
||||
FROM pg_default_acl d
|
||||
JOIN pg_namespace n ON n.oid = d.defaclnamespace
|
||||
WHERE n.nspname IN ('auth', 'biz');
|
||||
|
||||
-- 4. 验证 public Schema 中现有表未受影响
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'public'
|
||||
ORDER BY table_name;
|
||||
```
|
||||
169
docs/database/BD_Manual_auth_tables.md
Normal file
169
docs/database/BD_Manual_auth_tables.md
Normal file
@@ -0,0 +1,169 @@
|
||||
# BD_Manual:auth Schema 认证业务表
|
||||
|
||||
> 目标库:`test_zqyy_app`(通过 `APP_DB_DSN` 连接)
|
||||
> 迁移脚本:
|
||||
> - `db/zqyy_app/migrations/2026-02-25__p3_create_auth_tables.sql`(建表)
|
||||
> - `db/zqyy_app/migrations/2026-02-25__p3_seed_roles_permissions.sql`(种子数据)
|
||||
> 关联 SPEC:`miniapp-auth-system`(P3 小程序用户认证系统)
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 新增表(8 张)
|
||||
|
||||
| # | 表名 | 用途 | 主要字段 |
|
||||
|---|------|------|---------|
|
||||
| 1 | `auth.users` | 微信用户主表 | `id`(PK), `wx_openid`(UK), `wx_union_id`, `wx_avatar_url`, `nickname`, `phone`, `status`(默认 `pending`), `created_at`, `updated_at` |
|
||||
| 2 | `auth.user_applications` | 用户入驻申请表 | `id`(PK), `user_id`(FK→users), `site_code`, `site_id`, `applied_role_text`, `employee_number`, `phone`, `status`(默认 `pending`), `reviewer_id`, `review_note`, `created_at`, `reviewed_at` |
|
||||
| 3 | `auth.site_code_mapping` | 球房ID与门店映射表 | `id`(PK), `site_code`(UK), `site_id`(UK), `site_name`, `tenant_id`, `created_at` |
|
||||
| 4 | `auth.roles` | 角色定义表 | `id`(PK), `code`(UK), `name`, `description`, `created_at` |
|
||||
| 5 | `auth.permissions` | 权限定义表 | `id`(PK), `code`(UK), `name`, `description`, `created_at` |
|
||||
| 6 | `auth.role_permissions` | 角色-权限关联表 | `role_id`(FK→roles), `permission_id`(FK→permissions),联合主键 |
|
||||
| 7 | `auth.user_site_roles` | 用户-门店-角色关联表 | `id`(PK), `user_id`(FK→users), `site_id`, `role_id`(FK→roles), `created_at`,`(user_id, site_id, role_id)` 唯一约束 |
|
||||
| 8 | `auth.user_assistant_binding` | 用户-人员绑定表 | `id`(PK), `user_id`(FK→users), `site_id`, `assistant_id`(可空), `staff_id`(可空), `binding_type`, `created_at` |
|
||||
|
||||
### 约束与索引
|
||||
|
||||
| 表 | 约束/索引名 | 类型 | 说明 |
|
||||
|----|-----------|------|------|
|
||||
| `users` | `uq_users_wx_openid` | UNIQUE | 微信 openid 唯一 |
|
||||
| `users` | `ix_users_wx_openid` | INDEX | openid 查询加速 |
|
||||
| `site_code_mapping` | `uq_site_code_mapping_site_code` | UNIQUE | 球房ID 唯一 |
|
||||
| `site_code_mapping` | `uq_site_code_mapping_site_id` | UNIQUE | site_id 唯一映射 |
|
||||
| `site_code_mapping` | `ix_site_code_mapping_site_code` | INDEX | site_code 查询加速 |
|
||||
| `roles` | `uq_roles_code` | UNIQUE | 角色 code 唯一 |
|
||||
| `permissions` | `uq_permissions_code` | UNIQUE | 权限 code 唯一 |
|
||||
| `role_permissions` | PK `(role_id, permission_id)` | PRIMARY KEY | 联合主键 |
|
||||
| `role_permissions` | `fk_role_permissions_role_id` | FK | → `auth.roles(id)` CASCADE |
|
||||
| `role_permissions` | `fk_role_permissions_permission_id` | FK | → `auth.permissions(id)` CASCADE |
|
||||
| `user_applications` | `fk_user_applications_user_id` | FK | → `auth.users(id)` CASCADE |
|
||||
| `user_applications` | `ix_user_applications_user_id` | INDEX | user_id 查询加速 |
|
||||
| `user_applications` | `ix_user_applications_status` | INDEX | status 过滤加速 |
|
||||
| `user_site_roles` | `uq_user_site_roles_user_site_role` | UNIQUE | 防止重复分配 |
|
||||
| `user_site_roles` | `fk_user_site_roles_user_id` | FK | → `auth.users(id)` CASCADE |
|
||||
| `user_site_roles` | `fk_user_site_roles_role_id` | FK | → `auth.roles(id)` CASCADE |
|
||||
| `user_site_roles` | `ix_user_site_roles_user_site` | INDEX | (user_id, site_id) 查询加速 |
|
||||
| `user_assistant_binding` | `fk_user_assistant_binding_user_id` | FK | → `auth.users(id)` CASCADE |
|
||||
|
||||
|
||||
### 种子数据
|
||||
|
||||
#### 权限(5 条)
|
||||
|
||||
| code | name | description |
|
||||
|------|------|-------------|
|
||||
| `view_tasks` | 查看任务 | 允许查看任务列表和任务详情 |
|
||||
| `view_board` | 查看看板 | 允许查看数据看板概览 |
|
||||
| `view_board_finance` | 查看财务看板 | 允许查看财务相关的数据看板 |
|
||||
| `view_board_customer` | 查看客户看板 | 允许查看客户相关的数据看板 |
|
||||
| `view_board_coach` | 查看助教看板 | 允许查看助教相关的数据看板 |
|
||||
|
||||
#### 角色(4 条)
|
||||
|
||||
| code | name | description |
|
||||
|------|------|-------------|
|
||||
| `coach` | 助教 | 球房助教,可查看任务和助教看板 |
|
||||
| `staff` | 员工 | 球房员工,可查看任务和数据看板 |
|
||||
| `site_admin` | 店铺管理员 | 单店管理员,可查看所有看板 |
|
||||
| `tenant_admin` | 租户管理员 | 租户级管理员,拥有全部权限 |
|
||||
|
||||
#### 角色-权限映射(14 条)
|
||||
|
||||
| 角色 | 权限列表 | 权限数 |
|
||||
|------|---------|--------|
|
||||
| `coach` | `view_tasks`, `view_board_coach` | 2 |
|
||||
| `staff` | `view_tasks`, `view_board` | 2 |
|
||||
| `site_admin` | `view_tasks`, `view_board`, `view_board_finance`, `view_board_customer`, `view_board_coach` | 5 |
|
||||
| `tenant_admin` | `view_tasks`, `view_board`, `view_board_finance`, `view_board_customer`, `view_board_coach` | 5 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL 任务 | 无影响。本次变更仅操作业务库 `auth` Schema,不涉及 ETL 库 |
|
||||
| 后端 API | 直接依赖。FastAPI 后端将基于这些表实现微信登录、用户申请、审核、权限中间件等认证功能 |
|
||||
| 小程序 | 间接依赖。小程序通过后端 API 间接使用这些表(登录、申请、状态查询) |
|
||||
| 管理后台 | 间接依赖。管理端通过后端 API 进行申请审核操作 |
|
||||
| FDW 配置 | 无影响。`fdw_etl` Schema 独立于 `auth`,但人员匹配服务会通过 FDW 查询 ETL 库的助教/员工表 |
|
||||
| `public` Schema | 无影响。脚本不包含任何对 `public` 的操作 |
|
||||
| 现有 `auth` Schema | 兼容。`auth` Schema 已由 P1 迁移脚本创建,本次仅在其中新增表,不修改已有对象 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
按逆序 `DROP TABLE IF EXISTS CASCADE`(迁移脚本末尾已包含注释形式的回滚语句):
|
||||
|
||||
```sql
|
||||
-- 先删除种子数据(如需保留表结构)
|
||||
DELETE FROM auth.role_permissions
|
||||
WHERE role_id IN (SELECT id FROM auth.roles WHERE code IN ('coach', 'staff', 'site_admin', 'tenant_admin'))
|
||||
AND permission_id IN (SELECT id FROM auth.permissions WHERE code IN ('view_tasks', 'view_board', 'view_board_finance', 'view_board_customer', 'view_board_coach'));
|
||||
|
||||
DELETE FROM auth.roles WHERE code IN ('coach', 'staff', 'site_admin', 'tenant_admin');
|
||||
DELETE FROM auth.permissions WHERE code IN ('view_tasks', 'view_board', 'view_board_finance', 'view_board_customer', 'view_board_coach');
|
||||
|
||||
-- 删除表(按逆序,CASCADE 处理外键依赖)
|
||||
DROP TABLE IF EXISTS auth.user_assistant_binding CASCADE;
|
||||
DROP TABLE IF EXISTS auth.user_site_roles CASCADE;
|
||||
DROP TABLE IF EXISTS auth.user_applications CASCADE;
|
||||
DROP TABLE IF EXISTS auth.role_permissions CASCADE;
|
||||
DROP TABLE IF EXISTS auth.permissions CASCADE;
|
||||
DROP TABLE IF EXISTS auth.roles CASCADE;
|
||||
DROP TABLE IF EXISTS auth.site_code_mapping CASCADE;
|
||||
DROP TABLE IF EXISTS auth.users CASCADE;
|
||||
```
|
||||
|
||||
注意:`CASCADE` 会级联删除依赖对象。如果表中已有业务数据,需先备份再执行回滚。
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 auth Schema 下 8 张认证表全部存在
|
||||
SELECT table_name
|
||||
FROM information_schema.tables
|
||||
WHERE table_schema = 'auth'
|
||||
AND table_name IN (
|
||||
'users', 'user_applications', 'site_code_mapping',
|
||||
'roles', 'permissions', 'role_permissions',
|
||||
'user_site_roles', 'user_assistant_binding'
|
||||
)
|
||||
ORDER BY table_name;
|
||||
-- 预期:返回 8 行
|
||||
|
||||
-- 2. 验证种子数据:5 条权限
|
||||
SELECT COUNT(*) AS perm_count
|
||||
FROM auth.permissions
|
||||
WHERE code IN ('view_tasks', 'view_board', 'view_board_finance', 'view_board_customer', 'view_board_coach');
|
||||
-- 预期:5
|
||||
|
||||
-- 3. 验证种子数据:4 条角色
|
||||
SELECT COUNT(*) AS role_count
|
||||
FROM auth.roles
|
||||
WHERE code IN ('coach', 'staff', 'site_admin', 'tenant_admin');
|
||||
-- 预期:4
|
||||
|
||||
-- 4. 验证角色-权限映射数量
|
||||
SELECT r.code AS role_code, COUNT(rp.permission_id) AS perm_count
|
||||
FROM auth.roles r
|
||||
JOIN auth.role_permissions rp ON r.id = rp.role_id
|
||||
GROUP BY r.code
|
||||
ORDER BY r.code;
|
||||
-- 预期:coach=2, site_admin=5, staff=2, tenant_admin=5(共 14 条映射)
|
||||
|
||||
-- 5. 验证关键约束存在
|
||||
SELECT conname, contype
|
||||
FROM pg_constraint
|
||||
WHERE conrelid IN (
|
||||
'auth.users'::regclass,
|
||||
'auth.site_code_mapping'::regclass,
|
||||
'auth.user_site_roles'::regclass
|
||||
)
|
||||
ORDER BY conrelid::regclass::text, conname;
|
||||
-- 预期:包含 uq_users_wx_openid、uq_site_code_mapping_site_code、uq_site_code_mapping_site_id、uq_user_site_roles_user_site_role 等
|
||||
```
|
||||
266
docs/database/BD_Manual_dws_assistant_order_contribution.md
Normal file
266
docs/database/BD_Manual_dws_assistant_order_contribution.md
Normal file
@@ -0,0 +1,266 @@
|
||||
# BD_Manual:dws_assistant_order_contribution(助教订单流水四项统计)
|
||||
|
||||
> DWS 表:`dws.dws_assistant_order_contribution`
|
||||
> DWD 数据源:`dwd.dwd_settlement_head`(结算主表)、`dwd.dwd_table_fee_log`(台费明细)、`dwd.dwd_assistant_service_log`(助教服务记录)
|
||||
> 任务代码:`DWS_ASSISTANT_ORDER_CONTRIBUTION`
|
||||
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dws/assistant_order_contribution_task.py`
|
||||
> DDL 位置:`docs/database/ddl/etl_feiqiu__dws.sql`
|
||||
> 迁移脚本:`db/etl_feiqiu/migrations/2025-02-24__create_dws_assistant_order_contribution.sql`
|
||||
> RLS 视图:`db/etl_feiqiu/migrations/2025-02-24__create_rls_view_assistant_order_contribution.sql`
|
||||
> FDW 映射:`db/zqyy_app/migrations/2025-02-24__add_fdw_dws_extensions.sql`
|
||||
|
||||
---
|
||||
|
||||
## 1. 表结构
|
||||
|
||||
| 列名 | 类型 | 默认值 | 业务含义 | 取值范围/示例 |
|
||||
|------|------|--------|---------|-------------|
|
||||
| `contribution_id` | BIGINT (SERIAL) | nextval 序列 | 自增主键(PK) | 自增 |
|
||||
| `site_id` | INTEGER NOT NULL | — | 门店 ID | 飞球门店 ID |
|
||||
| `tenant_id` | INTEGER NOT NULL | — | 租户 ID | 飞球租户 ID |
|
||||
| `assistant_id` | BIGINT NOT NULL | — | 助教 ID | 飞球助教 ID |
|
||||
| `assistant_nickname` | VARCHAR(100) | NULL | 助教昵称 | 中文昵称 |
|
||||
| `stat_date` | DATE NOT NULL | — | 统计日期 | `2025-01-15` |
|
||||
| `order_gross_revenue` | NUMERIC(14,2) | 0 | 订单总流水 = 台费 + 酒水食品 + 所有助教服务费 | `0.00` ~ 金额值 |
|
||||
| `order_net_revenue` | NUMERIC(14,2) | 0 | 订单净流水 = 订单总流水 - 所有助教服务分成 | `0.00` ~ 金额值 |
|
||||
| `time_weighted_revenue` | NUMERIC(14,2) | 0 | 时效贡献流水 = 台费按时长分摊 + 个人服务费 + 酒水食品按时长比例 | `0.00` ~ 金额值 |
|
||||
| `time_weighted_net_revenue` | NUMERIC(14,2) | 0 | 时效净贡献 = 时效贡献流水 - 个人服务分成 | `0.00` ~ 金额值 |
|
||||
| `order_count` | INTEGER | 0 | 当日参与订单数 | `0` ~ 正整数 |
|
||||
| `total_service_seconds` | INTEGER | 0 | 当日总服务时长(秒) | `0` ~ 正整数 |
|
||||
| `created_at` | TIMESTAMPTZ | NOW() | 记录创建时间 | ISO 时间戳 |
|
||||
| `updated_at` | TIMESTAMPTZ | NOW() | 记录最后更新时间 | ISO 时间戳 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 主键与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|----|------|
|
||||
| `dws_assistant_order_contribution_pkey` | PRIMARY KEY | `contribution_id` | 物理主键(自增序列) |
|
||||
| `idx_aoc_site_assistant_date` | UNIQUE INDEX | `(site_id, assistant_id, stat_date)` | 业务主键:每个门店每个助教每天唯一一条记录 |
|
||||
| `idx_aoc_stat_date` | INDEX | `(site_id, stat_date)` | 按门店+日期查询,支持日度报表 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据写入策略
|
||||
|
||||
- **delete-before-insert**:每次执行按 `site_id` + 日期窗口全量刷新
|
||||
1. `DELETE FROM dws.dws_assistant_order_contribution WHERE site_id = %s AND stat_date BETWEEN %s AND %s`
|
||||
2. 批量 `INSERT` 新计算结果
|
||||
- 继承 `BaseDwsTask` 默认 load 实现,幂等可重跑
|
||||
|
||||
---
|
||||
|
||||
## 4. 算法概要
|
||||
|
||||
### 4.1 数据来源
|
||||
|
||||
| 来源表 | 筛选条件 | 提取内容 |
|
||||
|--------|---------|---------|
|
||||
| `dwd.dwd_settlement_head` | 日期窗口内,`settle_type IN (1, 3)` | 结算单信息、酒水食品金额 |
|
||||
| `dwd.dwd_table_fee_log` | 关联结算单 | 台桌使用时长、台费金额、区域 |
|
||||
| `dwd.dwd_assistant_service_log` | 关联结算单 | 助教服务时长、服务流水、分成、课程类型 |
|
||||
|
||||
### 4.2 四项统计公式
|
||||
|
||||
**订单总流水(order_gross_revenue)**
|
||||
```
|
||||
order_gross_revenue = total_table_fee + total_goods_amount + SUM(所有助教 ledger_amount)
|
||||
```
|
||||
每个参与助教获得相同值。
|
||||
|
||||
**订单净流水(order_net_revenue)**
|
||||
```
|
||||
order_net_revenue = order_gross_revenue - SUM(所有助教 commission)
|
||||
```
|
||||
每个参与助教获得相同值。
|
||||
|
||||
**时效贡献流水(time_weighted_revenue)**
|
||||
```
|
||||
对于台桌 t:
|
||||
billable_seconds = MAX(SUM(助教服务时长), 台桌使用时长)
|
||||
台费分摊_a = table_fee_t × (service_seconds_a / billable_seconds)
|
||||
|
||||
酒水食品分摊_a = total_goods_amount × (助教 a 总服务时长 / 所有助教总服务时长)
|
||||
|
||||
time_weighted_revenue_a = SUM(各台桌台费分摊_a) + ledger_amount_a + 酒水食品分摊_a
|
||||
```
|
||||
|
||||
**时效净贡献(time_weighted_net_revenue)**
|
||||
```
|
||||
time_weighted_net_revenue_a = time_weighted_revenue_a - commission_a
|
||||
```
|
||||
|
||||
### 4.3 超休/打赏课特殊处理
|
||||
|
||||
当 `course_type = BONUS` 时,四项统计均等于个人服务流水和分成,不参与订单级分摊。
|
||||
|
||||
---
|
||||
|
||||
## 5. 前置依赖
|
||||
|
||||
- 任务依赖:`DWD_LOAD_FROM_ODS`(需先完成 DWD 层数据加载)
|
||||
- 数据源表:`dwd.dwd_settlement_head`、`dwd.dwd_table_fee_log`、`dwd.dwd_assistant_service_log` 必须已有数据
|
||||
|
||||
---
|
||||
|
||||
## 6. 验证 SQL
|
||||
|
||||
### 6.1 检查表是否存在且有数据
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) AS total_rows,
|
||||
COUNT(DISTINCT site_id) AS site_count,
|
||||
COUNT(DISTINCT assistant_id) AS assistant_count,
|
||||
MIN(stat_date) AS earliest_date,
|
||||
MAX(stat_date) AS latest_date
|
||||
FROM dws.dws_assistant_order_contribution;
|
||||
```
|
||||
|
||||
### 6.2 检查业务主键唯一性(不应有重复)
|
||||
|
||||
```sql
|
||||
SELECT site_id, assistant_id, stat_date, COUNT(*) AS cnt
|
||||
FROM dws.dws_assistant_order_contribution
|
||||
GROUP BY site_id, assistant_id, stat_date
|
||||
HAVING COUNT(*) > 1;
|
||||
-- 预期:无结果返回
|
||||
```
|
||||
|
||||
### 6.3 检查四项统计数值合理性(非负)
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE order_gross_revenue < 0) AS neg_gross,
|
||||
COUNT(*) FILTER (WHERE order_net_revenue < 0) AS neg_net,
|
||||
COUNT(*) FILTER (WHERE time_weighted_revenue < 0) AS neg_twr,
|
||||
COUNT(*) FILTER (WHERE time_weighted_net_revenue < 0) AS neg_twnr
|
||||
FROM dws.dws_assistant_order_contribution;
|
||||
-- 预期:所有列均为 0
|
||||
```
|
||||
|
||||
### 6.4 按门店查看统计概况
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
site_id,
|
||||
COUNT(*) AS record_count,
|
||||
SUM(order_count) AS total_orders,
|
||||
ROUND(AVG(order_gross_revenue), 2) AS avg_gross,
|
||||
ROUND(AVG(order_net_revenue), 2) AS avg_net,
|
||||
ROUND(AVG(time_weighted_revenue), 2) AS avg_twr,
|
||||
ROUND(AVG(time_weighted_net_revenue), 2) AS avg_twnr
|
||||
FROM dws.dws_assistant_order_contribution
|
||||
GROUP BY site_id
|
||||
ORDER BY site_id;
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. RLS 视图与 FDW 映射
|
||||
|
||||
### 7.1 RLS 视图(ETL 库 app schema)
|
||||
|
||||
```sql
|
||||
-- 视图名:app.v_dws_assistant_order_contribution
|
||||
CREATE OR REPLACE VIEW app.v_dws_assistant_order_contribution AS
|
||||
SELECT * FROM dws.dws_assistant_order_contribution
|
||||
WHERE site_id = current_setting('app.current_site_id')::bigint;
|
||||
```
|
||||
|
||||
### 7.2 FDW 外部表(业务库 fdw_etl schema)
|
||||
|
||||
```sql
|
||||
-- 外部表名:fdw_etl.v_dws_assistant_order_contribution
|
||||
-- 通过 app schema RLS 视图访问,非直接访问 dws schema
|
||||
CREATE FOREIGN TABLE fdw_etl.v_dws_assistant_order_contribution (...)
|
||||
SERVER etl_server
|
||||
OPTIONS (schema_name 'app', table_name 'v_dws_assistant_order_contribution');
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 兼容性说明
|
||||
|
||||
| 影响范围 | 说明 |
|
||||
|---------|------|
|
||||
| ETL 任务 | 新增任务 `DWS_ASSISTANT_ORDER_CONTRIBUTION`,依赖 `DWD_LOAD_FROM_ODS`。不影响现有 DWS 任务 |
|
||||
| 后端 API | 当前无 API 直接读取此表。后续小程序助教看板需新增接口 |
|
||||
| 管理后台 | 当前无前端页面展示。后续可在助教详情页新增流水统计展示 |
|
||||
| 小程序 | 小程序助教端将通过后端 API 读取此表数据展示四项统计 |
|
||||
| 其他 DWS 表 | 独立于现有 `dws_assistant_daily_detail`,不修改任何已有表或任务逻辑 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 回滚策略
|
||||
|
||||
### 9.1 删除数据(保留表结构)
|
||||
|
||||
```sql
|
||||
DELETE FROM dws.dws_assistant_order_contribution;
|
||||
```
|
||||
|
||||
### 9.2 完整回滚(删除表 + 视图 + FDW)
|
||||
|
||||
```sql
|
||||
-- 1. 删除 FDW 外部表(业务库)
|
||||
DROP FOREIGN TABLE IF EXISTS fdw_etl.v_dws_assistant_order_contribution;
|
||||
|
||||
-- 2. 删除 RLS 视图(ETL 库)
|
||||
DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution;
|
||||
|
||||
-- 3. 删除表和索引(ETL 库)
|
||||
DROP INDEX IF EXISTS dws.idx_aoc_stat_date;
|
||||
DROP INDEX IF EXISTS dws.idx_aoc_site_assistant_date;
|
||||
DROP TABLE IF EXISTS dws.dws_assistant_order_contribution;
|
||||
```
|
||||
|
||||
### 9.3 回滚任务注册
|
||||
|
||||
从 `orchestration/task_registry.py` 中移除 `DWS_ASSISTANT_ORDER_CONTRIBUTION` 注册行,并从 `tasks/dws/__init__.py` 中移除 `AssistantOrderContributionTask` 导出。
|
||||
|
||||
---
|
||||
|
||||
## 10. 代码引用
|
||||
|
||||
- 任务类:`tasks/dws/assistant_order_contribution_task.py` → `AssistantOrderContributionTask`
|
||||
- 数据结构:`TableUsage`、`AssistantService`、`OrderData`(同文件)
|
||||
- 继承:`BaseDwsTask`
|
||||
- 任务注册:`orchestration/task_registry.py` → `DWS_ASSISTANT_ORDER_CONTRIBUTION`
|
||||
- 属性测试:`tests/test_dws_contribution_properties.py`
|
||||
- 迁移脚本:`db/etl_feiqiu/migrations/2025-02-24__create_dws_assistant_order_contribution.sql`
|
||||
- RLS 视图:`db/etl_feiqiu/migrations/2025-02-24__create_rls_view_assistant_order_contribution.sql`
|
||||
- FDW 映射:`db/zqyy_app/migrations/2025-02-24__add_fdw_dws_extensions.sql`
|
||||
- 验证脚本:`apps/etl/connectors/feiqiu/scripts/verify_dws_extensions.py`
|
||||
|
||||
---
|
||||
|
||||
## 11. 关联扩展字段说明
|
||||
|
||||
本次 Spec(02-etl-dws-miniapp-extensions)同时扩展了两张已有表的字段,简要说明如下:
|
||||
|
||||
### 11.1 dws_member_consumption_summary 新增字段
|
||||
|
||||
| 列名 | 类型 | 默认值 | 业务含义 |
|
||||
|------|------|--------|---------|
|
||||
| `recharge_count_30d` | INTEGER | 0 | 近 30 天充值次数 |
|
||||
| `recharge_count_60d` | INTEGER | 0 | 近 60 天充值次数 |
|
||||
| `recharge_count_90d` | INTEGER | 0 | 近 90 天充值次数 |
|
||||
| `recharge_amount_30d` | NUMERIC(14,2) | 0 | 近 30 天充值金额 |
|
||||
| `recharge_amount_60d` | NUMERIC(14,2) | 0 | 近 60 天充值金额 |
|
||||
| `recharge_amount_90d` | NUMERIC(14,2) | 0 | 近 90 天充值金额 |
|
||||
| `avg_ticket_amount` | NUMERIC(14,2) | 0 | 次均消费 = total_consume_amount / MAX(total_visit_count, 1) |
|
||||
|
||||
迁移脚本:`db/etl_feiqiu/migrations/2025-02-24__alter_member_consumption_add_recharge_fields.sql`
|
||||
|
||||
### 11.2 dws_assistant_daily_detail 新增字段
|
||||
|
||||
| 列名 | 类型 | 默认值 | 业务含义 |
|
||||
|------|------|--------|---------|
|
||||
| `penalty_minutes` | NUMERIC(10,2) | 0 | 定档折算惩罚分钟数,无惩罚时为 0 |
|
||||
| `penalty_reason` | TEXT | NULL | 惩罚原因描述,无惩罚时为 NULL |
|
||||
| `is_exempt` | BOOLEAN | FALSE | 是否豁免惩罚 |
|
||||
| `per_hour_contribution` | NUMERIC(14,2) | NULL | 单人每小时贡献流水 = 台费每小时单价 / 助教人数 |
|
||||
|
||||
迁移脚本:`db/etl_feiqiu/migrations/2025-02-24__alter_assistant_daily_add_penalty_fields.sql`
|
||||
251
docs/database/BD_Manual_dws_member_spending_power_index.md
Normal file
251
docs/database/BD_Manual_dws_member_spending_power_index.md
Normal file
@@ -0,0 +1,251 @@
|
||||
# BD_Manual:dws_member_spending_power_index(SPI 消费力指数)
|
||||
|
||||
> DWS 表:`dws.dws_member_spending_power_index`
|
||||
> DWD 数据源:`dwd.dwd_settlement_head`(消费订单)、`dwd.dwd_recharge_order`(充值订单)
|
||||
> 配置表:`dws.cfg_index_parameters`(`index_type='SPI'`)
|
||||
> 任务代码:`DWS_SPENDING_POWER_INDEX`
|
||||
> 代码位置:`apps/etl/connectors/feiqiu/tasks/dws/index/spending_power_index_task.py`
|
||||
> DDL 位置:`docs/database/ddl/etl_feiqiu__dws.sql`
|
||||
> 迁移脚本:`db/etl_feiqiu/migrations/2026-02-23_create_dws_member_spending_power_index.sql`
|
||||
> 种子数据:`db/etl_feiqiu/seeds/seed_index_parameters.sql`(`index_type='SPI'` 部分)
|
||||
|
||||
---
|
||||
|
||||
## 1. 表结构
|
||||
|
||||
| 列名 | 类型 | 默认值 | 业务含义 | 取值范围/示例 |
|
||||
|------|------|--------|---------|-------------|
|
||||
| `spi_id` | BIGINT (SERIAL) | nextval 序列 | 自增主键(PK) | 自增 |
|
||||
| `site_id` | INTEGER NOT NULL | — | 门店 ID | 飞球门店 ID |
|
||||
| `member_id` | BIGINT NOT NULL | — | 会员 ID | 飞球会员 ID |
|
||||
| `spend_30` | NUMERIC(14,2) | 0 | 近 30 天消费总额(元) | `0.00` ~ 金额值 |
|
||||
| `spend_90` | NUMERIC(14,2) | 0 | 近 90 天消费总额(元) | `0.00` ~ 金额值 |
|
||||
| `recharge_90` | NUMERIC(14,2) | 0 | 近 90 天充值总额(元) | `0.00` ~ 金额值 |
|
||||
| `orders_30` | INTEGER | 0 | 近 30 天消费笔数 | `0` ~ 正整数 |
|
||||
| `orders_90` | INTEGER | 0 | 近 90 天消费笔数 | `0` ~ 正整数 |
|
||||
| `visit_days_30` | INTEGER | 0 | 近 30 天消费日数(按天去重) | `0` ~ `30` |
|
||||
| `visit_days_90` | INTEGER | 0 | 近 90 天消费日数(按天去重) | `0` ~ `90` |
|
||||
| `avg_ticket_90` | NUMERIC(14,2) | 0 | 90 天客单价(= spend_90 / max(orders_90, 1)) | `0.00` ~ 金额值 |
|
||||
| `active_weeks_90` | INTEGER | 0 | 近 90 天有消费的自然周数 | `0` ~ `13` |
|
||||
| `daily_spend_ewma_90` | NUMERIC(14,2) | 0 | 日消费 EWMA(指数加权移动平均) | `0.00` ~ 金额值 |
|
||||
| `score_level_raw` | NUMERIC(10,4) | 0 | Level 子分原始分(消费水平) | ≥ 0 |
|
||||
| `score_speed_raw` | NUMERIC(10,4) | 0 | Speed 子分原始分(消费速度) | ≥ 0 |
|
||||
| `score_stability_raw` | NUMERIC(10,4) | 0 | Stability 子分原始分(消费稳定性) | `0.0000` ~ `1.0000` |
|
||||
| `score_level_display` | NUMERIC(5,2) | 0 | Level 子分展示分 | `0.00` ~ `10.00` |
|
||||
| `score_speed_display` | NUMERIC(5,2) | 0 | Speed 子分展示分 | `0.00` ~ `10.00` |
|
||||
| `score_stability_display` | NUMERIC(5,2) | 0 | Stability 子分展示分 | `0.00` ~ `10.00` |
|
||||
| `raw_score` | NUMERIC(10,4) | 0 | SPI 总分原始分(加权合成) | ≥ 0 |
|
||||
| `display_score` | NUMERIC(5,2) | 0 | SPI 总分展示分 | `0.00` ~ `10.00` |
|
||||
| `calc_time` | TIMESTAMPTZ | NOW() | 本次计算时间 | ISO 时间戳 |
|
||||
| `created_at` | TIMESTAMPTZ | NOW() | 记录创建时间 | ISO 时间戳 |
|
||||
| `updated_at` | TIMESTAMPTZ | NOW() | 记录最后更新时间 | ISO 时间戳 |
|
||||
|
||||
---
|
||||
|
||||
## 2. 主键与索引
|
||||
|
||||
| 名称 | 类型 | 列 | 说明 |
|
||||
|------|------|----|------|
|
||||
| `dws_member_spending_power_index_pkey` | PRIMARY KEY | `spi_id` | 物理主键(自增序列) |
|
||||
| `idx_spi_site_member` | UNIQUE INDEX | `(site_id, member_id)` | 业务主键:每个门店每个会员唯一一条记录 |
|
||||
| `idx_spi_display_score` | INDEX | `(site_id, display_score DESC)` | 按门店查询展示分排名,支持 TOP-N 查询 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 数据写入策略
|
||||
|
||||
- **delete-before-insert**:每次执行按 `site_id` 全量刷新
|
||||
1. `DELETE FROM dws.dws_member_spending_power_index WHERE site_id = %s`
|
||||
2. 批量 `INSERT` 新计算结果
|
||||
- 无数据时跳过(不删除、不插入),返回 `{'status': 'skipped', 'reason': 'no_data'}`
|
||||
|
||||
---
|
||||
|
||||
## 4. 算法概要
|
||||
|
||||
### 4.1 数据来源
|
||||
|
||||
| 来源表 | 筛选条件 | 提取内容 |
|
||||
|--------|---------|---------|
|
||||
| `dwd.dwd_settlement_head` | 近 90 天,`settle_type IN (1, 3)` | 消费金额、笔数、消费日数、周覆盖、日消费序列 |
|
||||
| `dwd.dwd_recharge_order` | 近 90 天,`settle_type = 5` | 充值总额 |
|
||||
|
||||
### 4.2 子分公式
|
||||
|
||||
- **Level**(消费水平,权重 0.60):
|
||||
`L = w_s30 × ln(1 + spend_30/M30) + w_s90 × ln(1 + spend_90/M90) + w_ticket × ln(1 + avg_ticket_90/T0) + w_r90 × ln(1 + recharge_90/R90)`
|
||||
|
||||
- **Speed**(消费速度,权重 0.30):
|
||||
`S = w_abs × V_abs + w_rel × max(0, V_rel) + w_ewma × V_ewma`
|
||||
- `V_abs = ln(1 + spend_30 / (max(visit_days_30, 1) × V0))`
|
||||
- `V_rel = ln((v_30 + ε) / (v_90 + ε))`,仅加速加分
|
||||
- `V_ewma = ln(1 + daily_spend_ewma_90 / E0)`
|
||||
|
||||
- **Stability**(消费稳定性,权重 0.10):
|
||||
`P = active_weeks_90 / 13`,取值 [0, 1]
|
||||
|
||||
### 4.3 总分合成
|
||||
|
||||
`SPI_raw = w_L × L + w_S × S + w_P × P`(默认 0.60 / 0.30 / 0.10)
|
||||
|
||||
### 4.4 展示分映射
|
||||
|
||||
Raw → Winsorize(P5, P95) → 可选压缩(log1p/asinh) → MinMax [0, 10] → 可选 EWMA 平滑
|
||||
|
||||
子分(Level/Speed/Stability)各自独立映射,使用 `SPI_LEVEL` / `SPI_SPEED` / `SPI_STABILITY` 隔离分位历史。
|
||||
|
||||
---
|
||||
|
||||
## 5. 配置参数
|
||||
|
||||
所有参数存储在 `dws.cfg_index_parameters`(`index_type='SPI'`),缺失时回退到代码中 `DEFAULT_PARAMS`。
|
||||
|
||||
| 参数名 | 默认值 | 说明 |
|
||||
|--------|--------|------|
|
||||
| `spend_window_short_days` | 30 | 短窗口天数 |
|
||||
| `spend_window_long_days` | 90 | 长窗口天数 |
|
||||
| `ewma_alpha_daily_spend` | 0.3 | 日消费 EWMA 平滑系数 |
|
||||
| `amount_base_spend_30` | 500.0 | 30 天消费金额压缩基数 |
|
||||
| `amount_base_spend_90` | 1500.0 | 90 天消费金额压缩基数 |
|
||||
| `amount_base_ticket_90` | 200.0 | 客单价压缩基数 |
|
||||
| `amount_base_recharge_90` | 1000.0 | 充值金额压缩基数 |
|
||||
| `amount_base_speed_abs` | 100.0 | 绝对速度压缩基数 |
|
||||
| `amount_base_ewma_90` | 50.0 | EWMA 速度压缩基数 |
|
||||
| `w_level_spend_30` | 0.30 | Level 子分中 spend_30 权重 |
|
||||
| `w_level_spend_90` | 0.35 | Level 子分中 spend_90 权重 |
|
||||
| `w_level_ticket_90` | 0.20 | Level 子分中 avg_ticket_90 权重 |
|
||||
| `w_level_recharge_90` | 0.15 | Level 子分中 recharge_90 权重 |
|
||||
| `w_speed_abs` | 0.50 | Speed 子分中绝对速度权重 |
|
||||
| `w_speed_rel` | 0.30 | Speed 子分中相对速度权重 |
|
||||
| `w_speed_ewma` | 0.20 | Speed 子分中 EWMA 速度权重 |
|
||||
| `weight_level` | 0.60 | 总分中 Level 权重 |
|
||||
| `weight_speed` | 0.30 | 总分中 Speed 权重 |
|
||||
| `weight_stability` | 0.10 | 总分中 Stability 权重 |
|
||||
| `stability_window_days` | 90 | 稳定性计算窗口 |
|
||||
| `use_stability` | 1 | 是否启用稳定性子分(0=禁用) |
|
||||
| `percentile_lower` | 5 | Winsorize 下分位 |
|
||||
| `percentile_upper` | 95 | Winsorize 上分位 |
|
||||
| `compression_mode` | 1 | 压缩模式:0=无,1=log1p,2=asinh |
|
||||
| `use_smoothing` | 1 | 是否启用 EWMA 分位平滑 |
|
||||
| `ewma_alpha` | 0.2 | 分位平滑 EWMA 系数 |
|
||||
| `speed_epsilon` | 1e-6 | 速度计算防除零小量 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 前置依赖
|
||||
|
||||
- 任务依赖:`DWS_MEMBER_CONSUMPTION`(需先完成会员消费汇总)
|
||||
- 数据源表:`dwd.dwd_settlement_head`、`dwd.dwd_recharge_order` 必须已有数据
|
||||
- 配置表:`dws.cfg_index_parameters` 中 `index_type='SPI'` 种子数据已插入(缺失时使用默认值)
|
||||
- 分位历史表:`dws.dws_index_percentile_history`(首次执行时无历史,不平滑)
|
||||
|
||||
---
|
||||
|
||||
## 7. 验证 SQL
|
||||
|
||||
### 7.1 检查表是否存在且有数据
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) AS total_rows,
|
||||
COUNT(DISTINCT site_id) AS site_count,
|
||||
MIN(calc_time) AS earliest_calc,
|
||||
MAX(calc_time) AS latest_calc
|
||||
FROM dws.dws_member_spending_power_index;
|
||||
```
|
||||
|
||||
### 7.2 检查展示分范围是否合规(应全部在 [0, 10])
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
COUNT(*) FILTER (WHERE display_score < 0 OR display_score > 10) AS spi_out_of_range,
|
||||
COUNT(*) FILTER (WHERE score_level_display < 0 OR score_level_display > 10) AS level_out_of_range,
|
||||
COUNT(*) FILTER (WHERE score_speed_display < 0 OR score_speed_display > 10) AS speed_out_of_range,
|
||||
COUNT(*) FILTER (WHERE score_stability_display < 0 OR score_stability_display > 10) AS stability_out_of_range
|
||||
FROM dws.dws_member_spending_power_index;
|
||||
-- 预期:所有列均为 0
|
||||
```
|
||||
|
||||
### 7.3 检查业务主键唯一性(不应有重复)
|
||||
|
||||
```sql
|
||||
SELECT site_id, member_id, COUNT(*) AS cnt
|
||||
FROM dws.dws_member_spending_power_index
|
||||
GROUP BY site_id, member_id
|
||||
HAVING COUNT(*) > 1;
|
||||
-- 预期:无结果返回
|
||||
```
|
||||
|
||||
### 7.4 按门店查看 SPI 分布概况
|
||||
|
||||
```sql
|
||||
SELECT
|
||||
site_id,
|
||||
COUNT(*) AS member_count,
|
||||
ROUND(AVG(display_score), 2) AS avg_spi,
|
||||
ROUND(MIN(display_score), 2) AS min_spi,
|
||||
ROUND(MAX(display_score), 2) AS max_spi,
|
||||
ROUND(PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY display_score), 2) AS median_spi
|
||||
FROM dws.dws_member_spending_power_index
|
||||
GROUP BY site_id
|
||||
ORDER BY site_id;
|
||||
```
|
||||
|
||||
### 7.5 检查 Stability 子分原始分范围(应在 [0, 1])
|
||||
|
||||
```sql
|
||||
SELECT COUNT(*) AS out_of_range
|
||||
FROM dws.dws_member_spending_power_index
|
||||
WHERE score_stability_raw < 0 OR score_stability_raw > 1;
|
||||
-- 预期:0
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 兼容性说明
|
||||
|
||||
| 影响范围 | 说明 |
|
||||
|---------|------|
|
||||
| ETL 任务 | 新增任务 `DWS_SPENDING_POWER_INDEX`,依赖 `DWS_MEMBER_CONSUMPTION`。不影响现有 WBI/NCI/RS/OS/MS/ML 指数任务 |
|
||||
| 后端 API | 当前无 API 直接读取此表。后续如需暴露 SPI 数据,需新增接口 |
|
||||
| 管理后台 | 当前无前端页面展示 SPI。后续可在会员详情页新增 SPI 展示 |
|
||||
| 小程序 | 无影响 |
|
||||
| 其他指数 | SPI 独立于现有指数体系,不修改任何已有表或任务逻辑 |
|
||||
| 分位历史 | SPI 会向 `dws.dws_index_percentile_history` 写入 `index_type='SPI'`/`SPI_LEVEL`/`SPI_SPEED`/`SPI_STABILITY` 的分位记录 |
|
||||
|
||||
---
|
||||
|
||||
## 9. 回滚策略
|
||||
|
||||
### 9.1 删除数据(保留表结构)
|
||||
|
||||
```sql
|
||||
DELETE FROM dws.dws_member_spending_power_index;
|
||||
DELETE FROM dws.dws_index_percentile_history WHERE index_type LIKE 'SPI%';
|
||||
DELETE FROM dws.cfg_index_parameters WHERE index_type = 'SPI';
|
||||
```
|
||||
|
||||
### 9.2 完整回滚(删除表)
|
||||
|
||||
```sql
|
||||
DROP INDEX IF EXISTS dws.idx_spi_display_score;
|
||||
DROP INDEX IF EXISTS dws.idx_spi_site_member;
|
||||
DROP TABLE IF EXISTS dws.dws_member_spending_power_index;
|
||||
DROP SEQUENCE IF EXISTS dws.dws_member_spending_power_index_spi_id_seq;
|
||||
```
|
||||
|
||||
### 9.3 回滚任务注册
|
||||
|
||||
从 `orchestration/task_registry.py` 中移除 `DWS_SPENDING_POWER_INDEX` 注册行,并从 `tasks/dws/index/__init__.py` 和 `tasks/dws/__init__.py` 中移除 `SpendingPowerIndexTask` 导出。
|
||||
|
||||
---
|
||||
|
||||
## 10. 代码引用
|
||||
|
||||
- 任务类:`tasks/dws/index/spending_power_index_task.py` → `SpendingPowerIndexTask`
|
||||
- 继承:`BaseIndexTask`(`tasks/dws/index/base_index_task.py`)
|
||||
- 任务注册:`orchestration/task_registry.py` → `DWS_SPENDING_POWER_INDEX`
|
||||
- 属性测试:`tests/test_spi_properties.py`
|
||||
- 单元测试:`apps/etl/connectors/feiqiu/tests/unit/test_spi_task.py`
|
||||
- 迁移脚本:`db/etl_feiqiu/migrations/2026-02-23_create_dws_member_spending_power_index.sql`
|
||||
- 种子数据:`db/etl_feiqiu/seeds/seed_index_parameters.sql`
|
||||
123
docs/database/BD_Manual_fdw_etl_setup.md
Normal file
123
docs/database/BD_Manual_fdw_etl_setup.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# BD_Manual:FDW 跨库映射配置(fdw_etl)
|
||||
|
||||
> 目标库:`test_zqyy_app`(通过 `APP_DB_DSN` 连接)
|
||||
> 迁移脚本:`db/zqyy_app/migrations/2026-02-24__p1_setup_fdw_etl.sql`
|
||||
> 关联 SPEC:`miniapp-db-foundation`(P1 基础设施层)
|
||||
|
||||
---
|
||||
|
||||
## 1. 变更说明
|
||||
|
||||
### 新增扩展
|
||||
|
||||
| 扩展 | 用途 |
|
||||
|------|------|
|
||||
| `postgres_fdw` | PostgreSQL 外部数据包装器,支持跨库查询 |
|
||||
|
||||
### 新增外部服务器
|
||||
|
||||
| 服务器名 | 目标库 | 说明 |
|
||||
|----------|--------|------|
|
||||
| `etl_feiqiu_server` | ETL 库(通过 `PG_DSN` 连接) | 通用名称,通过 host/dbname/port 参数区分环境 |
|
||||
|
||||
### 新增用户映射
|
||||
|
||||
| 本地角色 | 远程角色 | 服务器 |
|
||||
|----------|----------|--------|
|
||||
| `app_user` | `app_reader` | `etl_feiqiu_server` |
|
||||
|
||||
### 新增 Schema
|
||||
|
||||
| Schema | 用途 |
|
||||
|--------|------|
|
||||
| `fdw_etl` | 存放从 ETL 库 `app` Schema 导入的外部表(只读) |
|
||||
|
||||
### 导入的外部表
|
||||
|
||||
通过 `IMPORT FOREIGN SCHEMA app` 批量导入,外部表与 ETL 库 `app` Schema 中的 RLS 视图一一对应(共 35 张):
|
||||
- 11 张 DWD 视图:`v_dim_member`、`v_dim_assistant`、`v_dim_member_card_account`、`v_dim_table`、`v_dwd_settlement_head`、`v_dwd_table_fee_log`、`v_dwd_assistant_service_log`、`v_dwd_recharge_order`、`v_dwd_store_goods_sale`、`v_dim_staff`、`v_dim_staff_ex`
|
||||
- 24 张 DWS 视图:`v_dws_member_consumption_summary`、`v_dws_member_visit_detail` 等
|
||||
|
||||
### 权限配置
|
||||
|
||||
| 角色 | Schema | 权限 |
|
||||
|------|--------|------|
|
||||
| `app_user` | `fdw_etl` | `USAGE` + `SELECT ON ALL TABLES` + `ALTER DEFAULT PRIVILEGES` |
|
||||
|
||||
---
|
||||
|
||||
## 2. 兼容性影响
|
||||
|
||||
| 组件 | 影响 |
|
||||
|------|------|
|
||||
| ETL 任务 | 无影响。本脚本仅在业务库创建外部表映射,不修改 ETL 库 |
|
||||
| 后端 API | 前置依赖。后端可通过 `fdw_etl.v_dim_member` 等外部表读取 ETL 数据,无需直连 ETL 库 |
|
||||
| 小程序 | 无直接影响。小程序通过后端 API 间接访问 |
|
||||
| 管理后台 | 无直接影响 |
|
||||
| `auth`/`biz` Schema | 无影响。FDW 配置独立于业务 Schema |
|
||||
| `public` Schema | 无影响 |
|
||||
| 现有 `db/fdw/setup_fdw_test.sql` | 功能重叠。本迁移脚本使用通用服务器名 `etl_feiqiu_server`(不含环境前缀),与旧脚本的 `test_etl_feiqiu_server` 共存但独立 |
|
||||
|
||||
### 幂等性说明
|
||||
|
||||
`IMPORT FOREIGN SCHEMA` 不支持 `IF NOT EXISTS`,重复执行会因外部表已存在而报错。本脚本采用 `DROP SCHEMA IF EXISTS fdw_etl CASCADE` + 重建的方式确保幂等性。副作用是每次执行会重建所有外部表,但由于外部表不存储数据,无数据丢失风险。
|
||||
|
||||
---
|
||||
|
||||
## 3. 回滚策略
|
||||
|
||||
迁移脚本末尾已包含注释形式的回滚语句,按逆序执行:
|
||||
|
||||
```sql
|
||||
ALTER DEFAULT PRIVILEGES IN SCHEMA fdw_etl REVOKE SELECT ON TABLES FROM app_user;
|
||||
REVOKE SELECT ON ALL TABLES IN SCHEMA fdw_etl FROM app_user;
|
||||
REVOKE USAGE ON SCHEMA fdw_etl FROM app_user;
|
||||
DROP SCHEMA IF EXISTS fdw_etl CASCADE;
|
||||
DROP USER MAPPING IF EXISTS FOR app_user SERVER etl_feiqiu_server;
|
||||
DROP SERVER IF EXISTS etl_feiqiu_server CASCADE;
|
||||
DROP EXTENSION IF EXISTS postgres_fdw;
|
||||
```
|
||||
|
||||
注意:
|
||||
- `DROP SERVER CASCADE` 会级联删除依赖的用户映射和外部表
|
||||
- 如果其他 Schema 也使用 `postgres_fdw` 扩展,不要执行最后一行 `DROP EXTENSION`
|
||||
- 回滚不影响 ETL 库侧的 `app` Schema 和 RLS 视图
|
||||
|
||||
---
|
||||
|
||||
## 4. 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 验证 postgres_fdw 扩展已安装
|
||||
SELECT extname, extversion
|
||||
FROM pg_extension
|
||||
WHERE extname = 'postgres_fdw';
|
||||
|
||||
-- 2. 验证外部服务器已创建
|
||||
SELECT srvname, srvowner::regrole, srvoptions
|
||||
FROM pg_foreign_server
|
||||
WHERE srvname = 'etl_feiqiu_server';
|
||||
|
||||
-- 3. 验证用户映射已创建
|
||||
SELECT um.umid, r.rolname AS local_role, s.srvname, um.umoptions
|
||||
FROM pg_user_mappings um
|
||||
JOIN pg_foreign_server s ON s.srvname = um.srvname
|
||||
JOIN pg_roles r ON r.rolname = um.usename
|
||||
WHERE s.srvname = 'etl_feiqiu_server';
|
||||
|
||||
-- 4. 验证 fdw_etl Schema 存在且包含外部表
|
||||
SELECT foreign_table_schema, foreign_table_name, foreign_server_name
|
||||
FROM information_schema.foreign_tables
|
||||
WHERE foreign_table_schema = 'fdw_etl'
|
||||
ORDER BY foreign_table_name;
|
||||
|
||||
-- 5. 验证 app_user 对 fdw_etl 有 USAGE 权限
|
||||
SELECT has_schema_privilege('app_user', 'fdw_etl', 'USAGE') AS fdw_etl_usage;
|
||||
|
||||
-- 6. 验证 ALTER DEFAULT PRIVILEGES 已设置
|
||||
SELECT n.nspname AS schema_name,
|
||||
d.defaclacl AS default_acl
|
||||
FROM pg_default_acl d
|
||||
JOIN pg_namespace n ON n.oid = d.defaclnamespace
|
||||
WHERE n.nspname = 'fdw_etl';
|
||||
```
|
||||
102
docs/database/BD_Manual_goods_stock_warning_info.md
Normal file
102
docs/database/BD_Manual_goods_stock_warning_info.md
Normal file
@@ -0,0 +1,102 @@
|
||||
# BD Manual: goodsStockWarningInfo(库存预警信息)
|
||||
|
||||
## 变更概述
|
||||
|
||||
- 日期:2026-02-24
|
||||
- 触发:一致性检查报告发现 API 独有嵌套字段 `goodsStockWarningInfo` 未映射到 ODS/DWD
|
||||
- 迁移脚本:`db/etl_feiqiu/migrations/2026-02-24__add_goods_stock_warning_info.sql`
|
||||
|
||||
## 字段来源
|
||||
|
||||
API 端点 `/TenantGoods/GetGoodsInventoryList` 返回的 `store_goods_master` 记录中包含嵌套对象:
|
||||
|
||||
```json
|
||||
{
|
||||
"goodsStockWarningInfo": {
|
||||
"tenant_goods_id": 0, // 冗余,已有同名顶层字段
|
||||
"site_goods_id": 0, // 冗余,对应顶层 id
|
||||
"sales_day": 0.0, // → warning_sales_day
|
||||
"warning_day_max": 0, // → warning_day_max
|
||||
"warning_day_min": 0 // → warning_day_min
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
仅提取 3 个有效字段,冗余 ID 不重复收录。
|
||||
|
||||
## 新增字段
|
||||
|
||||
| 层 | 表 | 列名 | 类型 | 说明 |
|
||||
|---|---|---|---|---|
|
||||
| ODS | `ods.store_goods_master` | `warning_sales_day` | NUMERIC(18,2) | 库存预警参考的日均销量 |
|
||||
| ODS | `ods.store_goods_master` | `warning_day_max` | INTEGER | 预警天数上限 |
|
||||
| ODS | `ods.store_goods_master` | `warning_day_min` | INTEGER | 预警天数下限 |
|
||||
| DWD | `dwd.dim_store_goods_ex` | `warning_sales_day` | NUMERIC(18,2) | 同 ODS,直接映射 |
|
||||
| DWD | `dwd.dim_store_goods_ex` | `warning_day_max` | INTEGER | 同 ODS,直接映射 |
|
||||
| DWD | `dwd.dim_store_goods_ex` | `warning_day_min` | INTEGER | 同 ODS,直接映射 |
|
||||
|
||||
## 数据流
|
||||
|
||||
```
|
||||
API goodsStockWarningInfo (嵌套 JSON)
|
||||
↓ _merge_record_layers 扁平化(_STOCK_WARNING_FIELD_MAP)
|
||||
ODS ods.store_goods_master (warning_sales_day / warning_day_max / warning_day_min)
|
||||
↓ DWD FACT_MAPPINGS 直接映射
|
||||
DWD dwd.dim_store_goods_ex (同名列)
|
||||
```
|
||||
|
||||
## 代码变更
|
||||
|
||||
| 文件 | 变更 |
|
||||
|---|---|
|
||||
| `apps/etl/connectors/feiqiu/tasks/ods/ods_tasks.py` | `_merge_record_layers` 增加 `goodsStockWarningInfo` 扁平化逻辑 |
|
||||
| `apps/etl/connectors/feiqiu/tasks/dwd/dwd_load_task.py` | `FACT_MAPPINGS["dwd.dim_store_goods_ex"]` 增加 3 个映射 |
|
||||
| `db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/schemas/ods.sql` | baseline 同步 |
|
||||
| `db/_archived/ddl_baseline_2026-02-22/db/etl_feiqiu/schemas/dwd.sql` | baseline 同步 |
|
||||
|
||||
## 兼容性
|
||||
|
||||
- 后端 API:无影响(后端不直接读取 ODS/DWD 层这些字段)
|
||||
- 小程序:无影响
|
||||
- ETL:ODS schema-aware 插入自动识别新列;DWD 通过 FACT_MAPPINGS 映射
|
||||
- 管理后台:如需展示库存预警信息,可从 `dwd.dim_store_goods_ex` 读取
|
||||
|
||||
## 回滚策略
|
||||
|
||||
```sql
|
||||
ALTER TABLE ods.store_goods_master
|
||||
DROP COLUMN IF EXISTS warning_sales_day,
|
||||
DROP COLUMN IF EXISTS warning_day_max,
|
||||
DROP COLUMN IF EXISTS warning_day_min;
|
||||
ALTER TABLE dwd.dim_store_goods_ex
|
||||
DROP COLUMN IF EXISTS warning_sales_day,
|
||||
DROP COLUMN IF EXISTS warning_day_max,
|
||||
DROP COLUMN IF EXISTS warning_day_min;
|
||||
```
|
||||
|
||||
## 验证 SQL
|
||||
|
||||
```sql
|
||||
-- 1. 确认 ODS 新列存在
|
||||
SELECT column_name, data_type FROM information_schema.columns
|
||||
WHERE table_schema = 'ods' AND table_name = 'store_goods_master'
|
||||
AND column_name IN ('warning_sales_day', 'warning_day_max', 'warning_day_min')
|
||||
ORDER BY column_name;
|
||||
-- 预期:3 行
|
||||
|
||||
-- 2. 确认 DWD 新列存在
|
||||
SELECT column_name, data_type FROM information_schema.columns
|
||||
WHERE table_schema = 'dwd' AND table_name = 'dim_store_goods_ex'
|
||||
AND column_name IN ('warning_sales_day', 'warning_day_max', 'warning_day_min')
|
||||
ORDER BY column_name;
|
||||
-- 预期:3 行
|
||||
|
||||
-- 3. 确认注释已设置
|
||||
SELECT c.column_name, pgd.description
|
||||
FROM information_schema.columns c
|
||||
JOIN pg_catalog.pg_statio_all_tables st ON st.schemaname = c.table_schema AND st.relname = c.table_name
|
||||
JOIN pg_catalog.pg_description pgd ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
|
||||
WHERE c.table_schema = 'ods' AND c.table_name = 'store_goods_master'
|
||||
AND c.column_name LIKE 'warning_%';
|
||||
-- 预期:3 行,description 非空
|
||||
```
|
||||
@@ -10,10 +10,11 @@
|
||||
| `etl_feiqiu__ods.sql` | etl_feiqiu | ods | 原始数据层(23 表) |
|
||||
| `etl_feiqiu__dwd.sql` | etl_feiqiu | dwd | 明细数据层(44 表) |
|
||||
| `etl_feiqiu__core.sql` | etl_feiqiu | core | 跨门店标准化(7 表) |
|
||||
| `etl_feiqiu__dws.sql` | etl_feiqiu | dws | 汇总数据层(32 表 + 1 视图 + 8 物化视图) |
|
||||
| `etl_feiqiu__app.sql` | etl_feiqiu | app | RLS 视图层(7 视图,无表) |
|
||||
| `etl_feiqiu__dws.sql` | etl_feiqiu | dws | 汇总数据层(34 表 + 1 视图 + 8 物化视图) |
|
||||
| `etl_feiqiu__app.sql` | etl_feiqiu | app | RLS 视图层(43 视图,无表) |
|
||||
| `zqyy_app__public.sql` | zqyy_app | public | 小程序业务表(12 表) |
|
||||
| `fdw.sql` | — | — | FDW 跨库映射配置 |
|
||||
| `zqyy_app__auth.sql` | zqyy_app | auth | 用户认证与权限(8 表) |
|
||||
| `fdw.sql` | — | — | FDW 正向跨库映射配置(etl→app) |
|
||||
|
||||
## 数据字典(BD_Manual — ODS→DWD 字段映射)
|
||||
|
||||
@@ -41,9 +42,15 @@
|
||||
- `etl_feiqiu_schema_migration.md`(旧迁移汇总)
|
||||
- `zqyy_app_admin_web_tables.md`(建表记录)
|
||||
|
||||
## 注意事项
|
||||
|
||||
- `fdw.sql` 仅包含正向映射(etl_feiqiu → zqyy_app),反向映射(zqyy_app → etl_feiqiu)的可执行脚本在 `db/fdw/setup_fdw_reverse*.sql`
|
||||
- DDL 基线中的统计数字以文件实际内容为准,本 README 的表格数字可能滞后于最新导出
|
||||
|
||||
## 相关资源
|
||||
|
||||
- 种子数据:`db/etl_feiqiu/seeds/`、`db/zqyy_app/seeds/`
|
||||
- FDW 配置:`db/fdw/`
|
||||
- FDW 配置(可执行):`db/fdw/`(含正向 + 反向 + 测试环境版本)
|
||||
- DDL 生成脚本:`scripts/ops/gen_consolidated_ddl.py`
|
||||
- 迁移脚本(活跃):`db/etl_feiqiu/migrations/`、`db/zqyy_app/migrations/`
|
||||
- 迁移脚本归档:`db/_archived/ddl_baseline_2026-02-22/`
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / core(跨门店标准化维度/事实)
|
||||
-- 生成日期:2026-02-23
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / dwd(明细数据层)
|
||||
-- 生成日期:2026-02-23
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -449,7 +449,10 @@ CREATE TABLE dwd.dim_store_goods_ex (
|
||||
scd2_is_current integer,
|
||||
scd2_version integer,
|
||||
batch_stock_quantity numeric,
|
||||
time_slot_sale integer
|
||||
time_slot_sale integer,
|
||||
warning_sales_day numeric(18,2),
|
||||
warning_day_max integer,
|
||||
warning_day_min integer
|
||||
);
|
||||
|
||||
CREATE TABLE dwd.dim_table (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / dws(汇总数据层)
|
||||
-- 生成日期:2026-02-23
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -17,6 +17,7 @@ CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_customer_stats_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_daily_detail_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_finance_analysis_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_monthly_summary_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_order_contribution_contribution_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_recharge_commission_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_assistant_salary_calc_id_seq AS bigint;
|
||||
CREATE SEQUENCE IF NOT EXISTS dws.dws_finance_daily_summary_id_seq AS bigint;
|
||||
@@ -194,7 +195,11 @@ CREATE TABLE dws.dws_assistant_daily_detail (
|
||||
trashed_seconds integer DEFAULT 0 NOT NULL,
|
||||
trashed_count integer DEFAULT 0 NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
penalty_minutes numeric(10,2) DEFAULT 0,
|
||||
penalty_reason text,
|
||||
is_exempt boolean DEFAULT false,
|
||||
per_hour_contribution numeric(14,2)
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_assistant_finance_analysis (
|
||||
@@ -258,6 +263,23 @@ CREATE TABLE dws.dws_assistant_monthly_summary (
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_assistant_order_contribution (
|
||||
contribution_id bigint DEFAULT nextval('dws.dws_assistant_order_contribution_contribution_id_seq'::regclass) NOT NULL,
|
||||
site_id integer NOT NULL,
|
||||
tenant_id integer NOT NULL,
|
||||
assistant_id bigint NOT NULL,
|
||||
assistant_nickname character varying(100),
|
||||
stat_date date NOT NULL,
|
||||
order_gross_revenue numeric(14,2) DEFAULT 0,
|
||||
order_net_revenue numeric(14,2) DEFAULT 0,
|
||||
time_weighted_revenue numeric(14,2) DEFAULT 0,
|
||||
time_weighted_net_revenue numeric(14,2) DEFAULT 0,
|
||||
order_count integer DEFAULT 0,
|
||||
total_service_seconds integer DEFAULT 0,
|
||||
created_at timestamp with time zone DEFAULT now(),
|
||||
updated_at timestamp with time zone DEFAULT now()
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_assistant_recharge_commission (
|
||||
id bigint DEFAULT nextval('dws.dws_assistant_recharge_commission_id_seq'::regclass) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
@@ -622,7 +644,14 @@ CREATE TABLE dws.dws_member_consumption_summary (
|
||||
is_active_90d boolean DEFAULT false NOT NULL,
|
||||
customer_tier character varying(20),
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
recharge_count_30d integer DEFAULT 0,
|
||||
recharge_count_60d integer DEFAULT 0,
|
||||
recharge_count_90d integer DEFAULT 0,
|
||||
recharge_amount_30d numeric(14,2) DEFAULT 0,
|
||||
recharge_amount_60d numeric(14,2) DEFAULT 0,
|
||||
recharge_amount_90d numeric(14,2) DEFAULT 0,
|
||||
avg_ticket_amount numeric(14,2) DEFAULT 0
|
||||
);
|
||||
|
||||
CREATE TABLE dws.dws_member_newconv_index (
|
||||
@@ -918,6 +947,7 @@ ALTER TABLE dws.dws_assistant_finance_analysis ADD CONSTRAINT dws_assistant_fina
|
||||
ALTER TABLE dws.dws_assistant_finance_analysis ADD CONSTRAINT uk_dws_assistant_finance UNIQUE (site_id, stat_date, assistant_id);
|
||||
ALTER TABLE dws.dws_assistant_monthly_summary ADD CONSTRAINT dws_assistant_monthly_summary_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE dws.dws_assistant_monthly_summary ADD CONSTRAINT uk_dws_assistant_monthly UNIQUE (site_id, assistant_id, stat_month, assistant_level_code);
|
||||
ALTER TABLE dws.dws_assistant_order_contribution ADD CONSTRAINT dws_assistant_order_contribution_pkey PRIMARY KEY (contribution_id);
|
||||
ALTER TABLE dws.dws_assistant_recharge_commission ADD CONSTRAINT dws_assistant_recharge_commission_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE dws.dws_assistant_salary_calc ADD CONSTRAINT dws_assistant_salary_calc_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE dws.dws_assistant_salary_calc ADD CONSTRAINT uk_dws_assistant_salary UNIQUE (site_id, assistant_id, salary_month, assistant_level_code);
|
||||
@@ -978,6 +1008,8 @@ CREATE INDEX idx_dws_assistant_finance_date ON dws.dws_assistant_finance_analysi
|
||||
CREATE INDEX idx_dws_assistant_monthly_asst ON dws.dws_assistant_monthly_summary USING btree (assistant_id, stat_month);
|
||||
CREATE INDEX idx_dws_assistant_monthly_month ON dws.dws_assistant_monthly_summary USING btree (stat_month);
|
||||
CREATE INDEX idx_dws_assistant_monthly_tier ON dws.dws_assistant_monthly_summary USING btree (tier_code);
|
||||
CREATE UNIQUE INDEX idx_aoc_site_assistant_date ON dws.dws_assistant_order_contribution USING btree (site_id, assistant_id, stat_date);
|
||||
CREATE INDEX idx_aoc_stat_date ON dws.dws_assistant_order_contribution USING btree (site_id, stat_date);
|
||||
CREATE INDEX idx_dws_assistant_commission_asst ON dws.dws_assistant_recharge_commission USING btree (assistant_id, commission_month);
|
||||
CREATE INDEX idx_dws_assistant_commission_batch ON dws.dws_assistant_recharge_commission USING btree (import_batch_no);
|
||||
CREATE INDEX idx_dws_assistant_commission_month ON dws.dws_assistant_recharge_commission USING btree (commission_month);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / meta(ETL 调度元数据)
|
||||
-- 生成日期:2026-02-23
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- etl_feiqiu / ods(原始数据层)
|
||||
-- 生成日期:2026-02-23
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
@@ -873,7 +873,10 @@ CREATE TABLE ods.store_goods_master (
|
||||
source_file text,
|
||||
source_endpoint text,
|
||||
fetched_at timestamp with time zone DEFAULT now(),
|
||||
time_slot_sale integer
|
||||
time_slot_sale integer,
|
||||
warning_sales_day numeric(18,2),
|
||||
warning_day_max integer,
|
||||
warning_day_min integer
|
||||
);
|
||||
|
||||
CREATE TABLE ods.store_goods_sales_records (
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- FDW 跨库映射(在 zqyy_app 中执行)
|
||||
-- 生成日期:2026-02-23
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:db/fdw/setup_fdw.sql
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
135
docs/database/ddl/zqyy_app__auth.sql
Normal file
135
docs/database/ddl/zqyy_app__auth.sql
Normal file
@@ -0,0 +1,135 @@
|
||||
-- =============================================================================
|
||||
-- zqyy_app / auth(用户认证与权限)
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
CREATE SCHEMA IF NOT EXISTS auth;
|
||||
|
||||
-- 序列
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.permissions_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.roles_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.site_code_mapping_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.user_applications_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.user_assistant_binding_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.user_site_roles_id_seq AS integer;
|
||||
CREATE SEQUENCE IF NOT EXISTS auth.users_id_seq AS integer;
|
||||
|
||||
-- 表
|
||||
CREATE TABLE auth.permissions (
|
||||
id integer DEFAULT nextval('auth.permissions_id_seq'::regclass) NOT NULL,
|
||||
code character varying(100) NOT NULL,
|
||||
name character varying(200) NOT NULL,
|
||||
description text,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.role_permissions (
|
||||
role_id integer NOT NULL,
|
||||
permission_id integer NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.roles (
|
||||
id integer DEFAULT nextval('auth.roles_id_seq'::regclass) NOT NULL,
|
||||
code character varying(50) NOT NULL,
|
||||
name character varying(100) NOT NULL,
|
||||
description text,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.site_code_mapping (
|
||||
id integer DEFAULT nextval('auth.site_code_mapping_id_seq'::regclass) NOT NULL,
|
||||
site_code character varying(10) NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
site_name character varying(200),
|
||||
tenant_id integer,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.user_applications (
|
||||
id integer DEFAULT nextval('auth.user_applications_id_seq'::regclass) NOT NULL,
|
||||
user_id integer NOT NULL,
|
||||
site_code character varying(10) NOT NULL,
|
||||
site_id bigint,
|
||||
applied_role_text character varying(100) NOT NULL,
|
||||
employee_number character varying(50),
|
||||
phone character varying(20) NOT NULL,
|
||||
status character varying(20) DEFAULT 'pending'::character varying NOT NULL,
|
||||
reviewer_id integer,
|
||||
review_note text,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
reviewed_at timestamp with time zone
|
||||
);
|
||||
|
||||
CREATE TABLE auth.user_assistant_binding (
|
||||
id integer DEFAULT nextval('auth.user_assistant_binding_id_seq'::regclass) NOT NULL,
|
||||
user_id integer NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
assistant_id bigint,
|
||||
staff_id bigint,
|
||||
binding_type character varying(20) NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.user_site_roles (
|
||||
id integer DEFAULT nextval('auth.user_site_roles_id_seq'::regclass) NOT NULL,
|
||||
user_id integer NOT NULL,
|
||||
site_id bigint NOT NULL,
|
||||
role_id integer NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE auth.users (
|
||||
id integer DEFAULT nextval('auth.users_id_seq'::regclass) NOT NULL,
|
||||
wx_openid character varying(100),
|
||||
wx_union_id character varying(100),
|
||||
wx_avatar_url text,
|
||||
nickname character varying(100),
|
||||
phone character varying(20),
|
||||
status character varying(20) DEFAULT 'pending'::character varying NOT NULL,
|
||||
created_at timestamp with time zone DEFAULT now() NOT NULL,
|
||||
updated_at timestamp with time zone DEFAULT now() NOT NULL
|
||||
);
|
||||
|
||||
-- 约束(主键 / 唯一 / 外键)
|
||||
ALTER TABLE auth.permissions ADD CONSTRAINT permissions_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.permissions ADD CONSTRAINT permissions_code_key UNIQUE (code);
|
||||
ALTER TABLE auth.permissions ADD CONSTRAINT uq_permissions_code UNIQUE (code);
|
||||
ALTER TABLE auth.role_permissions ADD CONSTRAINT fk_role_permissions_permission_id FOREIGN KEY (permission_id) REFERENCES auth.permissions(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.role_permissions ADD CONSTRAINT fk_role_permissions_role_id FOREIGN KEY (role_id) REFERENCES auth.roles(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.role_permissions ADD CONSTRAINT role_permissions_permission_id_fkey FOREIGN KEY (permission_id) REFERENCES auth.permissions(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.role_permissions ADD CONSTRAINT role_permissions_role_id_fkey FOREIGN KEY (role_id) REFERENCES auth.roles(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.role_permissions ADD CONSTRAINT role_permissions_pkey PRIMARY KEY (role_id, permission_id);
|
||||
ALTER TABLE auth.roles ADD CONSTRAINT roles_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.roles ADD CONSTRAINT roles_code_key UNIQUE (code);
|
||||
ALTER TABLE auth.roles ADD CONSTRAINT uq_roles_code UNIQUE (code);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT site_code_mapping_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT site_code_mapping_site_code_key UNIQUE (site_code);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT site_code_mapping_site_id_key UNIQUE (site_id);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT uq_site_code_mapping_site_code UNIQUE (site_code);
|
||||
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT uq_site_code_mapping_site_id UNIQUE (site_id);
|
||||
ALTER TABLE auth.user_applications ADD CONSTRAINT fk_user_applications_user_id FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_applications ADD CONSTRAINT user_applications_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_applications ADD CONSTRAINT user_applications_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.user_assistant_binding ADD CONSTRAINT fk_user_assistant_binding_user_id FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_assistant_binding ADD CONSTRAINT user_assistant_binding_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_assistant_binding ADD CONSTRAINT user_assistant_binding_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.user_site_roles ADD CONSTRAINT fk_user_site_roles_role_id FOREIGN KEY (role_id) REFERENCES auth.roles(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_site_roles ADD CONSTRAINT fk_user_site_roles_user_id FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_site_roles ADD CONSTRAINT user_site_roles_role_id_fkey FOREIGN KEY (role_id) REFERENCES auth.roles(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_site_roles ADD CONSTRAINT user_site_roles_user_id_fkey FOREIGN KEY (user_id) REFERENCES auth.users(id) ON DELETE CASCADE;
|
||||
ALTER TABLE auth.user_site_roles ADD CONSTRAINT user_site_roles_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.user_site_roles ADD CONSTRAINT uq_user_site_roles_user_site_role UNIQUE (user_id, site_id, role_id);
|
||||
ALTER TABLE auth.user_site_roles ADD CONSTRAINT user_site_roles_user_id_site_id_role_id_key UNIQUE (user_id, site_id, role_id);
|
||||
ALTER TABLE auth.users ADD CONSTRAINT users_pkey PRIMARY KEY (id);
|
||||
ALTER TABLE auth.users ADD CONSTRAINT uq_users_wx_openid UNIQUE (wx_openid);
|
||||
ALTER TABLE auth.users ADD CONSTRAINT users_wx_openid_key UNIQUE (wx_openid);
|
||||
|
||||
-- 索引
|
||||
CREATE INDEX ix_site_code_mapping_site_code ON auth.site_code_mapping USING btree (site_code);
|
||||
CREATE INDEX ix_user_applications_status ON auth.user_applications USING btree (status);
|
||||
CREATE INDEX ix_user_applications_user_id ON auth.user_applications USING btree (user_id);
|
||||
CREATE INDEX ix_user_site_roles_user_site ON auth.user_site_roles USING btree (user_id, site_id);
|
||||
CREATE INDEX ix_users_status ON auth.users USING btree (status);
|
||||
CREATE INDEX ix_users_wx_openid ON auth.users USING btree (wx_openid);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
-- =============================================================================
|
||||
-- zqyy_app / public(小程序业务表)
|
||||
-- 生成日期:2026-02-23
|
||||
-- 生成日期:2026-02-25
|
||||
-- 来源:测试库(通过脚本自动导出)
|
||||
-- =============================================================================
|
||||
|
||||
|
||||
Reference in New Issue
Block a user