Files
cursor/plans/IMEI FB-3a02c714.plan.md
ray zhou 2dd9f17da9 ok
2026-06-29 14:51:55 +08:00

8.3 KiB
Raw Permalink Blame History


todos:

  • id: "parse-fbc-fbclid" content: "在 insertADTag 回退路径实现 fbc→fbclid 解析与字段映射ad_fbp/ad_ua/ad_ip" status: pending
  • id: "redis-pop-latest" content: "实现 resolveLatestDotPayloadBySourcezPopMax 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/UserController.php 里已有被注释的 zPopMax 回退逻辑,slot_user/app/service/register/ImeiRegisterService.php 在新用户路径调用 insertADTag

你已确认范围:仅 IMEI 新用户注册(现有 insertADTag 调用点),不覆盖老用户登录补写。


现有数据流

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 校验 FB_IAB UA写入 user:dot:set:{source}member 为 serialize(fbp,fbc,ua,ip,source)score 为 microtime
IMEI 注册 ImeiRegisterService::run() 新用户分支 L115 调用 insertADTag
写入用户标签 insertADTag 当前固定用 $this->fbclidad_fbciOS 客户端通常为空
FB 正式打点 FbDotad_fbcFbService::generateFBC() 用 fbclid 生成 fbc/fbp

方案为何可行

  1. 基础设施已就绪Redis key USER_DOT_SET_KEY、落地页写入、注册侧回退思路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 写:

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_fbpad_uaad_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

  • 新增私有方法,例如 resolveLatestDotPayloadBySource(string $source): ?array
    • Redis::zPopMax(RedisKeyManagerService::getDotSetKey($source), 1)
    • unserialize + 校验必需字段(至少 fbp 非空;fbc 非空才能解析 fbclid
  • 改造 insertADTag
    • $this->fbclid 为空,调用上述方法补全 fbclid/fbp/ua/ip
    • 若补全后仍无 fbclidreturn(不写空 tag
    • 否则按现有逻辑 setInfo

可选:若希望仅 IMEI 走回退、不影响 Name 注册,可把回退逻辑放在 ImeiRegisterService 覆写 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. 落地页 mockPOST SiteController::fb,确认 Redis user:dot:set:{source} 有 member
  2. IMEI 新用户注册(不带 fb 参数),查 UserInfoCache / user tag 中 ad_fbcad_fbpad_ua
  3. 触发 slot_console 注册打点,确认 FbDot::registerEvent 不再报 fbclid 为空
  4. 并发两条落地页 + 两次注册,观察是否出现串号(评估是否需加时间窗)

总结

可行,且是 iOS 上架场景下合理的补偿方案。 实现重点不是「能不能取 Redis」而是

  1. 正确从 fbc 解析 fbclid 再写入 ad_fbc
  2. 仅在客户端无 fb 参数时回退
  3. zPopMax 消费最新一条
  4. 接受同渠道并发下的 attribution 碰撞,或后续迭代匹配策略

按你确认的范围,只改 IMEI 新注册 insertADTag 即可,无需动登录链路。