Files
Neo-ZQYY/docs/guides/FRONTEND-BACKEND-INTEGRATION.md
Neo 6f8f12314f feat: 累积功能变更 — 聊天集成、租户管理、小程序更新、ETL 增强、迁移脚本
包含多个会话的累积代码变更:
- backend: AI 聊天服务、触发器调度、认证增强、WebSocket、调度器最小间隔
- admin-web: ETL 状态页、任务管理、调度配置、登录优化
- miniprogram: 看板页面、聊天集成、UI 组件、导航更新
- etl: DWS 新任务(finance_area_daily/board_cache)、连接器增强
- tenant-admin: 项目初始化
- db: 19 个迁移脚本(etl_feiqiu 11 + zqyy_app 8)
- packages/shared: 枚举和工具函数更新
- tools: 数据库工具、报表生成、健康检查
- docs: PRD/架构/部署/合约文档更新

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-06 00:03:48 +08:00

13 KiB
Raw Blame History

前后端联调规范手册

最后更新2026-04-01 适用范围:小程序(apps/miniprogram/)与 FastAPI 后端(apps/backend/)的联调 约束力:高度建议参考。特殊需求可偏离但需注明原因。 速查索引:.kiro/steering/frontend-backend-integration.mdfileMatch 自动加载)


一、数据契约

1.1 ResponseWrapper

后端 ResponseWrapperMiddleware 自动包装所有 2xx JSON 响应为 {"code": 0, "data": <原始body>}。脚本/测试调用 API 时必须从 resp.json()["data"] 取实际数据,不能直接用 resp.json()(踩坑记录 2026-03-28

1.2 字段命名与 CamelCase 转换

  • 后端统一 snake_casePydantic CamelModel 自动转 camelCase 输出
  • 前端用 camelCase 读取,兼容写法:rec.memberId ?? rec.member_id ?? 0
  • 踩坑:me.avatar 应为 me.avatarUrlavatar_url 转换后)

1.3 条件显示放后端

字段需要根据业务条件决定显示/隐藏时,后端满足条件返回值、不满足返回 null。前端只判 wx:if="{{field}}",禁止做数值比较。

组件 property 收到 null 不走默认值,传入前必须清洗:r.durationRaw ?? 0r.drinks ?? ''

1.4 Schema 与 Service 同步规则

Pydantic response schema 必须与 service 返回字段严格一致,否则 500。排查 500 优先检查:

  1. 数据库迁移是否已执行
  2. Schema 字段是否与 service 一致
  3. 环境变量是否缺失
  4. 鉴权依赖是否匹配

踩坑记录汇总Pydantic 系列)

问题 日期 表现 规则
静默丢弃未声明字段 2026-03-28 新增字段(descdiscount_total)前端收不到 新增后端返回字段时必须同步更新 Schema
list[dict] 不转换内部 key 2026-03-28 前端拿到 snake_case key 用 CamelModel 子类替代 dict,或 service 层手动递归转换
Optional 与 None 不同步 2026-03-29 service 返回 None 但 Schema 非 Optional → 500 修改 service 返回 None 时必须同步 Schema 为 `XxxModel
response_model 类型不匹配 2026-03-29 Schema 期望 list[CoachSkillItem] 但返回 list[str] → 500 修改 service 返回字段时必须同步检查 Schema 类型
嵌套 CamelModel 字段名不匹配 2026-03-29 dict key 与 Schema 字段名不一致 → 500 _build_* 方法必须逐字段对照 Schema
含数字字段名转换 2026-03-29 consumption_60dconsumption60D(大写 D 前端用 d.consumption60D ?? d.consumption60d 兼容

1.5 接口返回结构

// 列表:{ "records": [...], "total_count": 42, "has_more": true }
// 概览:{ "coach_name": "...", "income_items": [...], "this_month_records": [...] }

收入明细始终返回所有项(即使为 0前端不做过滤。


二、展示映射

2.1 头像颜色

后端只传 member_id + avatar_char,前端 nameToAvatarColor(String(memberId)) 计算颜色 key。散客member_id ≤ 0)→ 'default'(灰色),avatar_char 返回 "?"

2.2 课程标签

后端返回数据库原始 skill_name中文WXSS 不支持中文类名,前端映射:

const COURSE_TAG_MAP: Record<string, string> = {
  '陪打': 'basic', '基础课': 'basic',
  '包厢': 'room', '包厢课': 'room',
  '超休': 'incentive', '激励课': 'incentive', '打赏课': 'incentive',
}

2.3 角色标签

后端返回英文 codecoach/staff/head_coach/manager),前端 ROLE_LABELS 映射中文。助教拼接等级:${coachLevel}助教

2.4 助教姓名显示规则2026-03-28

所有助教必须使用昵称/花名(nickname),不得显示真实姓名(real_name。SQL 中统一用 COALESCE(da.nickname, da.real_name, '')nickname 优先。

2.5 助教离职过滤2026-03-29

所有查询助教的 SQL 必须加 da.leave_status = 0 过滤离职助教。LEFT JOIN 场景用 (da.leave_status IS NULL OR da.leave_status = 0) 兼容无匹配行。dim_assistantleave_status=1 为离职(占 74%),不过滤会展示大量已离职助教。

2.6 预估规则(全小程序统一)

当月且当前日期 ≤ 5号时显示"预估"标签。业务含义:每月 1-5 日为工资审核期。

2.7 WXSS 类名拼接

后端值用于 class="xxx-{{value}}" 时必须是合法 CSS 标识符(纯英文),禁止中文/hex/特殊字符。

2.8 前端筛选枚举同步(踩坑记录 2026-03-28

前端下拉/筛选组件的 value 必须与后端 Enum 值完全一致(大小写、命名)。修改后端枚举时,必须全局搜索所有使用该枚举的前端页面同步更新。

2.9 WXS 格式化规范

  • WXS 数值方法防护:format.wxs 中所有调用 .toFixed() 的函数必须先 parseFloat 转数字
  • TS 与 WXS 格式化互斥2026-03-27WXML 用 {{fmt.money(field)}}TS 层 setData 必须传原始数字(?? 0),禁止 formatMoney() 预格式化
  • 后端 service 层同理2026-03-29前端用 WXS 格式化的字段后端必须返回原始数字float/int禁止预格式化为字符串。Schema 类型需同步从 str 改为 float/int
  • 环比显示统一规范2026-03-28所有环比文本必须使用 fmt.compareText(compare, isDown) + fmt.compareClass(compare, isDown, size) WXS 函数
  • WXS 零值语义2026-03-27value === 0 不一定是空值。days(0) 表示"今天到店"是有效值。新增 WXS 函数时必须区分"零值有业务含义"和"零值等同空值"

三、SQL 查询规范

3.1 ETL 连接复用(必须)

同一 service 方法内创建一个 etl_conn 传给所有 fdw_queries.*

3.2 多页面复用组件时 SQL 统一

后端为同一组件提供数据的查询必须同口径(共享 JOIN/聚合),各页面只负责过滤和分页。

3.3 一对多明细用 LATERAL 避免行膨胀

LEFT JOIN LATERAL (
    SELECT string_agg(gs.ledger_name || '×' || gs.total_count, '、') AS drinks
    FROM (...GROUP BY ledger_name) gs
) gs_agg ON true

3.4 助教收入口径

  • sl.ledger_amount:行级毛收入,不是到手
  • 到手:hours × (base_course_price - base_deduction)
  • sh.assistant_pd_money + cx_money:整单级汇总,禁止 JOIN 到行级

3.5 快照值 vs 流量值2026-03-27

  • 流量值(充值/消费/收入):可以 SUM
  • 快照值(卡余额 cash_card_balance/gift_card_balance/total_card_balance):日末快照,多天聚合取最后一天的值,禁止 SUM

3.6 dim_member_card_account 多卡膨胀2026-03-29

同一 tenant_member_idscd2_is_current=1 下可能有多条记录(多张卡),必须先 GROUP BY tenant_member_id + SUM(balance) 聚合后再 JOIN。

3.7 台费原价推算2026-03-29

台费正价 = table_charge_money + adjust_amountadjust_amount > 0 时显示原价删除线。

3.8 环比同期对比规则2026-03-28

当前周期 end_date cap 到今天,上期取同类周期的同期天数。已完成周期取完整周期对比再上一个完整周期。

3.9 优惠拆分恒等式2026-03-28

discount_total = discount_groupbuy + discount_manual + discount_other + discount_vip + discount_gift_card + discount_rounding。拆分展示必须包含全部 6 个子字段。

3.10 团购金额双口径2026-03-28

DWS groupbuy_pay_amount 是团购交易金额,daily_revenue_report.pysale_price × 0.75 估算回款。财务看板用 DWS 口径,经营报告用估算回款口径。

3.11 助教财务三列口径2026-03-28

pay/share/hourly 全部从 DWS dws_assistant_salary_calc 计算。禁止从 DWD ledger_amount 取客户支付再 JOIN DWS 等级(同一助教同月可有多等级记录导致行膨胀)。

3.12 客源储值口径(纠正 2026-03-29

客源储值 = 该助教关联客户的卡余额合计,不是充值提成。数据源:v_dws_member_assistant_relation_indexsession_count > 0+ v_dim_member_card_account(先 GROUP BY 聚合)。

3.13 DATE_TRUNC 返回 timestamp 不是 date2026-03-29

用作 dict key 时必须先 .date() 转换。

3.14 关系指数表字段名2026-03-29

dws_member_assistant_relation_index 的字段是 session_count(非 service_counttotal_duration_minutes(非 total_hoursml_allocated_amount(非 total_income

3.15 dim_table 主键列名2026-03-29

主键是 table_id(不是 site_table_id。JOIN 时用 ON s.site_table_id = dt.table_id


四、数据库操作陷阱

4.1 psycopg2 Windows GBK 编码2026-03-29

psycopg2.connect() 用关键字参数时libpq 拼接系统 locale 信息触发 UnicodeDecodeError。解决:用显式 DSN 字符串 + client_encoding=UTF8 + os.environ.setdefault("PGCLIENTENCODING", "UTF8")

4.2 软删除 + ON CONFLICT

ON CONFLICT DO UPDATE SET is_removed=false,禁止 DO NOTHING

4.3 ON CONFLICT 精确匹配 partial unique index

列列表和 WHERE 条件必须与索引定义完全对应。

4.4 跨库过滤分页

先构建排除列表SQL 层统一过滤,禁止内存过滤修正 total。

4.5 共享连接子流程

需事务隔离时用独立连接,避免异常被外层 except 吞掉。

4.6 FDW 查询禁止 N+1 串行2026-03-29

必须改为 WHERE assistant_id = ANY(%s) 批量查询 + Python 层 dict 映射。

4.7 FDW 查询必须传 etl_conn2026-03-29

所有 _build_* 子函数都应接收并传递 etl_conn,否则 _fdw_context 在业务库连接上设置 RLS 导致空结果。


五、前端交互规范

5.1 页面加载态2026-03-29

统一用 wx.showLoading / wx.hideLoading 原生遮罩,禁止自定义加载组件。

5.2 跨页面跳转

URL 参数必须包含可查询 IDmemberId/taskId),不能只传展示字段。

5.3 dataset 同步

TS e.currentTarget.dataset.xxx 必须与 WXML data-xxx 属性同步。

5.4 自定义组件静默不渲染2026-03-29

未在页面 JSON usingComponents 中注册的组件不报错但完全不渲染。

5.5 多状态列表页

后端按 status 过滤,前端需并行请求所有需要展示的状态并合并。

5.6 微信头像

chooseAvatar 组件 + wx.uploadFile 上传服务器,临时路径会过期。

5.7 Vite 代理

跨前缀接口必须在 vite.config.ts proxy 中添加规则。

5.8 权限守卫

fallback 目标页必须对当前用户无条件可访问,避免跳转死循环。

5.9 前后端权限一致性

后端 /api/xcx/me 返回 permissions[],前端根据权限码动态控制可见性。新增权限码只需更新数据库 auth.role_permissions

5.10 custom-tab-bar 异步刷新

更新 globalData.visibleTabs 后必须主动调用 tabBar._refreshTabs()。看板 boardTabs 刷新必须放在 checkPageAccess().then() 回调中。

5.11 禁止页面滚动2026-03-28

wx.setPageScrollEnabled 不存在。用 <page-meta page-style="overflow:hidden" /> 控制。

5.12 登录与 /me 接口字段差异2026-03-28

/api/xcx/login 返回 WxLoginResponse(不含 permissions/api/xcx/me 返回 UserStatusResponse(含 permissions。登录后需权限码时必须额外请求 /me

5.13 分页接口 API 函数必须返回完整响应2026-03-29

必须返回完整 data(含 items/total/page/pageSize),禁止只返回 data.items

5.14 懒加载分页追加必须去重 + 双条件判底2026-03-29

① 追加数据按 id 去重(Set 过滤);② hasMore 用双条件:items.length >= pageSize && merged.length < total


六、事件驱动触发器

  • biz.trigger_jobstrigger_condition='event' 的任务必须有明确的 fire_event(event_name) 发射端
  • 任务引擎全流程由 ETL 任务 DWS_TASK_ENGINE 编排,通过 HTTP 调用后端 POST /api/internal/run-job
  • task_expiry_check 的 interval 触发器保留(每小时),DWS_TASK_ENGINE 是补充而非替代
  • recall_detector 直接生成回访任务CHANGE 2026-03-31
  • 任务生成器使用客户级别升级/转移CHANGE 2026-03-31基于 t_v / ideal_interval ratio 决定分配范围
  • 任务统计写入CHANGE 2026-03-31task_generator.run() 末尾调用 _update_task_stats()

七、ETL 调用后端 API 注意事项

  • ETL 的 AppConfig 不设置 os.environETL 任务中用 os.environ.get() 前必须先 load_dotenv
  • 后端 ResponseWrapperMiddleware 包装响应ETL 调用时需从 resp.json()["data"] 取数据

八、列表与分页

  • 客户列表:默认 5 条,展开最多 20 条
  • 服务记录按日期分组DateGroup默认 2 组,可展开
  • 新客:本月有服务但之前无历史
  • 常客:本月服务 ≥ 2 次,展示近 90 天聚合