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:
Neo
2026-02-26 08:03:53 +08:00
parent fafc95e64c
commit b25308c3f4
224 changed files with 17660 additions and 32198 deletions

View File

@@ -1,9 +1,10 @@
# db/ — 数据库资产目录
## 当前状态2026-02-22 基线重置后
## 当前状态2026-02-25 更新
完整 DDL 基线已迁移至 `docs/database/ddl/`(按 schema 分文件,从测试库自动导出)。
本目录保留运行时资产种子数据、FDW 配置、建库脚本),历史文件已归档
本目录保留运行时资产(迁移脚本、种子数据、FDW 配置、建库脚本)。
2026-02-22 基线重置前的旧迁移已归档至 `_archived/`;之后的新迁移仍在 `migrations/` 中。
## 目录结构
@@ -11,7 +12,7 @@
db/
├── etl_feiqiu/
│ ├── schemas/ — 已清空DDL 基线见 docs/database/ddl/etl_feiqiu__*.sql
│ ├── migrations/ — 已归档(全部变更已吸收进新 DDL 基线
│ ├── migrations/ — 基线重置后的增量迁移(重置前的已归档)
│ ├── seeds/ — 种子数据(运行时需要)
│ │ ├── seed_ods_tasks.sql
│ │ ├── seed_scheduler_tasks.sql
@@ -21,7 +22,7 @@ db/
│ └── create_test_db.sql
├── zqyy_app/
│ ├── schemas/ — 已清空
│ ├── migrations/ — 已归档
│ ├── migrations/ — 基线重置后的增量迁移(含 auth 建表、FDW 配置、种子数据)
│ ├── seeds/
│ │ └── admin_web_seed.sql
│ └── scripts/
@@ -31,7 +32,7 @@ db/
│ ├── setup_fdw_test.sql
│ ├── setup_fdw_reverse.sql
│ └── setup_fdw_reverse_test.sql
└── _archived/ — 归档(旧 DDL + 迁移脚本,仅供历史参考)
└── _archived/ — 归档(旧 DDL + 基线重置前的迁移脚本,仅供历史参考)
└── ddl_baseline_2026-02-22/
```
@@ -45,11 +46,13 @@ db/
- `docs/database/ddl/etl_feiqiu__dws.sql`
- `docs/database/ddl/etl_feiqiu__app.sql`(仅视图)
- `docs/database/ddl/zqyy_app__public.sql`
- `docs/database/ddl/fdw.sql`
- `docs/database/ddl/zqyy_app__auth.sql`
- `docs/database/ddl/fdw.sql`(仅正向映射;反向映射见 `db/fdw/setup_fdw_reverse*.sql`
重新生成:`python scripts/ops/gen_consolidated_ddl.py`
## 未来迁移
## 迁移管理
归档后,新的迁移脚本`migrations/`,文件名格式 `YYYY-MM-DD__描述.sql`
每次迁移执行后,建议重新运行 DDL 生成脚本刷新基线
新的迁移脚本放 `migrations/`,文件名格式 `YYYY-MM-DD__描述.sql`
每次迁移执行后,建议重新运行 DDL 生成脚本刷新基线`python scripts/ops/gen_consolidated_ddl.py`
种子数据类脚本(纯 INSERT/DELETE`seeds/`,不放 `migrations/`

View File

@@ -845,6 +845,9 @@ CREATE TABLE IF NOT EXISTS dim_store_goods_ex (
sort_order INTEGER,
batch_stock_quantity NUMERIC(18,2), -- CHANGE 2026-02-21 | 修正类型:与 DB 实际一致(迁移脚本用 NUMERIC(18,2)
time_slot_sale INTEGER, -- CHANGE 2026-02-21 | 新增:分时段销售标记
warning_sales_day NUMERIC(18,2), -- CHANGE 2026-02-24 | 新增:库存预警日均销量
warning_day_max INTEGER, -- CHANGE 2026-02-24 | 新增:预警天数上限
warning_day_min INTEGER, -- CHANGE 2026-02-24 | 新增:预警天数下限
SCD2_start_time TIMESTAMPTZ,
SCD2_end_time TIMESTAMPTZ,
SCD2_is_current INT,

View File

@@ -1744,6 +1744,9 @@ CREATE TABLE IF NOT EXISTS ods.store_goods_master (
commodity_code TEXT,
not_sale INTEGER,
time_slot_sale INTEGER, -- CHANGE 2026-02-21 | 新增分时段销售标记API 返回但此前未收录)
warning_sales_day NUMERIC(18,2), -- CHANGE 2026-02-24 | 新增库存预警日均销量goodsStockWarningInfo.sales_day
warning_day_max INTEGER, -- CHANGE 2026-02-24 | 新增预警天数上限goodsStockWarningInfo.warning_day_max
warning_day_min INTEGER, -- CHANGE 2026-02-24 | 新增预警天数下限goodsStockWarningInfo.warning_day_min
payload JSONB NOT NULL,
content_hash TEXT NOT NULL,
source_file TEXT,

View File

@@ -0,0 +1,139 @@
-- =============================================================================
-- FDW 外部表扩展 — DWS 层新表 + 已有表新增字段
-- 目标库test_zqyy_app测试/ zqyy_app生产
-- 前提ETL 库 app schema 已创建/更新对应 RLS 视图
-- =============================================================================
-- -----------------------------------------------------------------------------
-- 1. 新建:助教订单流水统计 FDW 外部表
-- -----------------------------------------------------------------------------
CREATE FOREIGN TABLE IF NOT EXISTS fdw_etl.v_dws_assistant_order_contribution (
contribution_id BIGINT,
site_id INTEGER,
tenant_id INTEGER,
assistant_id BIGINT,
assistant_nickname VARCHAR(100),
stat_date DATE,
order_gross_revenue NUMERIC(14,2),
order_net_revenue NUMERIC(14,2),
time_weighted_revenue NUMERIC(14,2),
time_weighted_net_revenue NUMERIC(14,2),
order_count INTEGER,
total_service_seconds INTEGER,
created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE
) SERVER etl_server
OPTIONS (schema_name 'app', table_name 'v_dws_assistant_order_contribution');
-- -----------------------------------------------------------------------------
-- 2. 重建:会员消费汇总 FDW 外部表(新增充值窗口 + 次均消费字段)
-- -----------------------------------------------------------------------------
DROP FOREIGN TABLE IF EXISTS fdw_etl.v_dws_member_consumption_summary;
CREATE FOREIGN TABLE fdw_etl.v_dws_member_consumption_summary (
id BIGINT,
site_id BIGINT,
tenant_id BIGINT,
member_id BIGINT,
stat_date DATE,
member_nickname VARCHAR(100),
member_mobile VARCHAR(20),
card_grade_name VARCHAR(50),
register_date DATE,
first_consume_date DATE,
last_consume_date DATE,
total_visit_count INTEGER,
total_consume_amount NUMERIC(14,2),
total_recharge_amount NUMERIC(14,2),
total_table_fee NUMERIC(14,2),
total_goods_amount NUMERIC(14,2),
total_assistant_amount NUMERIC(14,2),
visit_count_7d INTEGER,
visit_count_10d INTEGER,
visit_count_15d INTEGER,
visit_count_30d INTEGER,
visit_count_60d INTEGER,
visit_count_90d INTEGER,
consume_amount_7d NUMERIC(14,2),
consume_amount_10d NUMERIC(14,2),
consume_amount_15d NUMERIC(14,2),
consume_amount_30d NUMERIC(14,2),
consume_amount_60d NUMERIC(14,2),
consume_amount_90d NUMERIC(14,2),
cash_card_balance NUMERIC(14,2),
gift_card_balance NUMERIC(14,2),
total_card_balance NUMERIC(14,2),
days_since_last INTEGER,
is_active_7d BOOLEAN,
is_active_30d BOOLEAN,
is_active_90d BOOLEAN,
customer_tier VARCHAR(20),
created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE,
recharge_count_30d INTEGER,
recharge_count_60d INTEGER,
recharge_count_90d INTEGER,
recharge_amount_30d NUMERIC(14,2),
recharge_amount_60d NUMERIC(14,2),
recharge_amount_90d NUMERIC(14,2),
avg_ticket_amount NUMERIC(14,2)
) SERVER etl_server
OPTIONS (schema_name 'app', table_name 'v_dws_member_consumption_summary');
-- -----------------------------------------------------------------------------
-- 3. 重建:助教日度明细 FDW 外部表(新增惩罚字段)
-- -----------------------------------------------------------------------------
DROP FOREIGN TABLE IF EXISTS fdw_etl.v_dws_assistant_daily_detail;
CREATE FOREIGN TABLE fdw_etl.v_dws_assistant_daily_detail (
id BIGINT,
site_id BIGINT,
tenant_id BIGINT,
assistant_id BIGINT,
assistant_nickname VARCHAR(50),
stat_date DATE,
assistant_level_code INTEGER,
assistant_level_name VARCHAR(20),
total_service_count INTEGER,
base_service_count INTEGER,
bonus_service_count INTEGER,
room_service_count INTEGER,
total_seconds INTEGER,
base_seconds INTEGER,
bonus_seconds INTEGER,
room_seconds INTEGER,
total_hours NUMERIC(10,2),
base_hours NUMERIC(10,2),
bonus_hours NUMERIC(10,2),
room_hours NUMERIC(10,2),
total_ledger_amount NUMERIC(12,2),
base_ledger_amount NUMERIC(12,2),
bonus_ledger_amount NUMERIC(12,2),
room_ledger_amount NUMERIC(12,2),
unique_customers INTEGER,
unique_tables INTEGER,
trashed_seconds INTEGER,
trashed_count INTEGER,
created_at TIMESTAMP WITH TIME ZONE,
updated_at TIMESTAMP WITH TIME ZONE,
penalty_minutes NUMERIC(10,2),
penalty_reason TEXT,
is_exempt BOOLEAN,
per_hour_contribution NUMERIC(14,2)
) SERVER etl_server
OPTIONS (schema_name 'app', table_name 'v_dws_assistant_daily_detail');
-- -----------------------------------------------------------------------------
-- 授权
-- -----------------------------------------------------------------------------
GRANT SELECT ON fdw_etl.v_dws_assistant_order_contribution TO app_user;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- REVOKE SELECT ON fdw_etl.v_dws_assistant_order_contribution FROM app_user;
-- DROP FOREIGN TABLE IF EXISTS fdw_etl.v_dws_assistant_daily_detail;
-- DROP FOREIGN TABLE IF EXISTS fdw_etl.v_dws_member_consumption_summary;
-- DROP FOREIGN TABLE IF EXISTS fdw_etl.v_dws_assistant_order_contribution;
-- 然后重建旧版本的 v_dws_assistant_daily_detail 和 v_dws_member_consumption_summary不含新字段

View File

@@ -0,0 +1,67 @@
-- =============================================================================
-- 迁移脚本:扩展助教日度业绩明细表 — 新增定档折算惩罚字段
-- 日期2025-02-24
-- 说明:在 dws.dws_assistant_daily_detail 中新增 penalty_minutes、penalty_reason、
-- is_exempt、per_hour_contribution 四个字段,支撑定档折算惩罚检测与计算。
-- 需求5.1, 5.2
-- =============================================================================
BEGIN;
-- ---------------------------------------------------------------------------
-- 1. ALTER TABLE新增定档折算惩罚字段
-- ---------------------------------------------------------------------------
ALTER TABLE dws.dws_assistant_daily_detail
ADD COLUMN IF NOT EXISTS penalty_minutes NUMERIC(10,2) DEFAULT 0,
ADD COLUMN IF NOT EXISTS penalty_reason TEXT,
ADD COLUMN IF NOT EXISTS is_exempt BOOLEAN DEFAULT FALSE,
ADD COLUMN IF NOT EXISTS per_hour_contribution NUMERIC(14,2);
COMMENT ON COLUMN dws.dws_assistant_daily_detail.penalty_minutes
IS '定档折算惩罚分钟数,无惩罚时为 0';
COMMENT ON COLUMN dws.dws_assistant_daily_detail.penalty_reason
IS '惩罚原因描述,无惩罚时为 NULL';
COMMENT ON COLUMN dws.dws_assistant_daily_detail.is_exempt
IS '是否豁免惩罚TRUE 时跳过惩罚计算';
COMMENT ON COLUMN dws.dws_assistant_daily_detail.per_hour_contribution
IS '单人每小时贡献流水 = 台费每小时实收单价 / 本次基础课助教人数';
COMMIT;
-- =============================================================================
-- 回滚脚本(如需撤销)
-- =============================================================================
-- ALTER TABLE dws.dws_assistant_daily_detail
-- DROP COLUMN IF EXISTS penalty_minutes,
-- DROP COLUMN IF EXISTS penalty_reason,
-- DROP COLUMN IF EXISTS is_exempt,
-- DROP COLUMN IF EXISTS per_hour_contribution;
-- =============================================================================
-- 验证 SQL
-- =============================================================================
-- 1. 确认新增字段存在
-- SELECT column_name, data_type, column_default
-- FROM information_schema.columns
-- WHERE table_schema = 'dws'
-- AND table_name = 'dws_assistant_daily_detail'
-- AND column_name IN ('penalty_minutes', 'penalty_reason', 'is_exempt', 'per_hour_contribution')
-- ORDER BY column_name;
-- 预期4 行
-- 2. 确认字段类型正确
-- SELECT column_name, data_type, numeric_precision, numeric_scale
-- FROM information_schema.columns
-- WHERE table_schema = 'dws'
-- AND table_name = 'dws_assistant_daily_detail'
-- AND column_name IN ('penalty_minutes', 'per_hour_contribution')
-- ORDER BY column_name;
-- 预期penalty_minutes → numeric(10,2)per_hour_contribution → numeric(14,2)
-- 3. 确认 is_exempt 默认值
-- SELECT column_name, column_default
-- FROM information_schema.columns
-- WHERE table_schema = 'dws'
-- AND table_name = 'dws_assistant_daily_detail'
-- AND column_name = 'is_exempt';
-- 预期column_default = 'false'

View File

@@ -0,0 +1,89 @@
-- =============================================================================
-- 迁移脚本:扩展会员消费汇总表 — 新增充值窗口和次均消费字段
-- 日期2025-02-24
-- 说明:在 dws.dws_member_consumption_summary 中新增 30/60/90 天充值次数、
-- 充值金额以及次均消费额度字段,支撑小程序客户看板展示。
-- 需求3.1, 3.2
-- =============================================================================
BEGIN;
-- ---------------------------------------------------------------------------
-- 1. ALTER TABLE新增充值窗口 + 次均消费字段
-- ---------------------------------------------------------------------------
ALTER TABLE dws.dws_member_consumption_summary
ADD COLUMN IF NOT EXISTS recharge_count_30d INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS recharge_count_60d INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS recharge_count_90d INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS recharge_amount_30d NUMERIC(14,2) DEFAULT 0,
ADD COLUMN IF NOT EXISTS recharge_amount_60d NUMERIC(14,2) DEFAULT 0,
ADD COLUMN IF NOT EXISTS recharge_amount_90d NUMERIC(14,2) DEFAULT 0,
ADD COLUMN IF NOT EXISTS avg_ticket_amount NUMERIC(14,2) DEFAULT 0;
COMMENT ON COLUMN dws.dws_member_consumption_summary.recharge_count_30d
IS '近 30 天充值次数';
COMMENT ON COLUMN dws.dws_member_consumption_summary.recharge_count_60d
IS '近 60 天充值次数';
COMMENT ON COLUMN dws.dws_member_consumption_summary.recharge_count_90d
IS '近 90 天充值次数';
COMMENT ON COLUMN dws.dws_member_consumption_summary.recharge_amount_30d
IS '近 30 天充值金额';
COMMENT ON COLUMN dws.dws_member_consumption_summary.recharge_amount_60d
IS '近 60 天充值金额';
COMMENT ON COLUMN dws.dws_member_consumption_summary.recharge_amount_90d
IS '近 90 天充值金额';
COMMENT ON COLUMN dws.dws_member_consumption_summary.avg_ticket_amount
IS '次均消费额度 = total_consume_amount / MAX(total_visit_count, 1)';
COMMIT;
-- =============================================================================
-- 回滚脚本(如需撤销)
-- =============================================================================
-- ALTER TABLE dws.dws_member_consumption_summary
-- DROP COLUMN IF EXISTS recharge_count_30d,
-- DROP COLUMN IF EXISTS recharge_count_60d,
-- DROP COLUMN IF EXISTS recharge_count_90d,
-- DROP COLUMN IF EXISTS recharge_amount_30d,
-- DROP COLUMN IF EXISTS recharge_amount_60d,
-- DROP COLUMN IF EXISTS recharge_amount_90d,
-- DROP COLUMN IF EXISTS avg_ticket_amount;
-- =============================================================================
-- 验证 SQL
-- =============================================================================
-- 1. 确认新增字段存在
-- SELECT column_name, data_type, column_default
-- FROM information_schema.columns
-- WHERE table_schema = 'dws'
-- AND table_name = 'dws_member_consumption_summary'
-- AND column_name IN (
-- 'recharge_count_30d', 'recharge_count_60d', 'recharge_count_90d',
-- 'recharge_amount_30d', 'recharge_amount_60d', 'recharge_amount_90d',
-- 'avg_ticket_amount'
-- )
-- ORDER BY column_name;
-- 预期7 行
-- 2. 确认字段类型正确
-- SELECT column_name, data_type, numeric_precision, numeric_scale
-- FROM information_schema.columns
-- WHERE table_schema = 'dws'
-- AND table_name = 'dws_member_consumption_summary'
-- AND column_name LIKE 'recharge_amount%'
-- ORDER BY column_name;
-- 预期3 行data_type = numeric, precision = 14, scale = 2
-- 3. 确认注释已设置
-- SELECT c.column_name,
-- pgd.description
-- FROM information_schema.columns c
-- JOIN pg_catalog.pg_statio_all_tables st
-- ON c.table_schema = st.schemaname AND c.table_name = st.relname
-- JOIN pg_catalog.pg_description pgd
-- ON pgd.objoid = st.relid AND pgd.objsubid = c.ordinal_position
-- WHERE c.table_schema = 'dws'
-- AND c.table_name = 'dws_member_consumption_summary'
-- AND c.column_name LIKE 'recharge%'
-- ORDER BY c.column_name;
-- 预期6 行description 非空

View File

@@ -0,0 +1,89 @@
-- =============================================================================
-- 迁移脚本:创建助教订单流水四项统计表
-- 日期2025-02-24
-- 说明:新建 dws.dws_assistant_order_contribution 表,存储每名助教每日的
-- 订单总流水、订单净流水、时效贡献流水、时效净贡献四项统计数据。
-- 需求1.1, 1.2, 1.3, 1.4, 1.5
-- =============================================================================
BEGIN;
-- ---------------------------------------------------------------------------
-- 1. 建表dws.dws_assistant_order_contribution
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS dws.dws_assistant_order_contribution (
contribution_id BIGSERIAL PRIMARY KEY,
site_id INTEGER NOT NULL,
tenant_id INTEGER NOT NULL,
assistant_id BIGINT NOT NULL,
assistant_nickname VARCHAR(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()
);
COMMENT ON TABLE dws.dws_assistant_order_contribution
IS '助教订单流水四项统计表,粒度 (site_id, assistant_id, stat_date)';
COMMENT ON COLUMN dws.dws_assistant_order_contribution.order_gross_revenue
IS '订单总流水 = 台费 + 酒水食品 + 所有助教服务费';
COMMENT ON COLUMN dws.dws_assistant_order_contribution.order_net_revenue
IS '订单净流水 = 订单总流水 - 所有助教服务分成';
COMMENT ON COLUMN dws.dws_assistant_order_contribution.time_weighted_revenue
IS '时效贡献流水 = 台费按时长分摊 + 个人服务费 + 酒水食品按时长比例';
COMMENT ON COLUMN dws.dws_assistant_order_contribution.time_weighted_net_revenue
IS '时效净贡献 = 时效贡献流水 - 个人服务分成';
-- ---------------------------------------------------------------------------
-- 2. 唯一索引:确保 (site_id, assistant_id, stat_date) 唯一
-- ---------------------------------------------------------------------------
CREATE UNIQUE INDEX IF NOT EXISTS idx_aoc_site_assistant_date
ON dws.dws_assistant_order_contribution (site_id, assistant_id, stat_date);
-- ---------------------------------------------------------------------------
-- 3. 查询索引:按门店+日期查询
-- ---------------------------------------------------------------------------
CREATE INDEX IF NOT EXISTS idx_aoc_stat_date
ON dws.dws_assistant_order_contribution (site_id, stat_date);
COMMIT;
-- =============================================================================
-- 回滚脚本(如需撤销)
-- =============================================================================
-- 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;
-- =============================================================================
-- 验证 SQL
-- =============================================================================
-- 1. 确认表存在
-- SELECT table_schema, table_name FROM information_schema.tables
-- WHERE table_schema = 'dws' AND table_name = 'dws_assistant_order_contribution';
-- 预期1 行
-- 2. 确认字段完整
-- SELECT column_name, data_type, column_default
-- FROM information_schema.columns
-- WHERE table_schema = 'dws' AND table_name = 'dws_assistant_order_contribution'
-- ORDER BY ordinal_position;
-- 预期16 行
-- 3. 确认索引存在
-- SELECT indexname FROM pg_indexes
-- WHERE schemaname = 'dws' AND tablename = 'dws_assistant_order_contribution'
-- ORDER BY indexname;
-- 预期3 行PK + idx_aoc_site_assistant_date + idx_aoc_stat_date

View File

@@ -0,0 +1,34 @@
-- =============================================================================
-- RLS 视图:助教订单流水统计 + 已有视图重建(包含新增字段)
-- 目标库test_etl_feiqiu测试/ etl_feiqiu生产
-- 前提dws.dws_assistant_order_contribution 表已创建;
-- dws.dws_member_consumption_summary 已新增充值窗口字段;
-- dws.dws_assistant_daily_detail 已新增惩罚字段
-- =============================================================================
-- -----------------------------------------------------------------------------
-- 1. 新建:助教订单流水统计 RLS 视图
-- -----------------------------------------------------------------------------
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;
GRANT SELECT ON app.v_dws_assistant_order_contribution TO app_reader;
-- -----------------------------------------------------------------------------
-- 2. 重建已有视图(使用 SELECT * 以包含新增字段)
-- 原视图使用显式字段列表,新增字段不会自动暴露
-- -----------------------------------------------------------------------------
CREATE OR REPLACE VIEW app.v_dws_member_consumption_summary AS
SELECT * FROM dws.dws_member_consumption_summary
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_daily_detail AS
SELECT * FROM dws.dws_assistant_daily_detail
WHERE site_id = current_setting('app.current_site_id')::bigint;
-- =============================================================================
-- 回滚脚本
-- =============================================================================
-- DROP VIEW IF EXISTS app.v_dws_assistant_order_contribution;
-- 对于已有视图的回滚,需要用旧的显式字段列表重建(参见 2026-02-24__p1_create_app_schema_rls_views.sql

View File

@@ -0,0 +1,78 @@
-- 迁移:新增 goodsStockWarningInfo 嵌套字段到 ODS 和 DWD
-- 日期2026-02-24
-- 关联:一致性检查报告发现 API 独有字段 goodsStockWarningInfo 未映射
-- 字段来源API store_goods_master -> goodsStockWarningInfo 嵌套对象
-- - sales_day: 销售天数(用于库存预警计算)
-- - warning_day_max: 预警天数上限
-- - warning_day_min: 预警天数下限
-- - site_goods_id / tenant_goods_id: 已有冗余字段,不重复收录
BEGIN;
-- =============================================================================
-- 1. ODS 层ods.store_goods_master 新增 3 列
-- =============================================================================
ALTER TABLE ods.store_goods_master
ADD COLUMN IF NOT EXISTS warning_sales_day NUMERIC(18,2),
ADD COLUMN IF NOT EXISTS warning_day_max INTEGER,
ADD COLUMN IF NOT EXISTS warning_day_min INTEGER;
COMMENT ON COLUMN ods.store_goods_master.warning_sales_day IS
'【说明】库存预警参考的日均销量。 【ODS来源】store_goods_master - goodsStockWarningInfo.sales_day。 【JSON字段】store_goods_master.json - data.orderGoodsList - goodsStockWarningInfo.sales_day。';
COMMENT ON COLUMN ods.store_goods_master.warning_day_max IS
'【说明】库存预警天数上限。 【ODS来源】store_goods_master - goodsStockWarningInfo.warning_day_max。 【JSON字段】store_goods_master.json - data.orderGoodsList - goodsStockWarningInfo.warning_day_max。';
COMMENT ON COLUMN ods.store_goods_master.warning_day_min IS
'【说明】库存预警天数下限。 【ODS来源】store_goods_master - goodsStockWarningInfo.warning_day_min。 【JSON字段】store_goods_master.json - data.orderGoodsList - goodsStockWarningInfo.warning_day_min。';
-- =============================================================================
-- 2. DWD 层dwd.dim_store_goods_ex 新增 3 列
-- =============================================================================
ALTER TABLE dwd.dim_store_goods_ex
ADD COLUMN IF NOT EXISTS warning_sales_day NUMERIC(18,2),
ADD COLUMN IF NOT EXISTS warning_day_max INTEGER,
ADD COLUMN IF NOT EXISTS warning_day_min INTEGER;
COMMENT ON COLUMN dwd.dim_store_goods_ex.warning_sales_day IS
'【说明】库存预警参考的日均销量。 【ODS来源】store_goods_master - goodsStockWarningInfo.sales_day。 【JSON字段】store_goods_master.json - data.orderGoodsList - goodsStockWarningInfo.sales_day。';
COMMENT ON COLUMN dwd.dim_store_goods_ex.warning_day_max IS
'【说明】库存预警天数上限。 【ODS来源】store_goods_master - goodsStockWarningInfo.warning_day_max。 【JSON字段】store_goods_master.json - data.orderGoodsList - goodsStockWarningInfo.warning_day_max。';
COMMENT ON COLUMN dwd.dim_store_goods_ex.warning_day_min IS
'【说明】库存预警天数下限。 【ODS来源】store_goods_master - goodsStockWarningInfo.warning_day_min。 【JSON字段】store_goods_master.json - data.orderGoodsList - goodsStockWarningInfo.warning_day_min。';
COMMIT;
-- =============================================================================
-- 回滚策略
-- =============================================================================
-- 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
-- =============================================================================
-- 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 col_description(
-- (SELECT oid FROM pg_class WHERE relname = 'store_goods_master' AND relnamespace = (SELECT oid FROM pg_namespace WHERE nspname = 'ods')),
-- (SELECT ordinal_position FROM information_schema.columns WHERE table_schema = 'ods' AND table_name = 'store_goods_master' AND column_name = 'warning_sales_day')
-- );
-- 预期:非空

View File

@@ -0,0 +1,21 @@
-- 迁移:清理 ODS_ASSISTANT_ABOLISH 残留元数据
-- 日期2026-02-24
-- 关联:联调报告发现 meta.etl_task 中仍有 ODS_ASSISTANT_ABOLISH 注册,
-- 导致调度器尝试执行已删除的任务并报 ValueError
-- 幂等DELETE WHERE 不存在时无影响
BEGIN;
-- 1. 清理 meta.etl_task 中的 ODS_ASSISTANT_ABOLISH 注册
DELETE FROM meta.etl_task WHERE task_code = 'ODS_ASSISTANT_ABOLISH';
-- 2. 清理 meta.etl_task 中的旧式 ASSISTANT_ABOLISH 注册seed_scheduler_tasks.sql 残留)
DELETE FROM meta.etl_task WHERE task_code = 'ASSISTANT_ABOLISH';
COMMIT;
-- =============================================================================
-- 验证 SQL
-- =============================================================================
-- SELECT * FROM meta.etl_task WHERE task_code IN ('ODS_ASSISTANT_ABOLISH', 'ASSISTANT_ABOLISH');
-- 预期0 行

View File

@@ -0,0 +1,196 @@
-- =============================================================================
-- 迁移脚本:创建 app Schema、RLS 视图层与 app_reader 角色
-- 日期2026-02-24
-- 目标库test_etl_feiqiu通过 PG_DSN 连接)
-- 说明:为 DWD/DWS 层共 35 张表创建带 site_id 行级过滤的 RLS 视图,
-- 供业务库通过 postgres_fdw 只读访问。
-- cfg_* 配置表无 site_id 列,视图直接 SELECT * 不加过滤。
-- dim_member / dim_member_card_account 使用 register_site_id 列过滤。
-- dim_staff_ex 无 site_id 列,视图直接 SELECT * 不加过滤。
-- 需求2.1, 2.2, 2.3, 2.4, 2.7, 2.8, 4.1, 4.3, 4.4, 4.5, 4.6
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. 创建 app Schema
-- ---------------------------------------------------------------------------
CREATE SCHEMA IF NOT EXISTS app;
-- ---------------------------------------------------------------------------
-- 2. 创建 app_reader 只读角色(条件创建)
-- ---------------------------------------------------------------------------
DO $$ BEGIN
IF NOT EXISTS (SELECT FROM pg_roles WHERE rolname = 'app_reader') THEN
CREATE ROLE app_reader LOGIN;
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- 3. DWD 层 RLS 视图11 张,全部含 site_id 过滤)
-- ---------------------------------------------------------------------------
-- dim_member 使用 register_site_id 而非 site_id
CREATE OR REPLACE VIEW app.v_dim_member AS
SELECT * FROM dwd.dim_member
WHERE register_site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dim_assistant AS
SELECT * FROM dwd.dim_assistant
WHERE site_id = current_setting('app.current_site_id')::bigint;
-- dim_member_card_account 使用 register_site_id 而非 site_id
CREATE OR REPLACE VIEW app.v_dim_member_card_account AS
SELECT * FROM dwd.dim_member_card_account
WHERE register_site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dim_table AS
SELECT * FROM dwd.dim_table
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dwd_settlement_head AS
SELECT * FROM dwd.dwd_settlement_head
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dwd_table_fee_log AS
SELECT * FROM dwd.dwd_table_fee_log
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dwd_assistant_service_log AS
SELECT * FROM dwd.dwd_assistant_service_log
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dwd_recharge_order AS
SELECT * FROM dwd.dwd_recharge_order
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dwd_store_goods_sale AS
SELECT * FROM dwd.dwd_store_goods_sale
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dim_staff AS
SELECT * FROM dwd.dim_staff
WHERE site_id = current_setting('app.current_site_id')::bigint;
-- dim_staff_ex 无 site_id 列,直接 SELECT * 不加过滤
CREATE OR REPLACE VIEW app.v_dim_staff_ex AS
SELECT * FROM dwd.dim_staff_ex;
-- ---------------------------------------------------------------------------
-- 4. DWS 层 RLS 视图 — 含 site_id 过滤20 张)
-- ---------------------------------------------------------------------------
CREATE OR REPLACE VIEW app.v_dws_member_consumption_summary AS
SELECT * FROM dws.dws_member_consumption_summary
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_member_visit_detail AS
SELECT * FROM dws.dws_member_visit_detail
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_member_winback_index AS
SELECT * FROM dws.dws_member_winback_index
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_member_newconv_index AS
SELECT * FROM dws.dws_member_newconv_index
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_member_recall_index AS
SELECT * FROM dws.dws_member_recall_index
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_member_assistant_relation_index AS
SELECT * FROM dws.dws_member_assistant_relation_index
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_member_assistant_intimacy AS
SELECT * FROM dws.dws_member_assistant_intimacy
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_daily_detail AS
SELECT * FROM dws.dws_assistant_daily_detail
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_monthly_summary AS
SELECT * FROM dws.dws_assistant_monthly_summary
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_salary_calc AS
SELECT * FROM dws.dws_assistant_salary_calc
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_customer_stats AS
SELECT * FROM dws.dws_assistant_customer_stats
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_finance_analysis AS
SELECT * FROM dws.dws_assistant_finance_analysis
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_finance_daily_summary AS
SELECT * FROM dws.dws_finance_daily_summary
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_finance_income_structure AS
SELECT * FROM dws.dws_finance_income_structure
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_finance_recharge_summary AS
SELECT * FROM dws.dws_finance_recharge_summary
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_finance_discount_detail AS
SELECT * FROM dws.dws_finance_discount_detail
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_finance_expense_summary AS
SELECT * FROM dws.dws_finance_expense_summary
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_platform_settlement AS
SELECT * FROM dws.dws_platform_settlement
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_assistant_recharge_commission AS
SELECT * FROM dws.dws_assistant_recharge_commission
WHERE site_id = current_setting('app.current_site_id')::bigint;
CREATE OR REPLACE VIEW app.v_dws_order_summary AS
SELECT * FROM dws.dws_order_summary
WHERE site_id = current_setting('app.current_site_id')::bigint;
-- ---------------------------------------------------------------------------
-- 5. DWS 层 cfg_* 配置表视图4 张,无 site_id直接 SELECT *
-- ---------------------------------------------------------------------------
CREATE OR REPLACE VIEW app.v_cfg_performance_tier AS
SELECT * FROM dws.cfg_performance_tier;
CREATE OR REPLACE VIEW app.v_cfg_assistant_level_price AS
SELECT * FROM dws.cfg_assistant_level_price;
CREATE OR REPLACE VIEW app.v_cfg_bonus_rules AS
SELECT * FROM dws.cfg_bonus_rules;
CREATE OR REPLACE VIEW app.v_cfg_index_parameters AS
SELECT * FROM dws.cfg_index_parameters;
-- ---------------------------------------------------------------------------
-- 6. P2 预留(待 P2 完成后取消注释并创建视图)
-- ---------------------------------------------------------------------------
-- TODO [P2] dws.dws_member_spending_power_index → app.v_dws_member_spending_power_index
-- TODO [P2] dws.dws_assistant_order_contribution → app.v_dws_assistant_order_contribution
-- ---------------------------------------------------------------------------
-- 7. 授权app_reader 对 app Schema 的只读访问
-- ---------------------------------------------------------------------------
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;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- 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;

View File

@@ -9,7 +9,7 @@ task_codes AS (
-- Must match tasks/ods_tasks.py (ENABLED_ODS_CODES)
'ODS_ASSISTANT_ACCOUNT',
'ODS_ASSISTANT_LEDGER',
'ODS_ASSISTANT_ABOLISH',
-- CHANGE [2026-02-24] intent: 移除 ODS_ASSISTANT_ABOLISH(全链路已清理,表已删除)
'ODS_SETTLEMENT_RECORDS',
'ODS_TABLE_USE',
'ODS_PAYMENT',

View File

@@ -14,7 +14,7 @@ WITH target_store AS (
),
task_codes AS (
SELECT unnest(ARRAY[
'ASSISTANT_ABOLISH',
-- CHANGE [2026-02-24] intent: 移除 ASSISTANT_ABOLISH(全链路已清理)
'ASSISTANTS',
'COUPON_USAGE',
'CHECK_CUTOFF',

164
db/zqyy_app/README.md Normal file
View File

@@ -0,0 +1,164 @@
# db/zqyy_app — 业务数据库
`zqyy_app` 是 NeoZQYY 的业务数据库,存储用户认证、任务队列、调度配置、执行日志等数据。
测试库:`test_zqyy_app`(开发和测试环境默认连接)。
## Schema 架构
| Schema | 用途 | 状态 |
|--------|------|------|
| `auth` | 用户认证与权限(微信用户、角色、权限、申请、绑定) | 已建表 |
| `biz` | 业务数据(预留,未来存储门店业务数据) | 已创建,待建表 |
| `public` | 管理后台admin_users、task_queue、scheduled_tasks、task_execution_log | 已建表 |
## auth Schema — 认证系统8 张表)
### auth.users — 微信用户主表
| 字段 | 类型 | 说明 |
|------|------|------|
| id | SERIAL PK | 自增主键 |
| wx_openid | VARCHAR(128) UNIQUE | 微信 OpenID |
| wx_union_id | VARCHAR(128) | 微信 UnionID |
| wx_avatar_url | VARCHAR(512) | 头像 URL |
| nickname | VARCHAR(50) | 昵称 |
| phone | VARCHAR(20) | 手机号 |
| status | VARCHAR(20) | 状态pending / approved / rejected / disabled |
| created_at | TIMESTAMPTZ | 创建时间 |
| updated_at | TIMESTAMPTZ | 更新时间 |
### auth.site_code_mapping — 球房ID与门店映射
| 字段 | 类型 | 说明 |
|------|------|------|
| id | SERIAL PK | 自增主键 |
| site_code | VARCHAR(10) UNIQUE | 球房ID格式2字母+3数字如 AB123 |
| site_id | BIGINT UNIQUE | 门店 ID对应 ETL 库的 site_id |
| site_name | VARCHAR(100) | 门店名称 |
| tenant_id | INT | 租户 ID |
### auth.roles — 角色定义
| 字段 | 类型 | 说明 |
|------|------|------|
| id | SERIAL PK | 自增主键 |
| code | VARCHAR(50) UNIQUE | 角色编码 |
| name | VARCHAR(100) | 角色名称 |
| description | TEXT | 描述 |
预置角色:
| code | name | 权限 |
|------|------|------|
| `coach` | 助教 | view_tasks, view_board_coach |
| `staff` | 员工 | view_tasks, view_board |
| `site_admin` | 店铺管理员 | 全部 5 个权限 |
| `tenant_admin` | 租户管理员 | 全部 5 个权限 |
### auth.permissions — 权限定义
| 字段 | 类型 | 说明 |
|------|------|------|
| id | SERIAL PK | 自增主键 |
| code | VARCHAR(50) UNIQUE | 权限编码 |
| name | VARCHAR(100) | 权限名称 |
| description | TEXT | 描述 |
预置权限:
| code | name |
|------|------|
| `view_tasks` | 查看任务 |
| `view_board` | 查看看板 |
| `view_board_finance` | 查看财务看板 |
| `view_board_customer` | 查看客户看板 |
| `view_board_coach` | 查看助教看板 |
### auth.role_permissions — 角色-权限关联
联合主键 `(role_id, permission_id)`,外键级联删除。
### auth.user_applications — 用户入驻申请
| 字段 | 类型 | 说明 |
|------|------|------|
| id | SERIAL PK | 自增主键 |
| user_id | INT FK | 关联 auth.users |
| site_code | VARCHAR(10) | 球房ID |
| site_id | BIGINT | 门店 ID后端自动填充 |
| applied_role_text | VARCHAR(100) | 申请身份文本 |
| employee_number | VARCHAR(50) | 员工编号 |
| phone | VARCHAR(20) | 手机号 |
| status | VARCHAR(20) | pending / approved / rejected |
| reviewer_id | INT | 审核人 |
| review_note | TEXT | 审核备注 |
| created_at | TIMESTAMPTZ | 申请时间 |
| reviewed_at | TIMESTAMPTZ | 审核时间 |
### auth.user_site_roles — 用户-门店-角色关联
| 字段 | 类型 | 说明 |
|------|------|------|
| id | SERIAL PK | 自增主键 |
| user_id | INT FK | 关联 auth.users |
| site_id | BIGINT | 门店 ID |
| role_id | INT FK | 关联 auth.roles |
唯一约束:`(user_id, site_id, role_id)` — 同一用户在同一门店下不能重复分配同一角色。
### auth.user_assistant_binding — 用户-人员绑定
| 字段 | 类型 | 说明 |
|------|------|------|
| id | SERIAL PK | 自增主键 |
| user_id | INT FK | 关联 auth.users |
| site_id | BIGINT | 门店 ID |
| assistant_id | BIGINT | 助教 IDETL 库) |
| staff_id | BIGINT | 员工 IDETL 库) |
| binding_type | VARCHAR(20) | 绑定类型 |
## public Schema — 管理后台
### admin_users — 管理后台用户
用于管理后台(`apps/admin-web/`)的用户名密码登录。
默认种子数据:`admin / admin123`(生产环境部署后务必修改)。
### task_queue — 任务执行队列
存储待执行和执行中的 ETL 任务,按 `site_id` 隔离。
### scheduled_tasks — 调度任务配置
存储定时调度规则,由后端 `Scheduler` 服务消费。
### task_execution_log — 任务执行日志
记录每次 ETL 任务执行的状态、耗时、日志输出。
## FDW 跨库访问
`zqyy_app` 通过 Foreign Data WrapperFDW只读访问 `etl_feiqiu` 数据库:
- 迁移脚本:`2026-02-24__p1_setup_fdw_etl.sql`
- 用途:小程序认证时的人员匹配(查询 ETL 库中的助教/员工记录)
- 安全FDW 连接使用只读用户
## 目录结构
```
db/zqyy_app/
├── migrations/ # 迁移脚本(日期前缀)
│ ├── 2026-02-24__p1_create_auth_biz_schemas.sql # 创建 auth + biz Schema
│ ├── 2026-02-24__p1_setup_fdw_etl.sql # 设置 FDW 跨库访问
│ ├── 2026-02-25__p3_create_auth_tables.sql # 创建 auth 8 张表
│ └── 2026-02-25__p3_seed_roles_permissions.sql # 预置角色和权限
├── schemas/ # Schema DDL待补充
├── seeds/
│ └── admin_web_seed.sql # 管理后台默认管理员
├── scripts/
│ └── create_test_db.sql # 创建测试库脚本
└── README.md
```
## 迁移执行顺序
1. `p1_create_auth_biz_schemas.sql` — 创建 Schema
2. `p1_setup_fdw_etl.sql` — 设置 FDW
3. `p3_create_auth_tables.sql` — 创建认证表
4. `p3_seed_roles_permissions.sql` — 插入种子数据
所有迁移脚本使用 `IF NOT EXISTS` / `ON CONFLICT DO NOTHING` 幂等语法,可重复执行。
## 与其他模块的关系
- `apps/backend/` — 通过 `get_connection()` 读写此库
- `apps/miniprogram/` — 通过后端 API 间接访问
- `apps/admin-web/` — 通过后端 API 间接访问
- `db/etl_feiqiu/` — 通过 FDW 被此库只读引用

View File

@@ -0,0 +1,53 @@
-- =============================================================================
-- 迁移脚本:创建 auth/biz Schema 与 app_user 权限配置
-- 日期2026-02-24
-- 目标库test_zqyy_app通过 APP_DB_DSN 连接)
-- 说明:在业务库中创建 auth用户认证和 biz业务数据两个 Schema
-- 并授予 app_user 角色完整的 CRUD 权限(含未来新表自动授权)。
-- 不操作 public Schema保留其中现有系统管理表不受影响。
-- 前提app_user 角色已由 DBA 预创建
-- 需求1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 4.2, 4.3, 4.4, 4.5, 4.6
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. 创建 auth Schema用户认证、权限、映射
-- ---------------------------------------------------------------------------
CREATE SCHEMA IF NOT EXISTS auth;
-- ---------------------------------------------------------------------------
-- 2. 创建 biz Schema业务数据
-- ---------------------------------------------------------------------------
CREATE SCHEMA IF NOT EXISTS biz;
-- ---------------------------------------------------------------------------
-- 3. 授予 app_user 对 auth Schema 的 USAGE + CRUD 权限
-- ---------------------------------------------------------------------------
GRANT USAGE ON SCHEMA auth TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA auth TO app_user;
-- ---------------------------------------------------------------------------
-- 4. 授予 app_user 对 biz Schema 的 USAGE + CRUD 权限
-- ---------------------------------------------------------------------------
GRANT USAGE ON SCHEMA biz TO app_user;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA biz TO app_user;
-- ---------------------------------------------------------------------------
-- 5. 设置 ALTER DEFAULT PRIVILEGES未来新表自动授权
-- ---------------------------------------------------------------------------
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;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- 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;

View File

@@ -0,0 +1,71 @@
-- =============================================================================
-- 迁移脚本:配置 FDW 跨库映射ETL 库 → 业务库)
-- 日期2026-02-24
-- 目标库test_zqyy_app通过 APP_DB_DSN 连接)
-- 说明:通过 postgres_fdw 将 ETL 库 app Schema 的 RLS 视图映射为业务库
-- fdw_etl Schema 的只读外部表,使后端无需直连 ETL 库即可读取汇总/维度数据。
-- 前提:
-- 1. ETL 库已部署 app Schema 及 RLS 视图2026-02-24__p1_create_app_schema_rls_views.sql
-- 2. ETL 库已创建 app_reader 只读角色
-- 3. 业务库已创建 app_user 角色
-- 需求3.1, 3.2, 3.3, 3.4, 3.7, 4.2, 4.3, 4.4, 4.5, 4.6
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. 安装 postgres_fdw 扩展
-- ---------------------------------------------------------------------------
CREATE EXTENSION IF NOT EXISTS postgres_fdw;
-- ---------------------------------------------------------------------------
-- 2. 创建外部服务器(指向 ETL 库)
-- host / dbname / port 使用占位符 '***',部署时按环境替换
-- 服务器名使用通用名(不含环境前缀),通过连接参数区分环境
-- ---------------------------------------------------------------------------
CREATE SERVER IF NOT EXISTS etl_feiqiu_server
FOREIGN DATA WRAPPER postgres_fdw
OPTIONS (host '***', dbname '***', port '***');
-- ---------------------------------------------------------------------------
-- 3. 创建用户映射(只读角色)
-- app_user = test_zqyy_app 侧的应用连接角色
-- app_reader = ETL 库侧的只读角色
-- 密码占位符 '***',部署时替换为真实凭据
-- ---------------------------------------------------------------------------
CREATE USER MAPPING IF NOT EXISTS FOR app_user
SERVER etl_feiqiu_server
OPTIONS (user 'app_reader', password '***');
-- ---------------------------------------------------------------------------
-- 4. 创建 fdw_etl Schema幂等处理先 DROP 再重建)
-- IMPORT FOREIGN SCHEMA 不是幂等的(外部表已存在会报错),
-- 因此采用 DROP CASCADE + 重建的方式确保可重复执行。
-- ---------------------------------------------------------------------------
DROP SCHEMA IF EXISTS fdw_etl CASCADE;
CREATE SCHEMA fdw_etl;
-- ---------------------------------------------------------------------------
-- 5. 批量导入 ETL 库 app Schema 的所有外部表到 fdw_etl
-- ---------------------------------------------------------------------------
IMPORT FOREIGN SCHEMA app
FROM SERVER etl_feiqiu_server
INTO fdw_etl;
-- ---------------------------------------------------------------------------
-- 6. 授权:允许 app_user 访问 fdw_etl Schema 及其外部表
-- ---------------------------------------------------------------------------
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;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- 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;

View File

@@ -0,0 +1,280 @@
-- =============================================================================
-- 迁移脚本:创建 auth Schema 认证业务表
-- 日期2026-02-25
-- 目标库test_zqyy_app通过 APP_DB_DSN 连接)
-- 说明:在 auth Schema 下创建用户认证系统所需的 8 张业务表:
-- users、user_applications、site_code_mapping、roles、permissions、
-- role_permissions、user_site_roles、user_assistant_binding
-- 包含字段定义、约束、索引、外键。使用 IF NOT EXISTS 幂等语法。
-- 前提auth Schema 已由 P1 迁移脚本创建
-- 2026-02-24__p1_create_auth_biz_schemas.sql
-- 需求1.1, 1.2, 1.3, 1.4, 1.5, 1.6, 1.7, 1.8, 1.9, 1.10
-- =============================================================================
-- 确保 auth Schema 存在(幂等)
CREATE SCHEMA IF NOT EXISTS auth;
-- ---------------------------------------------------------------------------
-- 1. users — 微信用户主表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.users (
id SERIAL PRIMARY KEY,
wx_openid VARCHAR(128) NOT NULL,
wx_union_id VARCHAR(128),
wx_avatar_url VARCHAR(512),
nickname VARCHAR(50),
phone VARCHAR(20),
status VARCHAR(20) NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- wx_openid 唯一约束(幂等:先检查再添加)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_users_wx_openid' AND conrelid = 'auth.users'::regclass
) THEN
ALTER TABLE auth.users ADD CONSTRAINT uq_users_wx_openid UNIQUE (wx_openid);
END IF;
END $$;
-- 索引
CREATE INDEX IF NOT EXISTS ix_users_wx_openid ON auth.users (wx_openid);
-- ---------------------------------------------------------------------------
-- 2. site_code_mapping — 球房ID与门店映射表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.site_code_mapping (
id SERIAL PRIMARY KEY,
site_code VARCHAR(10) NOT NULL,
site_id BIGINT NOT NULL,
site_name VARCHAR(100),
tenant_id INT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- site_code 唯一约束格式2字母+3数字如 AB123
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_site_code_mapping_site_code' AND conrelid = 'auth.site_code_mapping'::regclass
) THEN
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT uq_site_code_mapping_site_code UNIQUE (site_code);
END IF;
END $$;
-- site_id 唯一约束
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_site_code_mapping_site_id' AND conrelid = 'auth.site_code_mapping'::regclass
) THEN
ALTER TABLE auth.site_code_mapping ADD CONSTRAINT uq_site_code_mapping_site_id UNIQUE (site_id);
END IF;
END $$;
-- 索引
CREATE INDEX IF NOT EXISTS ix_site_code_mapping_site_code ON auth.site_code_mapping (site_code);
-- ---------------------------------------------------------------------------
-- 3. roles — 角色定义表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.roles (
id SERIAL PRIMARY KEY,
code VARCHAR(50) NOT NULL,
name VARCHAR(100),
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_roles_code' AND conrelid = 'auth.roles'::regclass
) THEN
ALTER TABLE auth.roles ADD CONSTRAINT uq_roles_code UNIQUE (code);
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- 4. permissions — 权限定义表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.permissions (
id SERIAL PRIMARY KEY,
code VARCHAR(50) NOT NULL,
name VARCHAR(100),
description TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_permissions_code' AND conrelid = 'auth.permissions'::regclass
) THEN
ALTER TABLE auth.permissions ADD CONSTRAINT uq_permissions_code UNIQUE (code);
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- 5. role_permissions — 角色-权限关联表(联合主键)
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.role_permissions (
role_id INT NOT NULL,
permission_id INT NOT NULL,
PRIMARY KEY (role_id, permission_id)
);
-- 外键(幂等添加)
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_role_permissions_role_id' AND conrelid = 'auth.role_permissions'::regclass
) THEN
ALTER TABLE auth.role_permissions
ADD CONSTRAINT fk_role_permissions_role_id
FOREIGN KEY (role_id) REFERENCES auth.roles (id) ON DELETE CASCADE;
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_role_permissions_permission_id' AND conrelid = 'auth.role_permissions'::regclass
) THEN
ALTER TABLE auth.role_permissions
ADD CONSTRAINT fk_role_permissions_permission_id
FOREIGN KEY (permission_id) REFERENCES auth.permissions (id) ON DELETE CASCADE;
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- 6. user_applications — 用户入驻申请表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.user_applications (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
site_code VARCHAR(10) NOT NULL,
site_id BIGINT,
applied_role_text VARCHAR(100) NOT NULL,
employee_number VARCHAR(50),
phone VARCHAR(20) NOT NULL,
status VARCHAR(20) NOT NULL DEFAULT 'pending',
reviewer_id INT,
review_note TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
reviewed_at TIMESTAMPTZ
);
-- 外键
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_user_applications_user_id' AND conrelid = 'auth.user_applications'::regclass
) THEN
ALTER TABLE auth.user_applications
ADD CONSTRAINT fk_user_applications_user_id
FOREIGN KEY (user_id) REFERENCES auth.users (id) ON DELETE CASCADE;
END IF;
END $$;
-- 索引
CREATE INDEX IF NOT EXISTS ix_user_applications_user_id ON auth.user_applications (user_id);
CREATE INDEX IF NOT EXISTS ix_user_applications_status ON auth.user_applications (status);
-- ---------------------------------------------------------------------------
-- 7. user_site_roles — 用户-门店-角色关联表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.user_site_roles (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
site_id BIGINT NOT NULL,
role_id INT NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 外键
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_user_site_roles_user_id' AND conrelid = 'auth.user_site_roles'::regclass
) THEN
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;
END IF;
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_user_site_roles_role_id' AND conrelid = 'auth.user_site_roles'::regclass
) THEN
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;
END IF;
END $$;
-- 唯一约束:同一用户在同一门店下不能重复分配同一角色
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'uq_user_site_roles_user_site_role' AND conrelid = 'auth.user_site_roles'::regclass
) THEN
ALTER TABLE auth.user_site_roles
ADD CONSTRAINT uq_user_site_roles_user_site_role UNIQUE (user_id, site_id, role_id);
END IF;
END $$;
-- 索引
CREATE INDEX IF NOT EXISTS ix_user_site_roles_user_site ON auth.user_site_roles (user_id, site_id);
-- ---------------------------------------------------------------------------
-- 8. user_assistant_binding — 用户-人员绑定表
-- ---------------------------------------------------------------------------
CREATE TABLE IF NOT EXISTS auth.user_assistant_binding (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
site_id BIGINT NOT NULL,
assistant_id BIGINT,
staff_id BIGINT,
binding_type VARCHAR(20) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
-- 外键
DO $$
BEGIN
IF NOT EXISTS (
SELECT 1 FROM pg_constraint
WHERE conname = 'fk_user_assistant_binding_user_id' AND conrelid = 'auth.user_assistant_binding'::regclass
) THEN
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;
END IF;
END $$;
-- ---------------------------------------------------------------------------
-- 授予 app_user 对新表的 SEQUENCE 使用权限SERIAL 字段需要)
-- ---------------------------------------------------------------------------
GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA auth TO app_user;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- 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;

View File

@@ -0,0 +1,75 @@
-- =============================================================================
-- 种子数据脚本:预置权限列表、默认角色、角色-权限映射
-- 日期2026-02-25
-- 目标库test_zqyy_app通过 APP_DB_DSN 连接)
-- 说明:在 auth Schema 的 permissions、roles、role_permissions 表中插入种子数据。
-- 使用 ON CONFLICT DO NOTHING 幂等语法,重复执行不会产生重复数据。
-- 前提auth.roles、auth.permissions、auth.role_permissions 表已由
-- 2026-02-25__p3_create_auth_tables.sql 创建
-- 需求2.1, 2.2, 2.3, 2.4
-- =============================================================================
-- ---------------------------------------------------------------------------
-- 1. 插入固定权限5 条)
-- ---------------------------------------------------------------------------
INSERT INTO auth.permissions (code, name, description) VALUES
('view_tasks', '查看任务', '允许查看任务列表和任务详情'),
('view_board', '查看看板', '允许查看数据看板概览'),
('view_board_finance', '查看财务看板', '允许查看财务相关的数据看板'),
('view_board_customer', '查看客户看板', '允许查看客户相关的数据看板'),
('view_board_coach', '查看助教看板', '允许查看助教相关的数据看板')
ON CONFLICT (code) DO NOTHING;
-- ---------------------------------------------------------------------------
-- 2. 插入默认角色4 条)
-- ---------------------------------------------------------------------------
INSERT INTO auth.roles (code, name, description) VALUES
('coach', '助教', '球房助教,可查看任务和助教看板'),
('staff', '员工', '球房员工,可查看任务和数据看板'),
('site_admin', '店铺管理员', '单店管理员,可查看所有看板'),
('tenant_admin', '租户管理员', '租户级管理员,拥有全部权限')
ON CONFLICT (code) DO NOTHING;
-- ---------------------------------------------------------------------------
-- 3. 插入角色-权限映射
-- coach: view_tasks, view_board_coach
-- staff: view_tasks, view_board
-- site_admin: view_tasks, view_board, view_board_finance, view_board_customer, view_board_coach
-- tenant_admin: 全部 5 个权限
-- ---------------------------------------------------------------------------
INSERT INTO auth.role_permissions (role_id, permission_id)
SELECT r.id, p.id
FROM auth.roles r
CROSS JOIN auth.permissions p
WHERE (r.code, p.code) IN (
-- coach: 2 个权限
('coach', 'view_tasks'),
('coach', 'view_board_coach'),
-- staff: 2 个权限
('staff', 'view_tasks'),
('staff', 'view_board'),
-- site_admin: 5 个权限
('site_admin', 'view_tasks'),
('site_admin', 'view_board'),
('site_admin', 'view_board_finance'),
('site_admin', 'view_board_customer'),
('site_admin', 'view_board_coach'),
-- tenant_admin: 5 个权限
('tenant_admin', 'view_tasks'),
('tenant_admin', 'view_board'),
('tenant_admin', 'view_board_finance'),
('tenant_admin', 'view_board_customer'),
('tenant_admin', 'view_board_coach')
)
ON CONFLICT (role_id, permission_id) DO NOTHING;
-- =============================================================================
-- 回滚脚本(按逆序执行)
-- =============================================================================
-- 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');