30 KiB
数据库总览 — 表 × 功能 × 关系
跨表视角。单表字段级细节看同目录
<表名>.md(索引见 README)。 本文专门回答三件「跨表」的事:① 每块 App 功能用到哪些表 ② 什么操作往哪张表写 ③ 表和表怎么连(join key,含没有外键约束、靠业务字段对齐的语义关联)。 范围:业务表全部在shaguabijia-app-server(SQLAlchemy 2.0 + SQLite 开发 / PostgreSQL 生产)。pricebot-backend(比价/领券 Agent)是纯内存态、无任何表;Android 客户端只有 EncryptedSharedPreferences / SharedPreferences、无关系库。共 45 张业务表 +alembic_version(框架的迁移版本指针)。注意「比价/领券过程」始终在 pricebot 内存态跑、不落库——只有结果回 app-server 才落库:领券结果落coupon_*三张今日状态表 +coupon_session任务流水(#99);比价记录 2026-07 起由 app-server 透传壳 harvest 直接落库(comparison_record:帧0 建 running 行 → done/finalize 写终态,老客户端带 JWT 的POST /compare/record仅作兜底),pricebot 另经app/api/internal/server→server 把客观价格/门店事实落price_observation/store_mapping(不鉴权、匿名也记)+ 启动确认窗兜底样本落launch_confirm_sample。此外好友邀请(invite_*2 张 + 独立账本invite_cash_transaction)与 CPS 群发联盟(cps_*6 张,含落地页微信身份cps_wx_user;cps_order已含京东联盟单)是两个独立子系统。无障碍存活监控device_liveness(#65)按用户设备维度记心跳、检掉线召回;客户端埋点落analytics_event(#83)。
一、按功能域看表(每张表用在 App 哪里)
比价(核心业务)
| App 位置 / 动作 | 表 | 说明 |
|---|---|---|
| 比价/领券过程(看屏→决策→操作) | (无) | 在 pricebot-backend 内存态跑,过程不落库;只有结果回到 app-server 才落库 |
| 「我的比价记录」列表 / 详情 | comparison_record |
app-server 透传壳 harvest 落库(2026-07 起):帧0 建 running 行 → done 帧写 success/failed → trace/finalize 写 cancelled/failed;老客户端带 JWT 的 POST /compare/record 兜底 |
| 比价战绩里程碑(逐档领金币) | comparison_milestone_claim |
累计成功比价 N 次解锁;进度读 comparison_record 计数 |
| profile「累计省了 / 省钱战绩 / 省钱明细」 | savings_record |
真实下单归因(source=compare)+ 无真实数据时 demo 兜底 |
| 「上报更低价」提交 / 列表 | price_report |
众包纠偏:用户举证某平台更便宜,人工审核发奖 |
| (无 App UI)比价 done 后客观价格沉淀 | price_observation |
pricebot server→server 内部上报;平台/门店视角的到手价事实,匿名也记,与 comparison_record(用户视角)互补 |
| (无 App UI)跨平台门店身份映射 | store_mapping |
pricebot 内部上报;同店在淘宝/美团/京东的 id/名/deeplink,供下次比价 lookup 反查省掉现场搜店 |
| (无 App UI)启动确认窗兜底样本 | launch_confirm_sample |
pricebot 内部上报;LLM 兜底放行「想要打开 XX」跨 App 启动窗后回写样本(host 包 + 弹窗树 + plan + locale),供研发人工沉淀进 pricebot 规则 |
领券(每日领券联动 · 今日状态)
| App 位置 / 动作 | 表 | 说明 |
|---|---|---|
| 领券过程(看屏→领券) | (无) | 在 pricebot-backend 内存态跑,过程不落库;结果回 app-server 才落下面三张表 |
| 切外卖 App 时是否弹领券引导窗 | coupon_prompt_engagement |
今天 engage 过(点领/点拒)就不再弹;判断维度 device_id |
| 首页「去领取」卡是否置灰 | coupon_daily_completion |
今天跑完整轮(到 done)就置灰;判断维度 device_id |
| 每张券领取结果留痕 | coupon_claim_record |
资产/画像/排查/CPS;当前不参与判断 |
| (admin 领券看板)一次领券任务全程流水 | coupon_session |
客户端 POST /coupon/session 两段上报(发起建行/收尾更新);发起数、完成率、中途流失、平均耗时都从这算(#99) |
钱包 / 福利(看广告赚钱闭环)
| App 位置 / 动作 | 表 | 说明 |
|---|---|---|
| 资产卡 / 钱包余额 | coin_account |
一用户一行的金币+现金余额快照 |
| 金币明细 | coin_transaction |
每次金币变动一笔流水 |
| 现金明细 | cash_transaction |
每次现金变动一笔流水(分) |
| 每日签到 | signin_record + signin_boost_record |
7 天循环发币;签到后看广告可膨胀一次 |
| 一次性任务(开消息提醒等) | user_task |
领一次发币 |
| 看激励视频赚金币 | ad_reward_record + ad_watch_log + ad_ecpm_record |
独立数据流:发奖 / 旧版观看时长 / 收益对账 |
| 信息流/Draw 广告结算 | ad_feed_reward_record |
每展示满 10 秒累计一份奖励,完成后一次性入账;ad_type(feed/draw)+feed_scene(compare/coupon)分形态/场景 |
| (无 App UI)穿山甲后台收益对账 | ad_pangle_daily_revenue |
定时脚本拉 GroMore 数据 API 落日表(#92);admin 收益报表/大盘的「真实收益」侧 |
| 好友比价并下单发奖 / 邀请奖励金 | invite_cash_transaction + coin_account.invite_cash_balance_cents |
与现金物理隔离的第二本账(#82/#113):POST /order/report 触发 invite_reward 入账;source=invite_cash 提现出账 |
| 金币兑现金 | coin_account + coin_transaction + cash_transaction |
exchange_out + exchange_in 两笔流水 |
| 提现到微信零钱 | withdraw_order + wechat_transfer_authorization + cash_transaction |
人工审核 + 微信商家转账 |
| 绑定微信(提现前置) | user.wechat_* |
openid 唯一,一微信一账号 |
账号 / 反馈
| App 位置 / 动作 | 表 | 说明 |
|---|---|---|
| 登录(极光/短信)/ 改资料 / 注销 | user |
登录主体,注册即登录 |
| 新手引导是否再展示 | onboarding_completion |
按 设备+账号 去重;登录响应回 onboarding_completed,走完引导时标记,跨卸载重装 |
| 帮助与反馈 | feedback |
含截图,后台人工处理 |
| 无障碍存活监控 / 掉线召回 | device_liveness |
无障碍服务周期心跳;worker 检出心跳超时 → 推送/客户端进 App 弹「开启自启动」引导 |
好友邀请(注册增长)
| App 位置 / 动作 | 表 | 说明 |
|---|---|---|
| 输入/剪贴板邀请码绑定 | invite_relation |
注册即生效(绑定不发奖,#113);invitee_user_id 唯一;compare_reward_granted 幂等闸=好友比价并下单后才给邀请人发奖励金 |
| 落地页访问指纹(剪贴板归因兜底) | invite_fingerprint |
剪贴板没拿到码时,用 (ip+机型+屏幕) 7 天内反查邀请人 |
| 邀请奖励金入账/提现 | invite_cash_transaction |
独立账本(见上钱包节);余额在 coin_account.invite_cash_balance_cents |
美团 CPS 群发联盟(私域社群比价,运营后台驱动 · 群发选品→点击→对账漏斗)
| 后台/用户动作 | 表 | 说明 |
|---|---|---|
| 运营建可推广活动 | cps_activity |
预存券/物料(美团 actId / 淘宝淘口令 / 京东链接) |
| 运营建微信推广群 | cps_group |
一群一行,sid=美团二级渠道追踪位 |
| 后台批量生成群发短链 | cps_link |
群×活动 → /c/{code},运营复制到微信群 |
| 用户点短链(落地/复制口令) | cps_click |
记 visit/copy 事件,统计 PV/UV |
| 微信内打开落地页授权 | cps_wx_user |
服务号网页授权拿 openid(base 静默)/ 昵称头像 unionid(userinfo,点领券触发),记首次来源群 |
| 定时拉美团联盟订单对账 | cps_order |
query_order 按 sid 归群,串成点击→下单→佣金漏斗 |
埋点 / 首页门面 / 选品缓存
| 位置 / 动作 | 表 | 说明 |
|---|---|---|
| 客户端行为埋点 | analytics_event |
POST /analytics/events 批量上报,一事件一行(#83);admin「埋点日志」检索 |
| 首页三统计数字 | ops_stat_config |
real/manual/random 三模式,admin 配 |
| 首页轮播(省钱动态) | ops_marquee_seed |
种子条目,与真实 comparison_record 混播(数据源模式 mixed/real/seed 可切) |
| 首页推荐/销量榜离线选品 | meituan_coupon |
美团 CPS 券缓存,定时 ETL 灌入;feed?tab=rec 与 top-sales 纯库出、不实时打美团 |
运营后台 admin(独立子应用 app/admin/,端口 8771,独立鉴权)
| 后台模块 | 表 | 说明 |
|---|---|---|
| 管理员账号 / 登录 | admin_user |
与 C 端 user 完全隔离,独立 JWT + RBAC;pages_override 个人可见页覆盖 |
| 角色 / 可见页配置 | admin_role |
内建三角色 + 自定义角色(#117/#126);admin_user.role 按名引用 |
| 操作审计 | admin_audit_log |
每个写操作落一条,只增不改不删 |
| 运营可配置项(改奖励常量) | app_config |
空表 = 用代码默认;后台改了即覆盖 |
| 用户/钱包/提现/反馈管理 | 跨读写上面的 C 端表 | 见下「写入路径」admin 段 |
二、写入路径(什么操作 → 写哪张表)
C=插入新行 · U=更新已有行 · 同一行内多张表表示同一事务原子写。
C 端(App 用户触发)
| 触发(用户动作 / endpoint / 回调) | 写入 | 操作 |
|---|---|---|
登录 POST /auth/jverify-login、/auth/sms/login |
user |
C(首次=注册)/ U(last_login_at);并读 onboarding_completion 回 onboarding_completed |
走完新手引导 POST /user/onboarding/complete |
onboarding_completion(C) |
(user_id, device_id) 幂等,撞唯一约束即忽略 |
改昵称 PATCH /user/profile、传头像 POST /user/avatar |
user |
U |
注销 DELETE /user |
user |
U(软删:phone→deleted_<id>、status=deleted) |
绑/解绑微信 POST /wallet/bind-wechat、/unbind-wechat |
user.wechat_* |
U |
签到 POST /signin/do |
signin_record(C) + coin_account(U) + coin_transaction(C signin) |
同事务 |
签到膨胀 POST /signin/boost |
signin_boost_record(C) + coin_account(U) + coin_transaction(C signin_boost) |
同事务;同日一次 |
领任务 POST /tasks/claim |
user_task(C) + coin_account(U) + coin_transaction(C task_<key>) |
同事务 |
金币兑现金 POST /wallet/exchange |
coin_account(U) + coin_transaction(C exchange_out −) + cash_transaction(C exchange_in +) |
同事务 |
发起提现 POST /wallet/withdraw |
withdraw_order(C reviewing,记 source) + coin_account(U 按 source 扣对应余额) + 流水(C −:cash_transaction.withdraw 或 invite_cash_transaction.invite_withdraw) |
同事务,不打款;#121 按 source 分账 |
查提现状态 / 用户取消 GET /wallet/withdraw/status |
withdraw_order(U) + 失败→对应账本退款流水(C withdraw_refund / invite_withdraw_refund +) |
|
穿山甲发奖 S2S 回调 POST /ad/pangle-callback |
ad_reward_record(C)+ granted→coin_account(U)+coin_transaction(C reward_video/signin_boost) |
trans_id 幂等 |
看广告时长上报 POST /ad/watch-report |
ad_watch_log(C) |
|
广告 eCPM 上报 POST /ad/ecpm-report |
ad_ecpm_record(C) |
|
信息流广告结算 POST /ad/feed-reward |
ad_feed_reward_record(C)+ granted→coin_account(U)+coin_transaction(C feed_ad_reward) |
client_event_id 幂等 |
注册设备 / 更新 push token POST /device/register |
device_liveness(C/U upsert) |
(user_id, device_id) 幂等;只在传入非空时更新 registration_id/版本 |
无障碍服务心跳 POST /device/heartbeat |
device_liveness(C/U) |
心跳也能自注册;accessibility_enabled=true 时刷 last_heartbeat_at、置 alive、清 notified_at |
客户端 ack 掉线提醒 POST /device/liveness/ack |
device_liveness(U) |
清 kill_alert_pending(幂等;下次真掉线 worker 再置) |
比价透传(帧0 / done / finalize)POST /intent/recognize、/price/step、/trace/finalize |
comparison_record(C running → U 终态) |
harvest 三段式(2026-07):帧0 mint trace_id 建 running 行 → done 写 success/failed → finalize 写 cancelled/failed(不降级 success);best-effort,写失败不连累透传 |
比价 done 上报 POST /compare/record(老客户端兜底) |
comparison_record(C 或 U) |
trace_id 幂等覆盖(唯一键已从 (user_id,trace_id) 改单列) |
领里程碑 POST /compare/milestone/claim |
comparison_milestone_claim(C) |
当前不发币(coin_awarded=0) |
支付归因上报 POST /order/report |
savings_record(C source=compare)+ 触发邀请发奖:invite_relation(U compare_reward_granted)+coin_account(U invite_cash)+invite_cash_transaction(C invite_reward) |
(user_id, client_event_id) 幂等;#113 邀请发奖口径=好友比价并下单,compare_reward_granted 幂等闸,同事务 |
批量埋点 POST /analytics/events |
analytics_event(C 批量) |
不强制登录;每批 ≤200 条 |
领券任务流水 POST /coupon/session |
coupon_session(C/U upsert) |
trace_id 幂等:发起建行、收尾更新同一行(#99) |
重置新手引导 POST /user/onboarding/reset |
onboarding_completion(D 该 设备+账号 行) |
#114,删完成标记 → 下次登录重走引导 |
| 首次进 profile 省钱页且无真实记录 | savings_record(C source=demo) |
懒种子,ensure_seeded 按 user 幂等 |
上报更低价 POST /report |
price_report(C) |
读 comparison_record.best_price_cents 校验 |
提交反馈 POST /feedback |
feedback(C) |
|
绑定邀请 POST /invite/bind |
invite_relation(C effective) |
invitee_user_id 唯一幂等。#113 起绑定不发奖——发奖延后到好友比价并下单(POST /order/report 行),发的是邀请奖励金非金币 |
落地页归因 POST /invite/landing-track |
invite_fingerprint(C) |
剪贴板归因兜底线索,登录后用 (ip+机型+屏幕) 反查 |
领券首帧 POST /api/v1/coupon/step(step=0) |
coupon_prompt_engagement(C/U claim_started) |
(device_id, package, 北京日) 幂等;best-effort |
领券每帧结果 POST /api/v1/coupon/step |
coupon_claim_record(C/U) |
(device_id, coupon_id, 北京日) 幂等;best-effort |
领券跑完 POST /api/v1/coupon/step(action.command=done) |
coupon_daily_completion(C/U) |
(device_id, 北京日) 幂等;best-effort |
拒绝领券引导窗 POST /api/v1/coupon/prompt/dismiss |
coupon_prompt_engagement(C/U dismissed) |
同上;客户端通知(透传链路看不到拒绝) |
重置今日弹窗 POST /api/v1/coupon/prompt/reset(开发) |
coupon_prompt_engagement(D 今日条) |
删后今天又能弹 |
领券三表写库全 best-effort:
/coupon/step里写失败只logger.warning、不连累领券返回;判断只看device_id,user_id可空旁路(资产留痕)。
admin 端(管理员触发,均额外写一条 admin_audit_log)
| 后台操作 | 写入 | 操作 |
|---|---|---|
| 手动增减金币 | coin_account(U)+coin_transaction(C admin_grant/admin_deduct)+admin_audit_log(C) |
同事务 |
| 手动增减现金(#95 支持目标账户) | coin_account(U 按 account 选列)+ 流水(C:cash_transaction 或 invite_cash_transaction,admin_grant/admin_deduct)+admin_audit_log(C) |
同事务;account=coin_cash/invite_cash 两本账各调各 |
| 改用户状态(禁用/启用)/ 调试链接权限 | user(U status / debug_trace_enabled)+admin_audit_log(C) |
同事务 |
| 审核通过提现(含批量) | withdraw_order(U→pending/success/failed)+wechat_transfer_authorization(C/U)+失败时按 source 退款流水+admin_audit_log(C) |
|
| 审核拒绝提现(含批量) | withdraw_order(U→rejected)+按 source 退款流水(C withdraw_refund/invite_withdraw_refund)+admin_audit_log(C) |
|
| 反馈采纳/拒绝/标记处理 | feedback(U→adopted/rejected/handled + admin_reply/reject_reason)+采纳发币时 coin_account(U)+coin_transaction(C)+admin_audit_log(C) |
同事务(#94/#105) |
| 上报更低价审核 | price_report(U→approved/rejected)+通过发币 coin_account(U)+coin_transaction(C)+admin_audit_log(C) |
同事务 |
| 改运营配置 / 广告配置 / 反馈页二维码 | app_config(C/U)+admin_audit_log(C) |
同事务 |
| 轮播种子/数据源模式 | ops_marquee_seed(C/U/D)+app_config(模式 mixed/real/seed)+admin_audit_log(C) |
#122/#123 预览/浏览只读 |
| 角色管理(#117/#126) | admin_role(C/U/D)+admin_audit_log(C) |
内建/在用角色不可删 |
| 管理员管理 | admin_user(C/U/D #126)+admin_audit_log(C) |
删除为物理删,不可删自己 |
| CPS 建群/活动/生成短链/拉单对账 | cps_group/cps_activity(C/U/D)、cps_link(C)、cps_order(C/U upsert;美团 query_order + 京东联盟 #90) |
见 CPS 段 |
| 任意写操作 | admin_audit_log(C,永不 U/D) |
没有任何表会被业务流程物理 DELETE。注销是软删(改 user 行),其余只 C/U(领券
/prompt/reset、/completed-today/reset是开发用删除,非业务流程)。
CPS 群发联盟 & pricebot 内部上报(非 C 端用户、非 admin 常规管理触发)
| 触发 | 写入 | 操作 |
|---|---|---|
| 运营后台建活动 / 建群 | cps_activity(C/U) / cps_group(C/U) |
app/admin/routers/cps.py;群含美团平台才分配 sid |
后台批量生成短链 POST /admin/api/cps/referral-links |
cps_link(C) |
每 群×活动 一条;美团经 sid 转链拿 target_url(同群同活动重复生成产生多条) |
用户点群发短链 GET /c/{code} / POST /c/{code}/copy |
cps_click(C visit/copy) |
公开端点不鉴权;group_id/sid 从 link 冗余进来免 join |
微信落地页授权回调 GET /wx/oauth/cb |
cps_wx_user(C/U upsert) |
按 openid 幂等;base 拿 openid,userinfo 补昵称/unionid(非 None 才覆盖);任何失败兜底回落地页不阻断领券 |
定时拉联盟订单对账(美团 query_order + 京东联盟 #90) |
cps_order(C/U upsert) |
按 sid 归群;order_id 幂等(状态会变,重复拉则更新);京东单 platform='jd' + jd_valid_code 判有效 |
| 定时拉穿山甲 GroMore 后台收益(#92,systemd 每天 10:30) | ad_pangle_daily_revenue(C/U upsert) |
收益报表/大盘「真实收益」侧;与客户端上报的 eCPM 侧互为对照 |
| 定时 ETL 灌美团 CPS 选品库 | meituan_coupon(C/U) |
feed?tab=rec / top-sales 纯库出的数据源 |
pricebot 比价 done 内部上报 POST /internal/price-observation |
price_observation(C 批量) |
(trace_id,platform,scope) 幂等;不走 JWT、靠 X-Internal-Secret(未配→503) |
pricebot 比价 done 内部上报 POST /internal/store-mapping |
store_mapping(C/U 填空合并) |
trace_id 幂等;另有 lookup 反查 + invalidate deeplink 失效标记(淘宝/京东) |
pricebot LLM 兜底放行启动确认窗后上报 POST /internal/launch-confirm-sample |
launch_confirm_sample(C) |
都上报、不去重;靠 X-Internal-Secret(未配→503) |
后台 worker 检出心跳超时 heartbeat_monitor_worker |
device_liveness(U mark_notified) |
置 notified + notified_at + kill_alert_pending=True;非 C 端、非 admin,定时扫 list_overdue |
CPS 6 表与本 App
user无关(被推广群的用户在美团下单 / 在微信落地页授权,不是本 App 注册用户;cps_wx_user是微信openid维度的另一套用户身份);price_observation/store_mapping/launch_confirm_sample的source_user_id/device_id是可空旁路(链路不鉴权,匿名也记)。
三、表间关系 & Join Key
硬外键(数据库 FK 约束)
- 19 张用户维度表
.user_id→user.id:coin_account(同时是 PK)、coin_transaction、cash_transaction、invite_cash_transaction、withdraw_order、wechat_transfer_authorization(同时是 PK)、signin_record、signin_boost_record、user_task、comparison_record(2026-07 起user_id可空——harvest 帧0 建行时软鉴权可能拿不到)、comparison_milestone_claim、savings_record、ad_reward_record、ad_watch_log、ad_ecpm_record、ad_feed_reward_record、price_report、feedback、device_liveness。 admin_audit_log.admin_id→admin_user.id。price_report.comparison_record_id→comparison_record.id(可空:关联记录被删后仍留上报历史)。- 邀请两表 →
user.id:invite_relation.inviter_user_id、invite_relation.invitee_user_id(唯一)、invite_fingerprint.inviter_user_id——注意 FK 列名是inviter/invitee_user_id,不是user_id。 - CPS 6 表 + 比价沉淀 2 表 +
launch_confirm_sample均无硬 FK(全靠sid/trace_id/openid语义对齐,见下)。
语义 join key(无 FK 约束,靠业务字段对齐 —— 排障/对账必看)
-
coin_transaction.ref_id指向随biz_type变:biz_type ref_id 指向 amount 符号 signin当天日期串(= signin_record.signin_date的 ISOYYYY-MM-DD)+ signin_boost当天日期串(= signin_boost_record.signin_date的 ISOYYYY-MM-DD)+ task_<key>user_task.task_key+ reward_video/ad_reward(历史)ad_reward_record.trans_id+ feed_ad_rewardad_feed_reward_record.client_event_id+ exchange_outnull(兑现金,无单据) − admin_grant/admin_deductnull(原因记在 remark=admin:<reason>)+ / − -
cash_transaction.ref_id:biz_type ref_id 指向 amount 符号 withdraw/withdraw_refundwithdraw_order.out_bill_no(source=coin_cash的单)− / + exchange_innull + admin_grant/admin_deductnull(原因在 remark=admin:<reason>)+ / − -
invite_cash_transaction.ref_id(邀请奖励金账本,#82):biz_type ref_id 指向 amount 符号 invite_reward被邀请人 user.id(字符串)+ invite_withdraw/invite_withdraw_refundwithdraw_order.out_bill_no(source=invite_cash的单)− / + admin_grant/admin_deductnull + / − -
comparison_record.store_name≈savings_record.shop_name:无 id 关联,按店名字符串相等给比价记录打「已下单」标记(瞬态,不写库)。两边店名同源 = 比价意图识别阶段的门店 query,语义=店级(同店比价多次会一并标已下单)。 -
广告流会话关联:
ad_reward_record.ad_session_id可与ad_ecpm_record.ad_session_id对齐;ad_watch_log仍是旧版兼容统计,不逐条参与发奖。 -
里程碑解锁进度不存库:
comparison_milestone_claim只记「哪几档已领」;进度 =comparison_record里status='success'的count。 -
领券三表无硬 FK,全靠软关联:
coupon_prompt_engagement/coupon_daily_completion/coupon_claim_record的user_id软指user.id(可空、有登录态才记、不进唯一键、不阻塞判断);trace_id软指 pricebot work_logs(排查回指);唯一键都以device_id+ 北京自然日为主(详见coupon_state.md)。coupon_session(#99)同口径:trace_id唯一 upsert、user_id软指(admin 明细 LEFT JOIN 出手机号)。 -
analytics_event(#83)与coupon_session均无硬 FK:埋点/流水不鉴权也收,device_id/user_id只作维度。 -
admin_user.role按名语义引用admin_role.name(无硬 FK,#117):删除保护在应用层(在用/内建角色不可删);个人pages_override优先于角色pages。 -
onboarding_completion.(user_id, device_id):user_id语义关联user.id(无硬 FK,同coupon_*设备表),device_id= 客户端硬件级ANDROID_ID(≠ 领券 per-installdevice_id)。登录读、走完引导写,决定是否再展示新手引导。 -
CPS 群发 6 表全靠
sid/ id /openid语义串联(无硬 FK):cps_link.group_id→cps_group.id、cps_link.activity_id→cps_activity.id、cps_click.link_id→cps_link.id、cps_wx_user.first_group_id→cps_group.id;点击与订单无法对到单笔,只在群维度(sid)汇合——cps_order.sid≈cps_group.sid≈cps_link.sid≈cps_click.sid(仅美团有 sid,淘宝/京东无)。统计按群聚合,故group_id/sid冗余进cps_click免 join。cps_wx_user按openid自成用户身份维度,与cps_click/cps_order无 id 级 join。 -
比价沉淀两表(
price_observation/store_mapping):trace_id软指 pricebot work_logs(与comparison_record.trace_id同源但不互 join,各存各视角);source_user_id/source_device_id软指用户/设备(可空,匿名也记)。store_mapping.lookup靠店名字符串精确相等 + geo 取最近,非 id 级 join。
ER 关系(文字版)
user ─1:1─ coin_account
user ─1:1─ wechat_transfer_authorization
user ─1:N─ { coin_transaction, cash_transaction, invite_cash_transaction, withdraw_order,
signin_record, signin_boost_record, user_task, comparison_record(user_id 可空),
comparison_milestone_claim, savings_record, ad_reward_record, ad_watch_log,
ad_ecpm_record, ad_feed_reward_record, price_report, feedback, device_liveness }
(device_liveness 硬 FK; (user_id,device_id) 唯一)
user ─1:N─ onboarding_completion (user_id, 无硬 FK; (user_id,device_id) 去重)
comparison_record ─1:N─ price_report (comparison_record_id, 可空)
admin_user ─1:N─ admin_audit_log
app_config (独立, 无外键, key 为主键)
coupon_prompt_engagement / coupon_daily_completion / coupon_claim_record
(独立, 无硬 FK; 维度=device_id+北京日, user_id/trace_id 仅软关联)
coupon_session (独立, 无硬 FK; trace_id 唯一=一次领券任务一行, #99)
analytics_event (独立, 无硬 FK; 埋点事件流, device/user 仅维度, #83)
admin_role ◀──语义(role 按 name 引用, 无FK)── admin_user (pages_override 个人覆盖, #117/#126)
ops_stat_config / ops_marquee_seed / meituan_coupon / ad_pangle_daily_revenue
(独立运营/缓存/对账表, 无外键)
user ─1:N─ invite_relation (inviter_user_id 硬 FK); invitee_user_id ─1:1─ user (唯一硬 FK)
user ─1:N─ invite_fingerprint (inviter_user_id 硬 FK)
cps_activity / cps_group ──语义(无FK)──▶ cps_link ─1:N─ cps_click
(CPS 6 表自成子系统; cps_order 经 sid 归群对账, 与本 App user 无关)
cps_group ◀──语义(无FK)── cps_wx_user (first_group_id; openid 维度的微信落地页用户身份)
price_observation / store_mapping (独立, 无硬 FK; 维度=trace_id, source_user/device 仅软关联; pricebot 内部上报)
launch_confirm_sample (独立, 无硬 FK; 都上报不去重; pricebot 内部上报启动确认窗兜底样本)
四、资金模型(金币 / 现金 / 提现,三层)
- 余额快照
coin_account:coin_balance(金币个数)+cash_balance_cents(现金分)+invite_cash_balance_cents(邀请奖励金分,#82),一用户一行,读取展示用。 - 流水账本
coin_transaction/cash_transaction/invite_cash_transaction:每次变动写一笔,balance_after*记变动后余额,可逐笔回溯对账。现金与邀请奖励金是两本物理隔离的账——发放口径与提现对账各自独立。 - 唯一变动入口:金币走
repositories/wallet.grant_coins,邀请奖励金走grant_invite_cash——都是「更新快照 + 写流水,不 commit,由调用方同一事务 commit」。signin / signin_boost / task / ad_reward / feed_ad_reward / exchange / admin 走grant_coins;invite_reward/ admin 调整走grant_invite_cash,靠biz_type区分来源。
- 汇率:
10000 金币 = 1 元 = 100 分(rewards.COIN_PER_YUAN);兑换额必须是整分倍数。 - 提现状态机:
reviewing(发起即原子扣款、待人工审核、不打款)→ 审核通过pending(微信转账在途)→success/failed(失败自动退款);审核拒绝rejected(退款)。按withdraw_order.source分账(#121):coin_cash单的扣款/退款写cash_transaction,invite_cash单写invite_cash_transaction;out_bill_no幂等,孤儿 pending 单由reconcile_pending_withdraws对账兜底,adminwithdraws/ledger-check分账校验「单 ↔ 流水」。 - 防超额:扣款用带条件
UPDATE ... WHERE <对应余额列> >= amount(按 source 选cash_balance_cents/invite_cash_balance_cents),并发/重试不会双扣。
五、通用约定
见 README → 通用约定:金额存整数(金币计数 / 现金存分)、业务「今天」按北京时间日期串等值聚合(不在 SQL 跨时区比 date)、JSON 列 PG 用 JSONB·SQLite 退化 JSON、改表必写 alembic 迁移并保持单 head + 同步更新本目录文档。