ok
This commit is contained in:
176
plans/IMEI FB-3a02c714.plan.md
Normal file
176
plans/IMEI FB-3a02c714.plan.md
Normal file
@@ -0,0 +1,176 @@
|
||||
<!-- 3a02c714-3517-4fd8-ba3a-0c9172c34575 -->
|
||||
---
|
||||
todos:
|
||||
- id: "parse-fbc-fbclid"
|
||||
content: "在 insertADTag 回退路径实现 fbc→fbclid 解析与字段映射(ad_fbp/ad_ua/ad_ip)"
|
||||
status: pending
|
||||
- id: "redis-pop-latest"
|
||||
content: "实现 resolveLatestDotPayloadBySource:zPopMax user:dot:set:{source} + unserialize 校验"
|
||||
status: pending
|
||||
- id: "imei-only-guard"
|
||||
content: "回退逻辑仅 IMEI 新注册触发(覆写 ImeiRegisterService::insertADTag 或在 Abstract 中加平台/类型判断)"
|
||||
status: pending
|
||||
- id: "redis-cleanup"
|
||||
content: "可选:SiteController 或 pop 侧增加 ZSet 过期/长度清理"
|
||||
status: pending
|
||||
- id: "verify-fbdot"
|
||||
content: "联调:落地页上报 → IMEI 注册 → 检查 user tag 与 FbDot 注册打点"
|
||||
status: pending
|
||||
isProject: false
|
||||
---
|
||||
# IMEI 注册 FB 像素归因补写方案评估
|
||||
|
||||
## 结论
|
||||
|
||||
**方案可行**,且与现有设计意图一致:[`slot_user/app/api/controller/SiteController.php`](slot_user/app/api/controller/SiteController.php) 负责落地页采集,[`slot_user/app/api/controller/UserController.php`](slot_user/app/api/controller/UserController.php) 里已有被注释的 `zPopMax` 回退逻辑,[`slot_user/app/service/register/ImeiRegisterService.php`](slot_user/app/service/register/ImeiRegisterService.php) 在新用户路径调用 `insertADTag`。
|
||||
|
||||
你已确认范围:**仅 IMEI 新用户注册**(现有 `insertADTag` 调用点),不覆盖老用户登录补写。
|
||||
|
||||
---
|
||||
|
||||
## 现有数据流
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Landing as LandingPage_FB_IAB
|
||||
participant Site as SiteController_fb
|
||||
participant Redis as Redis_ZSet
|
||||
participant App as iOS_App
|
||||
participant Reg as ImeiRegisterService
|
||||
participant Tag as insertADTag
|
||||
participant Dot as slot_console_FbDot
|
||||
|
||||
Landing->>Site: POST fbp,fbc,ua,source
|
||||
Site->>Redis: zAdd user:dot:set:{source}
|
||||
App->>Reg: IMEI register (无 fb 参数)
|
||||
Reg->>Tag: insertADTag(uid)
|
||||
Tag->>Redis: 若无 fbclid 则取最新
|
||||
Tag->>Tag: UserInfoCache 写入 ad_fbc/ad_fbp/ad_ua/ad_ip
|
||||
Dot->>Tag: 读 ad_fbc 作为 fbclid 打 CompleteRegistration
|
||||
```
|
||||
|
||||
| 环节 | 现状 |
|
||||
|------|------|
|
||||
| 落地页采集 | [`SiteController::fb`](slot_user/app/api/controller/SiteController.php) 校验 `FB_IAB` UA,写入 `user:dot:set:{source}`,member 为 `serialize(fbp,fbc,ua,ip,source)`,score 为 `microtime` |
|
||||
| IMEI 注册 | [`ImeiRegisterService::run()`](slot_user/app/service/register/ImeiRegisterService.php) 新用户分支 L115 调用 `insertADTag` |
|
||||
| 写入用户标签 | [`insertADTag`](slot_user/app/service/register/AbstractRegisterService.php) 当前固定用 `$this->fbclid` 写 `ad_fbc`,iOS 客户端通常为空 |
|
||||
| FB 正式打点 | [`FbDot`](slot_console/app/command/dot/FbDot.php) 读 `ad_fbc`,[`FbService::generateFBC()`](slot_console/app/service/dot/FbService.php) 用 fbclid 生成 fbc/fbp |
|
||||
|
||||
---
|
||||
|
||||
## 方案为何可行
|
||||
|
||||
1. **基础设施已就绪**:Redis key [`USER_DOT_SET_KEY`](slot_user/app/service/RedisKeyManagerService.php)、落地页写入、注册侧回退思路(UserController 注释代码)均已存在。
|
||||
2. **iOS 场景匹配**:iOS 包拿不到 WebView cookie,但 FB 广告落地页在 `FB_IAB` 内可拿到 `fbp`/`fbc`,先上报再装 App 是合理补偿路径。
|
||||
3. **与 PWA 不冲突**:PWA 仍优先用请求里的 `fbclid`;仅当 `$this->fbclid`(及关联 fb 字段)为空时才回退 Redis。
|
||||
4. **打点链路可通**:`FbDot` 只要求 `ad_fbc` 非空;存入正确 fbclid 后现有 `FbService` 可继续生成 CAPI 所需 fbc/fbp。
|
||||
|
||||
---
|
||||
|
||||
## 必须处理的 4 个技术点
|
||||
|
||||
### 1. 字段映射(最关键)
|
||||
|
||||
当前 `insertADTag` 写:
|
||||
|
||||
```348:352:slot_user/app/service/register/AbstractRegisterService.php
|
||||
protected function insertADTag(int $uid)
|
||||
{
|
||||
// ...
|
||||
$userInfo->setInfo(['ad_fbc' => $this->fbclid, 'ad_fbp' => $this->fbp, ...], '');
|
||||
}
|
||||
```
|
||||
|
||||
Redis 里存的是落地页 **`fbc`/`fbp`**,不是 `fbclid`。而 `FbService` 期望 `ad_fbc` 存 **原始 fbclid**(见测试数据与 `generateFBC()` 实现)。
|
||||
|
||||
**实现要求**:从 Redis 取出 `fbc` 后,需解析出 fbclid:
|
||||
|
||||
- `fbc` 格式:`fb.{subdomainIndex}.{creationTime}.{fbclid}`
|
||||
- 取最后一个 `.` 之后的片段作为 fbclid 写入 `ad_fbc`
|
||||
- `ad_fbp`、`ad_ua`、`ad_ip` 直接使用 Redis 中的值(比服务端随机生成 fbp 更准确)
|
||||
- 若 `fbc` 为空(SiteController 允许),则无法补 attribution,应跳过写入并打 warn 日志
|
||||
|
||||
### 2. 取「最新」的方式
|
||||
|
||||
- 使用 `ZPOPMAX user:dot:set:{source} 1`(与注释代码一致),**消费**一条,避免同一条数据被下一个用户重复使用。
|
||||
- 若 `insertADTag` 后续 `setInfo` 失败,可考虑不 pop 或失败写回(当前 `insertADTag` 有 try/catch,失败只记日志;pop 后丢失风险可接受,或改为「先 ZREVRANGE 再 ZREM 成功后再写」)。
|
||||
|
||||
### 3. 同渠道并发碰撞(已知局限)
|
||||
|
||||
按 `source` 取全局最新一条,**无法 100% 保证**「这条 fbc 就是当前这台设备」。同渠道短时间多用户注册可能串 attribution。
|
||||
|
||||
可接受的 MVP:与当初 UserController 注释方案一致。若要提升准确率,后续可加:
|
||||
|
||||
- 时间窗口(如 30 分钟内)
|
||||
- IP 辅助匹配(落地页 IP vs 注册 IP,不完全可靠)
|
||||
- 落地页生成 `dot_token` 透传到 App(需客户端配合,改动更大)
|
||||
|
||||
### 4. Redis 集合维护
|
||||
|
||||
当前 `zAdd` **无 TTL、无上限**,长期会膨胀。建议实现时顺带:
|
||||
|
||||
- 写入时 `ZREMRANGEBYSCORE` 清理超过 N 天(如 7 天)的旧 member
|
||||
- 或按 source 限制 ZSet 最大长度
|
||||
|
||||
---
|
||||
|
||||
## 推荐改动位置(实现阶段)
|
||||
|
||||
不在 `UserController::register` 做(你已选 register_only + insertADTag 内聚),建议:
|
||||
|
||||
**主改文件**:[`slot_user/app/service/register/AbstractRegisterService.php`](slot_user/app/service/register/AbstractRegisterService.php)
|
||||
|
||||
- 新增私有方法,例如 `resolveLatestDotPayloadBySource(string $source): ?array`
|
||||
- `Redis::zPopMax(RedisKeyManagerService::getDotSetKey($source), 1)`
|
||||
- `unserialize` + 校验必需字段(至少 `fbp` 非空;`fbc` 非空才能解析 fbclid)
|
||||
- 改造 `insertADTag`:
|
||||
- 若 `$this->fbclid` 为空,调用上述方法补全 `fbclid/fbp/ua/ip`
|
||||
- 若补全后仍无 fbclid,`return`(不写空 tag)
|
||||
- 否则按现有逻辑 `setInfo`
|
||||
|
||||
**可选**:若希望仅 IMEI 走回退、不影响 Name 注册,可把回退逻辑放在 [`ImeiRegisterService`](slot_user/app/service/register/ImeiRegisterService.php) 覆写 `insertADTag`,而非改 Abstract 基类。
|
||||
|
||||
---
|
||||
|
||||
## 前置条件(产品/客户端)
|
||||
|
||||
方案生效依赖用户路径:
|
||||
|
||||
1. 用户从 FB 广告进入落地页(UA 含 `FB_IAB`)
|
||||
2. 落地页成功调用 `SiteController::fb`(带正确 `source`)
|
||||
3. 用户在合理时间窗口内完成 iOS 首次 IMEI 注册,且 `source` 与落地页一致
|
||||
|
||||
若用户跳过落地页直接装包注册,仍无法归因——这是架构固有限制,不是实现 bug。
|
||||
|
||||
---
|
||||
|
||||
## 风险清单
|
||||
|
||||
| 风险 | 级别 | 说明 |
|
||||
|------|------|------|
|
||||
| fbc 为空 | 中 | 落地页允许空 fbc,补写无效,FbDot 仍会报 fbclid 为空 |
|
||||
| 同 source 串号 | 中 | 多用户并发时可能拿错上一条 fbc |
|
||||
| pop 后写失败 | 低 | 数据丢失,仅影响单次 attribution |
|
||||
| ad_fbp 未被 FbDot 直接使用 | 低 | 当前 FbDot 用 fbclid 重新 generate fbp;落地页 fbp 写入 user tag 但 CAPI 仍用生成值。MVP 可接受;若要最佳 attribution 需后续改 FbDot 优先用 `ad_fbp`/真实 `fbc` |
|
||||
|
||||
---
|
||||
|
||||
## 验证建议
|
||||
|
||||
1. 落地页 mock:POST `SiteController::fb`,确认 Redis `user:dot:set:{source}` 有 member
|
||||
2. IMEI 新用户注册(不带 fb 参数),查 `UserInfoCache` / user tag 中 `ad_fbc`、`ad_fbp`、`ad_ua`
|
||||
3. 触发 `slot_console` 注册打点,确认 `FbDot::registerEvent` 不再报 fbclid 为空
|
||||
4. 并发两条落地页 + 两次注册,观察是否出现串号(评估是否需加时间窗)
|
||||
|
||||
---
|
||||
|
||||
## 总结
|
||||
|
||||
**可行,且是 iOS 上架场景下合理的补偿方案。** 实现重点不是「能不能取 Redis」,而是:
|
||||
|
||||
1. 正确从 `fbc` 解析 fbclid 再写入 `ad_fbc`
|
||||
2. 仅在客户端无 fb 参数时回退
|
||||
3. 用 `zPopMax` 消费最新一条
|
||||
4. 接受同渠道并发下的 attribution 碰撞,或后续迭代匹配策略
|
||||
|
||||
按你确认的范围,**只改 IMEI 新注册 `insertADTag` 即可**,无需动登录链路。
|
||||
Reference in New Issue
Block a user