Skip to content

Dev / Prd 双库对照 —— 一致性、仿真数据边界、问题处理 ​

生成:2026-09-20 · 依据:生产双库实测(v2.0.50,各 104 用例,两库均 0 失败) 相关:接线必读 · 通用约定 G2 · X-Env · 取值速查


一句话结论 ​

两个库的 API 契约完全一致 —— 你不需要为它们写两套逻辑。 真正的区别在数据从哪来:Dev 库的新号拿到仿真数据,Prd 库的新号拿到默认数据模板 (按四维画像算的示例数据,响应带 dataSource 标记)。

所以两库的新号都不会看到空页面 —— 但 Prd 那份是「示例」,前端必须按 接线必读 · 兜底 #8 标注,否则用户会以为是自己测的。


一、契约层:完全一致(有实测依据) ​

这一点是实测出来的,不是推测 —— 同一套 104 用例打两个库:

维度Dev 库Prd 库
用例数104104
失败00
字段结构逐条一致同左
错误码逐条一致同左
空值(null)语义逐条一致同左
字段级 L4 覆盖29/3028/30

两库跑出的报告字节级几乎相同(唯一差异是时间戳与各自的数据内容)。

由此可推:接口的形状由代码决定,与库无关 —— 所以你调试时看到"两个库返回结构一样"是正常的, 不要去找并不存在的差异。


二、数据层:两库新号都有数,但来源不同(这是唯一实质区别) ​

Dev 库:新号注册自动获得仿真数据 ​

新用户完成 submitOnboarding(提交四维:性别/年龄/身高/体重)后,系统会自动生成 7 天仿真健康数据:

数据内容
设备健康7 天步数/距离/卡路里/睡眠 + 6 类指标(HR / REST_HR / STRESS / SPO2 / VO2MAX / HRV)
设备绑定1 条荣耀绑定(授权中心显示「已连接」)
运动打卡7 天(含今天)→ 首页热量环、跑者统计有数
餐食21 条(每日 3 餐)→ 热量环的「摄入」非 0
赛事1 条马拉松报名 → 证书页赛事段有内容
训练1 个计划 + 12 任务 + 12 提醒

量级按四维反推(不是固定模板):BMR → TDEE → 步数/跑量/配速, 体重 70kg 与 100kg 的用户拿到不同的数据(配速、消耗、步数都不同)。

Prd 库:没有仿真数据,但有默认数据模板(2026-09-20 起) ​

先说仿真为什么不在 Prd —— 原因有二,都是设计而非缺陷:

  1. 双守卫 fail-closed:仿真需要 V2_SIMULATE_ON_ONBOARDING=1 且 请求的 schema 是 dev —— Dev/Prd 是同一容器、同一进程的两个 schema,开关对两者同时生效, schema=='dev' 才是真正的边界(见 src/app/simulation/application/guards.py)
  2. 真实数据由真实设备产生:Prd 库数据来自厂商云同步(荣耀/Zepp/小米/Suunto)

但 Prd 新号不会看到空页面 —— 后端按四维画像算出一套 7 天默认数据模板垫底 (vendor='default_template',落 device 五表):

维度内容
设备健康7 天步数/距离/卡路里/睡眠 + 6 类指标(HR / REST_HR / STRESS / SPO2 / VO2MAX / HRV)
睡眠汇总7 天(睡眠分期四段之和 = 总时长)
体成分7 天体重 / BMI / 体脂 / 肌肉 / 水分 / 骨量

⚠️ 与仿真的三点关键区别(不要混淆):

Dev 仿真数据Prd 默认模板
来源键simulateddefault_template
前端能否分辨不能(无标记)能 —— 响应带 dataSource: "default_template"
是否绑设备会写 1 条荣耀绑定(授权中心显示「已连接」)不写绑定 —— 「没绑设备」是事实,不假装
其他域运动打卡 / 餐食 / 赛事 / 训练计划都有只有设备健康五表(运动打卡/训练计划仍为空 —— 那是事实类)

⚠️ 两库都会「降级」 —— 但用的不是同一套数据:Dev 走仿真、Prd 走模板。前端不用判库 (三、前端该怎么处理 第 1 条),只要判 dataSource 键有没有。


三、前端该怎么处理 ​

1. 不要写「按库分叉」的逻辑 ​

契约一致 ⇒ 解析代码一份就够。不要写 if (X_ENV === 'Dev') {...} else {...}。 (App 产线包固定 X-Env: Prd,照示例调即与 App 同库。)

2. 但要知道「数据覆盖到哪」是两回事 ​

两库新号都有数据(Dev 仿真 / Prd 模板),但覆盖范围不同: Prd 的模板只填设备健康五表 —— 运动打卡、训练计划、餐食、赛事在 Prd 仍为空 (那些是事实类数据,后端不编造;「没报名计划」是实话)。 所以同一段渲染代码在 Prd 仍会遇到 null —— 这不是 bug,是契约里就写明的空值语义 (见接线必读 与 取值速查)。

⚠️ 最要紧的一条:withDefaults 只补 undefined、不补 null。 后端按「无数据源显式返 null,不伪造」设计,所以前端必须处理 null —— 详见接线必读 · 8 条兜底。

3. 怎么验证「有数据的页面」 ​

X-Env: Dev  →  新号注册即得完整仿真数据 → 所有页面有内容(含运动/训练/餐食/赛事)
X-Env: Prd  →  新号得默认数据模板 → 设备健康页有内容(带 dataSource 标记),
                运动/训练/餐食/赛事仍为空

要验全页面填充用 Dev;要验降级提示(dataSource 横幅)用 Prd。


四、问题处理(看到差异时怎么判、找谁) ​

现象判定处理
两库返回结构不同(字段多了/少了/类型变了)🔴 算 bug —— 契约层不该有差异报后端,附两库的原始响应
两库同一端点错误码不同🔴 算 bug同上
Dev 有数据、Prd 没有⚠️ 看是哪个域 —— 设备健康两库都有(Dev 仿真 / Prd 模板);运动/训练/餐食/赛事只有 Dev 有(事实类不降级)想验证有数据的场景就切 Dev
Prd 有数据、Dev 没有✅ 正常(Dev 库的仿真只对新注册号生成)Dev 库对老号请手动调 simulateData
同一库、同一用户、同一时刻,两次调用结果不同🔴 算 bug(除打卡/餐食等本就递增的数据)报后端,注明时间与用户
Dev 库某用户数据与文档里的示例值不同✅ 正常 —— 仿真数据按四维生成,因人而异以结构为准,别对具体数值写断言
Dev 库的数据在重跑后变了⚠️ 可能正常 —— 仿真确定性只在同一版本内成立(改任何算法细节都会变)见下方"确定性的边界"

⚠️ 确定性的边界(重要,避免误报) ​

仿真数据的"确定性"承诺是:同一版本内,同一用户 + 同一天 → 逐字节相同。

它不跨版本 —— 后端一旦调整生成算法(哪怕只是新增一个指标),其后所有随机值都会改变。 实测:把按日指标从 1 种扩到 6 种后,同一用户的打卡总距离从 32.2km 变成 33.4km。

所以:

  • ✅ 可以断言结构(字段存在、类型、取值范围)
  • ❌ 不要把具体数值写进前端断言或联调用例

Prd 的默认数据模板同理 —— 它是确定性计算(同一四维 + 同一版本 → 同一份模板, 不像仿真那样逐用户随机),但同样不跨版本:模板算法一改,数值就变。 「同一用户每次刷新数值一样」是正常的(这正是模板与仿真的本质区别 —— 模板必须可预测)。

排查路径 ​

  1. 先确认 X-Env 全程一致 —— token 绑 X-Env,跨库即失效(G2)
  2. 确认是契约差异还是数据差异(对照上表)
  3. 契约差异 → 附「两库原始响应 + 请求头」报后端
  4. 数据差异 → 先看是不是仿真范围(本页第二节)
  5. 仍不确定 → 用 POST /api/v1/simulation 的 status action 查当前环境能不能造数据:
    json
    { "action": "status", "payload": {} }
    → { "allowed": true, "schema": "dev", "days": 7, "userId": "u000078" }

五、仿真数据的操作入口(Dev 库) ​

action作用
simulateData按当前画像重新生成(幂等:先清后写,不翻倍)。改了画像后重跑可覆盖旧数据
clearSimulatedData清除该用户的全部仿真数据(按 source/vendor 标记删除,真实数据不受影响)
status只读:当前环境能不能造数据(守卫判定)

统一入口 POST /api/v1/simulation(需登录,Bearer access)。

⚠️ 不在 Prd 库可用 —— 会返回 40428003 DISABLED(这是设计,不是故障)。