# 后端架构文档 — FastAPI 服务层 > 更新日期:2026-03-19(RNS1.3 三看板接口 + CONFIG-1 技能类型) > 位置:`apps/backend/` > 框架:FastAPI + psycopg2(同步连接池) > 数据库:`zqyy_app`(业务库)+ `etl_feiqiu`(ETL 库,直连只读访问) --- ## 1. 模块交互总览 ```mermaid graph TB subgraph "微信小程序" MP[miniprogram pages] end subgraph "管理后台" ADMIN[admin-web] end subgraph "FastAPI 后端" subgraph "路由层 (routers/)" R_AUTH[xcx_auth] R_TASKS[xcx_tasks] R_NOTES[xcx_notes] R_PERF[xcx_performance] R_CUST[xcx_customers
CUST-1, CUST-2] R_COACH[xcx_coaches
COACH-1] R_CHAT[xcx_ai_chat] R_CACHE[xcx_ai_cache] R_ADMIN[admin_*] end subgraph "服务层 (services/)" S_TM[task_manager
任务 CRUD + 列表扩展] S_PS[performance_service
绩效概览 + 明细] S_NS[note_service
备注 CRUD + 评分] S_CS[customer_service
客户详情 + 服务记录] S_CO[coach_service
助教详情] S_FDW[fdw_queries
FDW 查询集中封装] S_TG[task_generator
任务自动生成] S_TE[task_expiry
过期检测] S_RD[recall_detector
召回完成检测] S_WX[wechat
微信登录/Token] end subgraph "中间件 + 认证" MW[ResponseWrapperMiddleware] AUTH[JWT + require_approved] end end subgraph "数据库" BIZ[(zqyy_app
biz / auth / public)] ETL[(etl_feiqiu
直连 app.v_*)] end MP --> R_AUTH & R_TASKS & R_NOTES & R_PERF & R_CUST & R_COACH & R_CHAT ADMIN --> R_ADMIN R_TASKS --> S_TM R_PERF --> S_PS R_NOTES --> S_NS R_CUST --> S_CS R_COACH --> S_CO R_CHAT --> S_TM S_TM --> S_FDW S_PS --> S_FDW S_CS --> S_FDW S_CO --> S_FDW S_TM --> BIZ S_PS --> BIZ S_NS --> BIZ S_CS --> BIZ S_CO --> BIZ S_FDW --> ETL style S_FDW fill:#9f9,stroke:#333 style S_PS fill:#9f9,stroke:#333 style R_PERF fill:#9f9,stroke:#333 style S_CS fill:#9f9,stroke:#333 style S_CO fill:#9f9,stroke:#333 style R_CUST fill:#9f9,stroke:#333 style R_COACH fill:#9f9,stroke:#333 ``` ## 2. 路由模块清单 | 路由文件 | 前缀 | 说明 | |----------|------|------| | `xcx_auth.py` | `/api/xcx/auth` | 微信登录、Token 刷新、入驻申请 | | `xcx_tasks.py` | `/api/xcx/tasks` | 任务列表(TASK-1)、详情(TASK-2)、pin/unpin、abandon/restore | | `xcx_notes.py` | `/api/xcx/notes` | 备注 CRUD(含 score 评分) | | `xcx_performance.py` | `/api/xcx/performance` | 绩效概览(PERF-1)、明细(PERF-2) | | `xcx_customers.py` | `/api/xcx/customers` | 客户详情(CUST-1)、客户服务记录(CUST-2)(RNS1.2 新增) | | `xcx_coaches.py` | `/api/xcx/coaches` | 助教详情(COACH-1)(RNS1.2 新增) | | `xcx_board.py` | `/api/xcx/board` | 助教看板(BOARD-1)、客户看板(BOARD-2)、财务看板(BOARD-3)(RNS1.3 新增) | | `xcx_config.py` | `/api/xcx/config` | 技能类型配置(CONFIG-1)(RNS1.3 新增) | | `xcx_ai_chat.py` | `/api/xcx/ai` | AI 对话(SSE 流式) | | `xcx_ai_cache.py` | `/api/xcx/ai-cache` | AI 缓存查询 | | `admin_applications.py` | `/api/admin/applications` | 入驻审核 | | `auth.py` | `/api/auth` | 管理后台登录 | | `member_retention_clue.py` | `/api/member-retention-clue` | 维客线索 | | `ops_panel.py` | `/api/ops` | 运维面板 | | `business_day.py` | `/api/business-day` | 营业日配置 | ## 3. 服务层模块清单 | 服务文件 | 职责 | 数据源 | |----------|------|--------| | `task_manager.py` | 任务 CRUD、`get_task_list_v2()`、`get_task_detail()` | biz.coach_tasks + FDW | | `performance_service.py` | `get_overview()`、`get_records()`,绩效汇总与明细 | FDW | | `note_service.py` | 备注创建(含 score)、AI 占位、回访触发 | biz.notes | | `customer_service.py` | 客户详情(CUST-1)、客户服务记录(CUST-2)(RNS1.2 新增) | biz.coach_tasks + biz.ai_cache + biz.notes + public.member_retention_clue + FDW | | `coach_service.py` | 助教详情(COACH-1):绩效/收入/任务分组/TOP客户/历史月份(RNS1.2 新增) | biz.coach_tasks + biz.notes + FDW | | `fdw_queries.py` | ETL RLS 视图查询集中封装,直连 ETL 库 + `SET LOCAL app.current_site_id` 门店隔离 | app.v_* (ETL 直连) | | `task_generator.py` | 定时任务:基于 WBI/NCI/RS 指数自动生成助教任务 | biz + FDW | | `task_expiry.py` | 定时任务:检测过期任务并标记 inactive | biz.coach_tasks | | `recall_detector.py` | 事件驱动:ETL 数据更新后检测召回完成 | biz + FDW | | `note_reclassifier.py` | 事件驱动:召回完成后回溯重分类备注 | biz.notes | | `wechat.py` | 微信 code2session、Token 管理 | 外部 API | | `role.py` | 角色权限查询 | auth.* | | `scheduler.py` | 触发器调度引擎 | biz.trigger_jobs | | `board_service.py` | 三看板编排:`get_coach_board()`、`get_customer_board()`、`get_finance_board()`,含日期范围/环比/排序/分页/降级(RNS1.3 新增) | FDW + biz.coach_tasks | | `application.py` | 入驻申请处理 | auth.applications | ## 4. FDW 查询封装(fdw_queries.py) 所有跨库查询集中在此模块,确保 DWD-DOC 强制规则统一实施。 ⚠️ 架构变更(2026-03-18):不再使用 zqyy_app 的 `fdw_etl.*` foreign table,改为通过 `get_etl_readonly_connection(site_id)` 直连 ETL 库查询 `app.v_*` RLS 视图。原因:`postgres_fdw` 不传递自定义 GUC 参数到远端连接,导致 RLS 视图的 `current_setting('app.current_site_id')` 在远端未设置而报错。 所有函数通过 `_fdw_context()` 上下文管理器:创建 ETL 直连 → `SET LOCAL app.current_site_id` → 执行查询 → 关闭连接。 | 函数 | 用途 | DWD-DOC 规则 | |------|------|-------------| | `get_member_info()` | 批量查询会员姓名/手机号 | DQ-6:JOIN v_dim_member | | `get_member_balance()` | 批量查询储值卡余额 | DQ-7:JOIN v_dim_member_card_account | | `get_last_visit_days()` | 批量查询距上次到店天数 | 废单排除:is_delete = 0 | | `get_salary_calc()` | 助教绩效/档位/收入 | 收入用 gross_salary;费用用 base_income + bonus_income | | `get_service_records()` | 服务记录明细(分页) | 收入用 ledger_amount + DQ-6 + 废单排除 | | `get_service_records_for_task()` | 特定客户服务记录 | 同上 | | `get_consumption_60d()` | 客户近 60 天消费金额(RNS1.2 新增) | 收入用 ledger_amount(items_sum)+ 废单排除 | | `get_relation_index()` | 客户与助教关系指数列表(RNS1.2 新增) | 来源 v_dws_member_assistant_relation_index | | `get_consumption_records()` | 客户消费记录嵌套查询(RNS1.2 新增) | items_sum + DWD-DOC 规则 2 + 废单排除 | | `get_total_service_count()` | 客户累计服务总次数(RNS1.2 新增) | 废单排除:is_delete = 0 | | `get_coach_60d_stats()` | 助教对客户近 60 天统计(RNS1.2 新增) | 废单排除:is_delete = 0 | | `get_assistant_info()` | 助教基本信息(RNS1.2 新增) | 来源 v_dim_assistant | | `get_salary_calc_multi_months()` | 批量多月绩效数据(RNS1.2 新增) | 来源 v_dws_assistant_salary_calc | | `get_monthly_customer_count()` | 各月不重复客户数(RNS1.2 新增) | 废单排除:is_delete = 0 | | `get_coach_top_customers()` | 助教 TOP 客户(RNS1.2 新增) | DQ-6 + DQ-7 + items_sum + 废单排除 | | `get_customer_service_records()` | 客户按月服务记录(RNS1.2 新增) | DQ-6 + items_sum + 废单排除 | | `get_all_assistants()` | BOARD-1:按技能筛选助教列表(RNS1.3 新增) | v_dim_assistant | | `get_salary_calc_batch()` | BOARD-1:批量查询当期/上期绩效(RNS1.3 新增) | items_sum + assistant_pd/cx_money | | `get_top_customers_for_coaches()` | BOARD-1:按亲密度 Top3 客户 + 四级 emoji(RNS1.3 新增) | DQ-6 + v_dws_member_assistant_relation_index | | `get_coach_sv_data()` | BOARD-1:助教客源储值数据(RNS1.3 新增) | v_dws_assistant_monthly_summary | | `get_customer_board_recall()` | BOARD-2:召回维度(WBI 降序)(RNS1.3 新增) | v_dws_member_winback_index + DQ-6 | | `get_customer_board_potential()` | BOARD-2:潜力维度(SPI 降序)(RNS1.3 新增) | v_dws_member_spending_power_index | | `get_customer_board_balance()` | BOARD-2:余额维度(RNS1.3 新增) | v_dim_member_card_account + DQ-7 | | `get_customer_board_recharge()` | BOARD-2:充值维度(RNS1.3 新增) | v_dwd_recharge_order | | `get_customer_board_recent()` | BOARD-2:最近到店维度(RNS1.3 新增) | v_dws_member_visit_detail | | `get_customer_board_spend60()` | BOARD-2:60 天消费维度(RNS1.3 新增) | items_sum 口径 | | `get_customer_board_freq60()` | BOARD-2:60 天频次维度 + weeklyVisits(RNS1.3 新增) | v_dws_member_consumption_summary | | `get_customer_board_loyal()` | BOARD-2:忠诚度维度(RS 降序)(RNS1.3 新增) | v_dws_member_assistant_relation_index | | `get_customer_assistants()` | BOARD-2:批量查询客户关联助教(RNS1.3 新增) | 含亲密度 + 当前跟进置顶 | | `get_finance_overview()` | BOARD-3:经营一览 8 项指标(RNS1.3 新增) | v_dws_finance_daily_summary + items_sum | | `get_finance_recharge()` | BOARD-3:预收资产(储值卡 + 赠送卡矩阵)(RNS1.3 新增) | v_dws_finance_recharge_summary | | `get_finance_revenue()` | BOARD-3:应计收入结构(RNS1.3 新增) | v_dws_finance_income_structure + discount_detail | | `get_finance_cashflow()` | BOARD-3:现金流入(RNS1.3 新增) | v_dws_finance_daily_summary + 互斥规则 7 | | `get_finance_expense()` | BOARD-3:现金流出 4 子分组(RNS1.3 新增) | v_dws_finance_expense_summary + platform_settlement | | `get_finance_coach_analysis()` | BOARD-3:助教分析(basic + incentive)(RNS1.3 新增) | v_dws_assistant_salary_calc | | `get_skill_types()` | CONFIG-1:技能类型配置(RNS1.3 新增) | ETL cfg 表 | ### 列名映射(design.md 理想名 → 实际 FDW 视图列名) RLS 视图直接暴露 DWD/DWS 原始列名,后端代码在 SQL 中使用 AS 别名转换。 **v_dwd_assistant_service_log**(基于 `dwd.dwd_assistant_service_log`,非 _ex 表): | 代码语义 | 实际列名 | 说明 | |----------|----------|------| | id | `assistant_service_id` | 服务记录主键 | | assistant_id (WHERE) | `site_assistant_id` | 与 salary_calc.assistant_id 同源 | | member_id | `tenant_member_id` | 会员 ID | | is_trash = false | `is_delete = 0` | 整数类型,0=正常 | | settle_time | `create_time` | 结算时间 | | start_time | `start_use_time` | 开始使用时间 | | end_time | `last_use_time` | 最后使用时间 | | service_hours | `income_seconds / 3600.0` | 折算工时(计算字段) | | service_hours_raw | `real_use_seconds / 3600.0` | 原始工时(计算字段) | | income (items_sum) | `ledger_amount` | 收入金额 | | course_type | `skill_name` | 课程类型 | | table_name | `site_table_id` | 仅有台桌 ID,无名称 | **v_dws_assistant_salary_calc**: | 代码语义 | 实际列名 | 说明 | |----------|----------|------| | calc_month | `salary_month` | date 类型,存储为 YYYY-MM-01 | | coach_level | `assistant_level_name` | 档位名称 | | tier_index | `tier_id` | 档位 ID | | basic_hours | `base_hours` | 基础课时 | | total_hours | `effective_hours` | 有效总工时 | | total_income | `gross_salary` | 总收入 | | basic_rate | `base_course_price` | 基础课单价 | | incentive_rate | `bonus_course_price` | 激励课单价 | | bonus_money | `sprint_bonus` | 冲刺奖金 | | assistant_pd_money_total | `base_income` | 基础课总收入 | | assistant_cx_money_total | `bonus_income` | 激励课总收入 | > 注意:`tier_nodes`、`total_customers`、`next_tier_*`、`tier_completed` 在视图中不存在,后端使用默认值或从其他数据源推算。 ## 5. 数据流向 ``` 小程序请求 → JWT 认证 (require_approved) → 路由层 (routers/) → 服务层 (services/) → 业务库 (zqyy_app) 直连 → ETL 库 (etl_feiqiu) 直连只读(app.v_* RLS 视图) → 响应包装 (ResponseWrapperMiddleware) → { code: 0, data: ..., message: "ok" } ``` ## 6. 关键设计决策 - **ETL 直连**:所有 ETL 查询封装在 `fdw_queries.py`,通过 `_fdw_context()` 直连 ETL 库查询 `app.v_*` RLS 视图(不使用 FDW foreign table,因 postgres_fdw 不传递自定义 GUC) - **优雅降级**:扩展字段(lastVisitDays/balance/aiSuggestion)查询失败返回 null,不影响核心响应 - **camelCase 转换**:所有小程序端响应通过 `CamelModel` 自动转换为 camelCase - **门店隔离**:业务库通过 `site_id` 参数过滤,ETL 查询通过 `SET LOCAL app.current_site_id` + RLS 视图隔离