feat(experiment): LES-1339 前端 source of truth + 后端 sanity check
背景
LES-1336 在治标修复后端 ExperimentService.buildPersonProperties 缺 $initial_utm_* 导致前后端 variant 不一致,但根本结构性问题没解决:后端要手动构造 personProperties 复刻前端 PostHog JS SDK 自动捕获的所有 person property —— 任何 release condition 新加字段都得后端跟一次。这是个永久维护负担。
本 MR 落地 LES-1339 方案 A:前端 source of truth + 服务端 sanity check。需配套上游 fly_moon_agent + jennie 两个仓库的 MR 一起发。
改动清单
-
flymoon-payment-provider/.../dubbo/AgentUserPointsServiceImpl.java— 新增常量ATTACHMENT_KEY_FRONTEND_VARIANT_ASSIGNMENTS(Dubbo attachment key) -
flymoon-payment-provider/.../service/impl/ExperimentServiceImpl.java—assignVariant加前端 attachment 优先 + 后端 PostHog 评估作 sanity check。一致落库source=frontend;不一致仍以前端值落库source=frontend_mismatch+ 警告日志;缺失退回当前 PostHog 评估路径作兜底 -
flymoon-payment-provider/src/test/java/.../ExperimentServiceImplFrontendVariantTest.java— 新增 10 个用例覆盖前端值一致 / 不一致 / 缺失 / JSON 损坏 / 已分桶 / PostHog 不可用等场景
根因分析
- Bug 表现:前后端 variant 不一致 → 用户登录后 0 积分 → 看起来"被 paywall 拦了"
- 触发条件:用户 utm 完整时前端 evaluate 成 control,后端因 PostHog ingest 延迟 + person property 缺字段 evaluate 失败 fallback 到 multivariate hash 分到 test
- 根因位置:架构层面(后端复刻前端 PostHog 评估逻辑)—— LES-1336 只补了
$initial_utm_*一个字段 - 修复方案:前端 source of truth,后端落库时优先信任前端值。后端 PostHog 评估降级为 sanity check,不一致只打 warn 日志(按 LES-1339 决策"信前端 + 观察",不防篡改)
- 影响评估:注册链路 + paywall 接口都过
ExperimentService.assignVariant,本次改动对所有调用方生效。前端值缺失时仍走原有 PostHog 评估路径,兼容老前端
测试
-
单测:新增 10 个用例,全部通过(用 junit-platform-launcher 直接执行验证:
10 tests started / 10 successful / 0 failed) -
本地
mvn clean test-compile通过 -
s1 部署验证:登录后
experiment_assignment表source=frontend,正常发 100 分
测试豁免说明:mvn surefire 2.12.4 在本仓库不识别 JUnit 5 测试(Tests run: 0),这是 pre-existing 问题(现有 AgentUserPointsServiceImplAbTest 等也是同样状态)。改完后跑过 mvn clean test-compile 验证编译通过,并用 junit-platform-launcher-1.10.2 直接 java 调用 JUnit 5 引擎跑过新增测试。
线下验证
- 环境:s1
- Jenkins build job:https://jenkins.deeplink.media/job/build_image_flymoon_payment/243/
- Jenkins deploy job:https://jenkins.deeplink.media/job/DM_s1_flymoon_payment/186/
- 状态:build SUCCESS(1分53秒)+ deploy SUCCESS(1分20秒)
- 镜像:
uswccr.ccs.tencentyun.com/lessiesit/flymoon-payment:v243_agent-claude-opus-4-7-953d77c1_059ee60_202605192006 - 验证结果:待人工 e2e 流程验证(需要 jennie + agent 同步部署后联调)
关联
- Issue: LES-1339 [architecture] PostHog 实验分桶前后端一致性
- 上游 MR:fly_moon_agent 接收 query param 并透传 Dubbo attachment / jennie 登录时上送 variant
- 前序 issue:LES-1336 短期治标修复(已合并)
- Agent-Task-Id: 20260519-les1339-frontend-source-of-truth
部署顺序建议
- 本 MR 先 merge —— payment 端兼容老前端(attachment 缺失走原 PostHog 评估路径)
- fly_moon_agent 后 merge —— agent 开始透传 attachment
- jennie 最后 merge —— 前端开始上送 variant
任意一步可独立回滚。
执行报告(dev-workflow)
任务摘要
- 类型:feat / refactor(架构调整)
- 范围:跨仓库(payment + agent + jennie)
- 验收标准:
-
前端登录请求带
posthog_assignmentsbody 字段 - fly_moon_agent LoginController 接收后通过 Dubbo attachment 透传给 payment
-
ExperimentService.assignVariant收到前端 assignment 时:sanity check + 落库 source 标记 - PostHog SDK 不可用时仍能用前端值落库(解决根本问题)
-
前端登录请求带
分析结论
- 影响调用方:注册链路 + paywall 接口共用
ExperimentService.assignVariant,本变更对所有调用方生效;前端值缺失时退回 PostHog 评估路径完全兼容老前端 - 最小改动面:本仓库新增 1 个常量 +
assignVariant加 30 行前端值优先逻辑,buildPersonProperties 保留作兜底路径(不删,灰度后再清理)
改动清单
-
flymoon-payment-provider/.../dubbo/AgentUserPointsServiceImpl.java— 新增 attachment key 常量 -
flymoon-payment-provider/.../service/impl/ExperimentServiceImpl.java— 前端 attachment 优先 + sanity check -
flymoon-payment-provider/src/test/java/.../ExperimentServiceImplFrontendVariantTest.java— 10 个单测 - commits: 059ee608
部署
- 测试环境:s1
- Jenkins build:https://jenkins.deeplink.media/job/build_image_flymoon_payment/243/
- Jenkins deploy:https://jenkins.deeplink.media/job/DM_s1_flymoon_payment/186/
- 状态:build SUCCESS(1分53秒)+ deploy SUCCESS(1分20秒)
测试
- 单测:10/10 通过(用 junit-platform-launcher 直接验证,参见 MR 主描述"测试豁免说明")
- 手动验证:待 e2e(需配套 jennie + agent 同步部署)
风险与后续
- 风险:sanity check 不一致时只 warn 不阻止落库 —— 按 LES-1339 决策"信前端 + 观察",不防篡改。后续若
source=frontend_mismatch日志比例 < 1%,可以拆掉 sanity check 完全信任前端 - 下一步:merge 顺序 payment → agent → jennie,每一步可独立回滚
跳过的阶段
- 无(全流程走完,闸门 0~3 全部通过)
审计
- Agent-Task-Id: 20260519-les1339-frontend-source-of-truth