源文件 04-ecommerce-spec.txt | 由 Markdown 渲染,内容与原文一致
版本 v1.0 | 2026-10-30 | 覆盖两个独立业务模块:整车商城、零配件商城
配套接口:
/api/sr3/*(15 条,见第 6 节)
整车和零配件看起来是两门生意,但它们共享同一份车型主数据,这是整个系统不散架的前提。
┌──────────────┐
│ sr3_brands │ 33 个国产品牌
└──────┬───────┘
│ 1:N
┌──────▼───────┐
模块① 整车商城 ──▶│ sr3_cars │ 100 台车型(当前 1 台上架)
└──────┬───────┘
│ N:M ← sr3_part_fitment(适配关系)
┌──────▼───────┐
模块② 零配件商城 ──▶│ sr3_parts │ 480 个 SKU
└──────┬───────┘
│ N:1
┌──────▼─────────────┐
│ sr3_part_categories │ 10 总成 + 41 子系统
└────────────────────┘
如果不共用会怎样:整车表里写「SU7 Ultra 敞篷版」,配件表里另写一份车型名字符串,两边改名就不同步, 买家点「适配我的车」查不到件。所以配件不存车型名字,只存 car_id 外键。
| 表 | 作用 | 关键字段 |
|---|---|---|
sr3_brands | 品牌 | code(byd/xiaomi)、中英文名、排序 |
sr3_cars | 整车商品 | model_code 唯一、body_type(轿车/SUV/跑车/MPV/越野/旅行)、energy_type(纯电/插混/增程/燃油)、tier(n0 入门→n3 顶级)、price_points、glb_path(直接指向原厂 GLB,不重建)、status |
sr3_part_categories | 零件分类树 | parent_id 两级、code、level |
sr3_parts | 零件 SKU | part_code 唯一、category_id、slot(安装槽位)、anchor(锚点名)、quality(原厂/副厂/性能/限量)、fit_mode、is_consumable、perf(JSON 性能加成)、price_points |
sr3_part_fitment | 零件↔车型适配 | part_id + car_id 唯一联合键、fit_note(如「需选装运动包」) |
| 表 | 作用 | 说明 |
|---|---|---|
sr3_garage | 拥有的整车 | installed(JSON:{slot: part_id})、condition(JSON:各槽位磨损/锈迹/污渍)、mileage_km |
sr3_user_parts | 拥有的零件 | qty、wear(消耗品磨损) |
sr3_cart | 购物车 | item_type ENUM('car','part') —— 一张表装两类商品 |
sr3_orders / sr3_order_items | 订单 | idem_key 唯一键做幂等;明细存 name_snapshot 防运营改名后订单显示错乱 |
| 表 | 作用 |
|---|---|
sr3_telemetry | 埋点事件(event + props JSON + session_id),驱动每日漏斗分析 |
sr3_client_log | 客户端 warn/error/fatal 日志(debug/info 服务端直接丢弃,防撑爆) |
sr3_releases | OTA 发布记录(版本、清单快照、灰度百分比、状态) |
sr3_daily_build | 每日构建闸门:local_test / e2e_full_flow 任一不过,deployed 不许置 1 |
一个页面两个入口,不混排:
┌─────────────────────────────────────────────────────┐
│ 极速狂飙3 · 商城 │
├──────────────────────┬──────────────────────────────┤
│ 🚗 整车商城 │ 🔧 零配件商城 │
│ 100 台国产车 │ 480 个零件 · 10 大总成 │
│ 按品牌 / 车身 / 分级 │ 按车型 / 总成 / 品质 │
│ [进入选车] │ [进入配件] │
└──────────────────────┴──────────────────────────────┘
为什么分成两个入口而不是一个混合列表:整车的决策链路是「我想要什么车」(横向比性能、比外形), 配件的决策链路是「我的车缺什么」(纵向找件)。两种心智完全不同,混在一起两边都难用。
筛选维度(四组可叠加):品牌 → 车身类型 → 能源类型 → 分级 排序:首发优先 / 价格升降 / 马力降 列表卡片:
┌────────────────────┐
│ [原厂 GLB 渲染] │ ← 直接用 GLB 出缩略图,后台批渲染,不手工做图
│ 小米汽车 │
│ SU7 Ultra 敞篷版 │
│ 敞篷 · 纯电 · 2 座 │
│ ★★★ 顶级 │
│ 免费(首发) │
│ 可装 238 个零件 ▸ │ ← 关键:卡片上就告诉你能改多少,直接导流到配件商城
└────────────────────┘
详情页:3D 预览(GLB 实时渲染)→ 性能参数 → 可改装件分区(点击某个分区直接跳配件商城并预选该车型 + 该总成)
筛选维度:车型(必选,可切「全部车型」)→ 总成 → 品质 → 搜索 列表卡片:
┌──────────────────────┐
│ [零件图标] │
│ 前大灯总成 左 │
│ 灯光系统 · 前大灯总成 │
│ 原厂件 │
│ 880 分 │
│ 适配 SU7 Ultra 敞篷版 │ ← 车型角标,一眼确认装得上
└──────────────────────┘
详情页三块:
fits,明确列出能装哪些车,带 fit_note 条件说明)alternatives)—— 这是改装商城的关键:玩家买前必比性价比对比
副厂件 720 分 brake +0
原厂件 880 分 brake +0
碳陶件 2280 分 brake +22 weight -3.2kg ← 高亮推荐
加购 → 购物车(服务端重算价) → 下单(生成 pending 单) → 支付(事务扣积分+发放)
三个硬约束:
idem_key,重试复用同一个;服务端命中即返回原单,不重复落单order_no 重复支付返回 idempotent:true,绝不二次扣费免费首发车特判:price_points=0 的车加购直接入库(返回 granted:true),不生成 0 元订单 —— 0 元订单会污染订单列表,也会让「支付」逻辑处理 0 分钱的边界。
10 个总成 → 41 个子系统 → 480 个 SKU(当前 SU7 238 件 + 宋 Plus 242 件)。
| 总成 | 子系统 | 件数 | 典型件 |
|---|---|---|---|
| 前脸外观系统 | 前舱盖 / 前保险杠 / 中网进气 / 前空气动力学 | 49 | 前舱盖、前唇碳纤扰流板、龙颜格栅 |
| 车身侧面系统 | 车门 / 翼子板 / 侧裙护板 / 外后视镜 / 门把手 | 94 | 车门、翼子板、隐藏式门把手、碳纤后视镜壳 |
| 车顶与敞篷系统 | 天窗 / 敞篷机构 / 防滚架收纳舱 / 车顶附件 | 30 | 全景天窗玻璃、软顶总成、防滚架装饰罩 |
| 车尾系统 | 尾门尾箱 / 后保险杠 / 后扩散器 / 尾翼 | 43 | 尾门、碳纤尾翼、扩散器导流鳍 |
| 灯光系统 | 前大灯 / 尾灯 / 雾灯 / 辅助灯氛围灯 | 64 | 大灯外罩、日行灯光导、示宽/刹车/转向分层 |
| 行走系统 | 轮毂 / 轮胎 / 刹车 | 92 | 锻造轮毂、碳陶盘、半热熔胎 |
| 底盘悬挂 | 减震弹簧 / 摆臂连杆 / 底盘护板 / 排气 | 33 | 绞牙避震、下摆臂、电池包护板 |
| 玻璃与密封 | 前挡风 / 侧窗 / 后挡风 / 密封胶条 | 19 | 夹层前挡、密封条 |
| 内饰系统 | 座椅 / 方向盘 / 仪表中控 / 中央通道 / 氛围灯 | 38 | 碳纤桶椅、碳纤方向盘、旋转中控屏 |
| 标识与改装 | 车标字符 / 拉花涂装 / 贴纸饰条 / 品牌旗 | 18 | xiaomi 尾标、闪电拉花、极速狂飙品牌旗 |
分类设计的一个取舍:左右对称件(前大灯、翼子板、门把手、轮毂×4)在库里是独立 SKU, 不是一条记录带数量。原因:撞坏左前大灯不该让你买一对;而且左右件在游戏里挂在不同骨骼节点上, 必须能独立装备、独立损伤。
fit_mode | 含义 | 例子 |
|---|---|---|
exact | 只装指定车型 | SU7 的碳纤尾翼 |
family | 同车系通用 | 预留(SU7 / SU7 Ultra 共用件) |
universal | 全车型通用 | 预留(品牌旗、贴纸、通用氛围灯) |
当前 480 件全部 exact + 一条 fitment 记录。接入更多车型后,通用件只需把 fit_mode 改成 universal,查询时 OR fit_mode='universal' 即自动出现在所有车型下。
slot + anchor只靠分类不够。分类回答「这是什么」(灯),slot 回答「装哪儿」(hl_lens_l), anchor 回答「对齐到哪个点」(引擎里的插槽名)。
slot 只能装一件 → 换件自动替换,不会叠加成两个尾翼anchor 是引擎侧的对齐基准点,美术做新件只要把模型原点对到锚点,玩家点装备即自动就位,无需逐件调坐标sr3_garage.installed 就是 {slot: part_id} 的字典,存档与商城共用同一套键| 方法 | 路径 | 权限 | 说明 |
|---|---|---|---|
| GET | /api/sr3/shop/cars | 公开 | 整车列表(brand/body/energy/tier/q/page/sort) |
| GET | /api/sr3/shop/car | 公开 | 整车详情(含 part_groups 可改装分区 + owned) |
| GET | /api/sr3/shop/cats | 公开 | 零件分类树(一级件数已累加子类) |
| GET | /api/sr3/shop/parts | 公开 | 零件列表(car/cat/quality/q/page/sort) |
| GET | /api/sr3/shop/part | 公开 | 零件详情(fits 适配车型 + alternatives 同槽位替代) |
| GET | /api/sr3/cart | 登录 | 购物车(服务端重算总价 + can_pay) |
| POST | /api/sr3/cart/add | 登录 | 加购(免费车直接入库) |
| POST | /api/sr3/cart/remove | 登录 | 移除 / 清空 |
| POST | /api/sr3/order/create | 登录 | 下单(idem_key 幂等) |
| POST | /api/sr3/order/pay | 登录 | 支付(事务 + 重复支付幂等) |
| GET | /api/sr3/order/list | 登录 | 订单记录 |
| GET | /api/sr3/garage | 登录 | 我的车库 |
| GET | /api/sr3/parts/mine | 登录 | 我的零件库存 |
| POST | /api/sr3/track | 公开 | 埋点(uid 由服务端会话自证) |
| POST | /api/sr3/log | 公开 | 客户端日志(只收 warn 及以上) |
每天:
① 客户端本地跑 e2e_full_flow(从新用户登录 → 选车 → 改装 → 比赛 → 商城 → 退出全链路)
② 不过 → sr3_daily_build.local_test 置 fail,deployed 保持 0,拒绝发布
③ 过了 → 写 sr3_daily_build(local_test=pass, e2e=pass, assets_added=[今日新增车型/零件])
④ 出包 → 写 sr3_releases(manifest 快照 + sha1 + 灰度百分比)→ OTA 主动推送
⑤ 次日拉 sr3_telemetry 漏斗 + sr3_client_log,定位流失点与崩溃
闸门为什么必须写库:只靠脚本 exit code 拦不住人。sr3_daily_build.deployed 是唯一真相, 部署脚本必须先 SELECT local_test,e2e_full_flow FROM sr3_daily_build WHERE build_date=CURDATE(), 不是双 pass 就 exit(3) —— 这和论坛现有的部署闸门是同一套规矩。