179 lines
8.6 KiB
Markdown
179 lines
8.6 KiB
Markdown
---
|
||
name: 转盘与Agent分享归因
|
||
overview: 实现「转盘分享双计(agent + 转盘)、agent 分享仅计 agent」:wheel 与 user_agent 各一条 share_url 短码,MQ 的 share_origin 直接来自 invite_code 查表;console 仅 share_origin=wheel 时发转盘助力。
|
||
todos:
|
||
- id: console-origin
|
||
content: "slot_console: wheel 独立落库 origin=wheel;Logic 仅 wheel 计助力;invite-link/createShareUrl 走 wheel 短码"
|
||
status: completed
|
||
- id: user-mq
|
||
content: "slot_user: shouldNotifyAgentByInviteOrigin 含 wheel;MQ share_origin 来自 share_url.origin"
|
||
status: completed
|
||
- id: agent-filter
|
||
content: "slot_agent(建议): console 回调粗筛 share_origin=wheel"
|
||
status: completed
|
||
- id: tests-docs
|
||
content: 单测/集成测 + 需求文档/YApi/deploy 对齐(撤销 wheel→user_agent 映射说明)
|
||
status: completed
|
||
isProject: false
|
||
---
|
||
|
||
# 转盘分享 vs Agent 分享单向归因方案(share_origin 双短码)
|
||
|
||
## 产品规则(SSOT)
|
||
|
||
| 分享入口 | share_url.origin | Agent 邀请 | Lucky Rewards 助力 |
|
||
| --- | --- | --- | --- |
|
||
| 转盘分享(`from=wheel` / invite-link) | `wheel` | 算 | 算 |
|
||
| Agent 分享(`from=user_agent`) | `user_agent` | 算 | **不算** |
|
||
|
||
## 方案选型(已确认)
|
||
|
||
**方案 A:两条短码** — `wheel` 与 `user_agent` 在 `share_url` 各存一行,同一邀请人可有 **两个** `short_code`。
|
||
|
||
- MQ `share_origin` 由注册时 `invite_code` 查 `share_url.origin` 得到,**无需** `landing_scene`、**无需** FE 注册透传额外字段。
|
||
- 撤销当前 [`ShareService::resolvePersistOrigin()`](slot_console/app/service/ShareService.php) 把 `wheel → user_agent` 的映射。
|
||
|
||
```mermaid
|
||
sequenceDiagram
|
||
participant Inviter
|
||
participant Console as slot_console
|
||
participant User as slot_user
|
||
participant Agent as slot_agent
|
||
|
||
alt WheelShare
|
||
Inviter->>Console: createShareUrl from=wheel
|
||
Console->>Console: share_url origin=wheel 短码W
|
||
Note over Inviter: 分享短码W
|
||
User->>User: register invite_code=W
|
||
User->>Agent: MQ share_origin=wheel
|
||
Agent->>Console: callback share_origin=wheel
|
||
Console->>Console: success发Spin
|
||
else AgentShare
|
||
Inviter->>Console: createShareUrl from=user_agent
|
||
Console->>Console: share_url origin=user_agent 短码A
|
||
User->>User: register invite_code=A
|
||
User->>Agent: MQ share_origin=user_agent
|
||
Agent->>Console: callback share_origin=user_agent
|
||
Console->>Console: skipped_not_wheel
|
||
Note over Agent: agent关系照常
|
||
end
|
||
```
|
||
|
||
---
|
||
|
||
## 为何之前不能「只改 MQ 不改落库」
|
||
|
||
当前实现把 `from=wheel` **映射成** `user_agent` 落库(复用一条短码)。此时代码查表得到的 `origin` 永远是 `user_agent`,MQ 无法出现 `share_origin=wheel`,除非 FE 在注册时强行覆盖 —— 那与 `landing_scene` 同类,且语义与 DB 不一致。
|
||
|
||
**双短码**后:`invite_code` 本身即归因,链路最简。
|
||
|
||
---
|
||
|
||
## 实现步骤
|
||
|
||
### 1. slot_console:wheel 独立短码 + 助力判定
|
||
|
||
**[`ShareService`](slot_console/app/service/ShareService.php)**
|
||
- **删除或停用** `resolvePersistOrigin()` 对 wheel 的 `user_agent` 映射;`SHARE_FROM_WHEEL` 直接作为 `createUrl()` 的 `$from` / `ShareUrlModel.origin`。
|
||
- `isAgentShareFrom()` 保持 `user_agent` + `wheel`(agent 文案接口仍适用)。
|
||
- 更新/替换 [`ShareServiceWheelFromTest`](slot_console/tests/Unit/ShareServiceWheelFromTest.php):断言 wheel 创建后 `origin=wheel`,且与 `user_agent` 为**不同** `short_code`(同一 uid 两行)。
|
||
|
||
**[`GiftController::createShareUrl()`](slot_console/app/api/controller/GiftController.php)**
|
||
- `from=wheel` 时 `createUrl(ShareService::SHARE_FROM_WHEEL)`,不再经 `resolvePersistOrigin`。
|
||
|
||
**[`LuckyRewardInviteLogic::buildInviteLink()`](slot_console/app/api/logic/LuckyRewardInviteLogic.php)**
|
||
- 同上,直接 `createUrl(SHARE_FROM_WHEEL)`。
|
||
- PHPDoc 更新:不再写「落库复用 user_agent 短码」。
|
||
|
||
**[`LuckyRewardInviteLogic::handleInviteBind()`](slot_console/app/api/logic/LuckyRewardInviteLogic.php)**
|
||
- 将现有「必须 `share_origin=user_agent`」改为 **必须 `share_origin=wheel`**:
|
||
|
||
```php
|
||
if ($callbackDto->shareOrigin !== ShareService::SHARE_FROM_WHEEL) {
|
||
return $this->buildInviteBindResult(
|
||
InviteBindResultEntity::STATUS_SKIPPED_NOT_WHEEL,
|
||
'Invite code origin is not wheel'
|
||
);
|
||
}
|
||
```
|
||
|
||
**[`InviteBindResultEntity`](slot_console/app/entity/luckyReward/InviteBindResultEntity.php)**
|
||
- 新增 `STATUS_SKIPPED_NOT_WHEEL = 'skipped_not_wheel'`(保留旧 `skipped_not_user_agent` 常量亦可,新逻辑用新 status)。
|
||
- **不需要** `landing_scene` / `skipped_no_landing`(除非后续另有用途)。
|
||
|
||
**[`InviteBindCallbackDTO`](slot_console/app/dto/luckyReward/InviteBindCallbackDTO.php)**
|
||
- 已有 `shareOrigin`,无需新增 `landingScene`(agent/SDK 侧 landing_scene 字段可留空,不读)。
|
||
|
||
### 2. slot_user:发 MQ + agent 资格
|
||
|
||
**[`AbstractRegisterService::shouldNotifyAgentByInviteOrigin()`](slot_user/app/service/register/AbstractRegisterService.php)**
|
||
|
||
```php
|
||
return in_array($origin, ['user_agent', 'wheel'], true);
|
||
```
|
||
|
||
**[`notifyAgentInviteBind()`](slot_user/app/service/register/AbstractRegisterService.php)**
|
||
- `share_origin` 继续写 `$this->invite_share_origin`(来自 `share_url.origin`),wheel 注册自然为 `wheel`。
|
||
- **无需**新增 `landing_scene` 属性或 MQ 字段(与 agent 侧已有 `landing_scene` 字段兼容即可,留空)。
|
||
|
||
**[`resolveInviteUidByInviteCode()`](slot_user/app/service/register/AbstractRegisterService.php)** — 已读 `shareRow->origin`,双短码下无需改逻辑。
|
||
|
||
### 3. slot_agent:console 回调粗筛(建议)
|
||
|
||
[`LuckyRewardInviteCallbackGatewayService::canNotifyInviteBind()`](slot_agent/app/service/LuckyRewardInviteCallbackGatewayService.php) 增加:
|
||
|
||
- 仅 `share_origin === 'wheel'` 时 HTTP 调 console(agent 绑定、奖励、关系同步**不受影响**,仍处理全部 invite_bind MQ)。
|
||
- `user_agent` 绑定不再打 console,减少无效请求。
|
||
|
||
### 4. 前端 / 产品说明
|
||
|
||
- 转盘页 Copy Link / 社媒分享:使用 **invite-link** 或 **create-share-url `from=wheel`** 返回的 `code`(wheel 短码)。
|
||
- Agent 中心分享:继续使用 **`from=user_agent`** 返回的 `code`(另一条短码)。
|
||
- **不要混用**:用 agent 短码进转盘页,只计 agent、不计转盘 —— 符合产品规则。
|
||
|
||
### 5. 测试
|
||
|
||
| 仓库 | 用例 |
|
||
| --- | --- |
|
||
| slot_console | `ShareServiceWheelFromTest`:同 uid 存在 wheel + user_agent 两行、短码不同 |
|
||
| slot_console | `LuckyRewardInviteLogicUnitTest`:`share_origin=user_agent` → `skipped_not_wheel`;`wheel` + 新注册 → success |
|
||
| slot_console | `LuckyRewardInviteBindIntegrationTest`:`buildValidInviteBindPayload()` 改 `share_origin` 为 `wheel` |
|
||
| slot_user | `shouldNotifyAgentByInviteOrigin`:wheel / user_agent 为 true,其它为 false |
|
||
|
||
### 6. 文档与 YApi
|
||
|
||
- 更新 [`05_slot_console_邀请助力与弹窗方案.md`](docs/requirements/lucky_rewards/05_slot_console_邀请助力与弹窗方案.md) §2:有效助力 = **新注册** + **`share_origin=wheel`**(双短码,非 landing_scene)。
|
||
- 更新 [`00_整体技术方案.md`](docs/requirements/lucky_rewards/00_整体技术方案.md) 归因段落。
|
||
- [`lucky_reward_deploy.md`](slot_console/doc/lucky_reward_deploy.md)、YApi **81** / invite-link:说明 wheel 与 user_agent **不同 code**;撤销「复用 user_agent 短码」描述。
|
||
- innerapi 回调文档:`skipped_not_wheel`;`landing_scene` 标注为可选/未使用。
|
||
|
||
---
|
||
|
||
## 判定优先级(console 更新后)
|
||
|
||
1. invalid_params
|
||
2. skipped_self_invite
|
||
3. skipped_old_user
|
||
4. **skipped_not_wheel**(原 skipped_not_user_agent 逻辑替换)
|
||
5. activity_closed
|
||
6. already_processed
|
||
7. success
|
||
|
||
---
|
||
|
||
## 风险与边界
|
||
|
||
- **历史已发出的 wheel 链接**:若当时用的是映射后的 `user_agent` 短码,升级后只计 agent、不计转盘;新 wheel 链接需走 invite-link / `from=wheel` 拿新短码。可在发版说明中写清。
|
||
- **同一用户两条码**:运营/客服需知 wheel 与 agent 链接不可互换。
|
||
- **手工绑码**:`share_url.origin` 决定 MQ `share_origin`;绑 agent 码不计转盘。
|
||
- **slot_sdk / agent** `landing_scene` 字段保留兼容,本期不参与判定。
|
||
|
||
---
|
||
|
||
## 涉及仓库
|
||
|
||
- **必改**:`slot_console`、`slot_user`
|
||
- **建议改**:`slot_agent`(HTTP 粗筛 `share_origin=wheel`)
|
||
- **不改**:`slot_sdk`(已有 share_origin 转发)
|
||
- **FE**:使用正确短码即可,无 register 额外字段
|