hyapp-server/docs/registration-invite-code-architecture.md
2026-05-08 18:48:29 +08:00

539 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Registration Invite Code Architecture
本文定义 App 注册邀请码能力。用户注册时可以选填邀请码;如果该邀请码属于某个有效用户,则该用户成为新用户的邀请人。邀请码归因是用户关系事实,不能只把字符串放在 `users.invite_code` 里。
## Goals
- 每个用户有一个可展示、可复制的永久邀请码。
- 新用户注册或完成资料时可以选填邀请码。
- 非空邀请码必须能解析到同一 `app_code` 下的有效邀请人。
- 邀请关系一旦绑定不可被客户端修改。
- 邀请关系必须落 MySQLRedis 只能做短 TTL 查询缓存。
- 邀请统计必须拆成 `invite_count``valid_invite_count`:前者表示成功填码绑定关系的人数,后者表示被邀请人累计充值达到后台配置门槛的人数。
- 后续奖励、活动、风控和运营统计都从邀请关系事实消费,不反推历史字符串。
## Non-Goals
- 首版不在注册链路内同步发奖励。
- 首版不支持用户自己修改邀请码。
- 首版不支持跨 App 邀请。
- 首版不把邀请码等同于 Agency、BD、币商或主播组织关系。
- 首版不做邀请排行榜;只保存事实和提供轻量查询。
- 首版不在注册链路内同步判断有效邀请;有效邀请由钱包充值事实异步推进。
## Ownership
| Capability | Owner | Rule |
| --- | --- | --- |
| 邀请码生成 | `user-service` | 新用户创建时生成自己的邀请码 |
| 邀请码解析 | `user-service` | 注册事务内按 `app_code + code` 查询有效 code |
| 邀请关系绑定 | `user-service` | 一个被邀请用户最多一条关系 |
| 外部 HTTP | `gateway-service` | 转发 `invite_code`,不自行解析归属 |
| 邀请人数统计 | `user-service` invite module | `invite_count` 来自 `user_invite_relations` 绑定事实 |
| 有效邀请判定 | `user-service` invite module + `wallet-service` recharge event | 钱包只产出充值事实,邀请模块按后台策略累计并标记有效 |
| 我的页展示 | `gateway-service` + `user-service` | `overview` 返回当前用户自己的邀请码、邀请人数和有效邀请人数 |
| 奖励/通知 | 后续活动或钱包模块 | 通过 outbox/event 异步消费,不阻塞注册 |
## Product Rules
### Code Ownership
- 邀请码属于用户不属于设备、session、三方账号或 Agency。
- 邀请码按 `app_code` 隔离;`lalu` 的邀请码不能邀请 `popoo` 用户。
- 用户被禁用或封禁后,默认不允许继续作为新邀请人的有效 code owner历史邀请关系不删除。
- 用户自己的邀请码创建后长期稳定。后续如果允许换码,必须保留旧码和历史关系快照。
### Binding Rules
| Case | Result |
| --- | --- |
| 注册不填邀请码 | 创建用户,不创建邀请关系 |
| 注册填写有效邀请码 | 创建用户,并绑定 `inviter_user_id` |
| 注册填写不存在、停用、跨 App 或 owner 非 active 的邀请码 | 返回 `INVALID_INVITE_CODE`,不静默忽略 |
| 用户填写自己的邀请码 | 返回 `INVALID_INVITE_CODE` |
| 已有邀请关系的用户再次提交邀请码 | 不修改原关系 |
| 已完成注册用户提交邀请码 | 不修改原关系;如果通过 onboarding 入口提交,按已完成资料逻辑不处理邀请码 |
| 已存在三方身份再次登录并带新邀请码 | 不创建、不修改邀请关系 |
非空邀请码失败时选择“失败注册”而不是“忽略邀请码”,原因是用户和渠道都需要明确知道归因没有成功,避免后续奖励和申诉没有事实依据。
### Count Rules
邀请统计必须拆成两个指标:
| Metric | Meaning | Source | Update Timing |
| --- | --- | --- | --- |
| `invite_count` | 邀请人数,表示有多少用户成功填写邀请码并绑定到当前邀请人 | `user_invite_relations` | 绑定关系成功后增加 |
| `valid_invite_count` | 有效邀请人数,表示有多少被邀请用户累计成功充值金币达到策略门槛 | `user_invite_recharge_progress.status=valid` | 钱包充值事件累计达到门槛后增加 |
规则:
- `invite_count` 只看填码绑定事实,不要求被邀请人充值、消费、上麦或进入房间。
- `valid_invite_count` 默认门槛是被邀请人累计成功充值 `80000` `COIN`,门槛必须由后台策略配置,不能写死在代码里。
- 有效邀请只统计钱包标记为“充值”的 `COIN` 账务事实;送礼收入、奖励、后台普通调账、资源赠送和当前余额变化都不能直接计入。
- 币商给玩家转金币如果已经由 `wallet-service.wallet_recharge_records` 记录为用户充值,可以计入;是否计入 Apple/Google、线下人工充值、币商转账等类型由策略的 `eligible_recharge_types` 控制。
- 同一个被邀请用户只能让邀请人成为一次有效邀请;充值超过门槛后不重复增加。
- 有效状态一旦达成,首版不因后续消费减少而回退;如果后续接入退款、拒付或作弊处理,需要后台风控撤销或新增负向事件重算。
- 邀请人后续被封禁不删除历史关系和历史有效事实,但可以让其邀请码失效,避免继续产生新关系。
## App Flow
首版兼容两种客户端采集时机:
1. 客户端在三方登录前已经拿到邀请码:提交到 `POST /api/v1/auth/third-party/login`
2. 客户端在资料补全页让用户输入邀请码:提交到 `POST /api/v1/users/me/onboarding/complete`
推荐 App 把邀请码放在资料补全页,因为当前架构是“三方登录即创建未完成用户”,注册页用户资料都在 `CompleteOnboarding` 原子提交。
```mermaid
sequenceDiagram
participant C as Client
participant G as gateway-service
participant U as user-service
participant DB as hyapp_user
C->>G: POST /auth/third-party/login(provider, credential, optional invite_code)
G->>U: LoginThirdParty
U->>U: verify provider credential
alt new third-party identity
U->>DB: create users + own user_invite_codes
opt invite_code present
U->>DB: resolve invite code and insert user_invite_relations
end
U->>DB: create third_party_identity + auth_session
else existing identity
U->>DB: create auth_session only
end
U-->>G: token(profile_completed=false/true)
C->>G: POST /users/me/onboarding/complete(username, avatar, country, optional invite_code)
G->>U: CompleteOnboarding
U->>DB: lock users row
opt no existing invite relation and invite_code present
U->>DB: resolve invite code and insert user_invite_relations
end
U->>DB: update profile_completed=true
U-->>G: new access_token(profile_completed=true)
```
## HTTP Contract
### Login Or Register
```http
POST /api/v1/auth/third-party/login
```
`invite_code` 仍然是可选字段。只有本次请求首次创建用户时,该字段才可能绑定邀请关系;已有用户登录时忽略该字段,不允许通过重新登录改归因。
### Complete Onboarding
```http
POST /api/v1/users/me/onboarding/complete
Authorization: Bearer <access_token>
```
Request:
```json
{
"username": "Alice",
"avatar": "https://cdn.example/avatar.png",
"gender": "female",
"country": "SG",
"invite_code": "A1B2C3"
}
```
Response:
```json
{
"user_id": "918274650129384448",
"profile_completed": true,
"invite": {
"bound": true,
"invite_code": "A1B2C3",
"inviter_user_id": "10001"
},
"token": {
"access_token": "jwt_xxx",
"profile_completed": true
}
}
```
`invite` 可以首版只返回 `bound`,但服务端必须在库里保存 `inviter_user_id`。客户端不能根据返回内容自行计算邀请关系。
### My Invite Code
首屏可以直接跟随 `GET /api/v1/users/me/overview` 返回:
```json
{
"invite": {
"my_invite_code": "A1B2C3",
"invite_enabled": true,
"invite_count": 12,
"valid_invite_count": 3,
"valid_invite_threshold_coin": 80000
}
}
```
`invite_count``valid_invite_count` 必须来自预计算 read model。首版没有 read model 时返回 `0` 或不返回,不能为了首页展示实时 `COUNT(*)` 扫邀请关系或充值流水。`invited_count` 这个旧命名语义不清,后续对外接口应收敛为 `invite_count`
## Data Model
### `user_invite_codes`
```sql
CREATE TABLE user_invite_codes (
app_code VARCHAR(32) NOT NULL,
invite_code_id BIGINT NOT NULL,
code VARCHAR(64) NOT NULL,
owner_user_id BIGINT NOT NULL,
status VARCHAR(32) NOT NULL,
is_primary TINYINT(1) NOT NULL DEFAULT 1,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, invite_code_id),
UNIQUE KEY uk_user_invite_codes_code (app_code, code),
KEY idx_user_invite_codes_owner (app_code, owner_user_id, status)
);
```
字段规则:
- `code` 入库前 trim、转大写只允许 `A-Z0-9`,长度建议 6 到 12。
- `status` 首版只需要 `active``disabled`
- 一个 owner 首版只创建一个 primary code。MySQL 无法跨状态表达复杂唯一时,先通过事务锁用户行和查询保证;后续可加 generated column 做 active primary 唯一。
- code 不使用 `users.current_display_user_id`,避免靓号、短号过期或短号运营变更影响邀请关系。
### `user_invite_relations`
```sql
CREATE TABLE user_invite_relations (
app_code VARCHAR(32) NOT NULL,
invited_user_id BIGINT NOT NULL,
inviter_user_id BIGINT NOT NULL,
invite_code_id BIGINT NOT NULL,
invite_code VARCHAR(64) NOT NULL,
source VARCHAR(32) NOT NULL,
bound_at_ms BIGINT NOT NULL,
request_id VARCHAR(96) NOT NULL,
created_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, invited_user_id),
KEY idx_user_invite_relations_inviter (app_code, inviter_user_id, bound_at_ms),
KEY idx_user_invite_relations_code (app_code, invite_code, bound_at_ms)
);
```
字段规则:
- `invited_user_id` 主键保证一个用户只能被归因一次。
- `invite_code` 是绑定时的快照;即使后续 code 被禁用或换码,历史关系仍可解释。
- `source` 首版可取 `login``onboarding`
- 历史关系不因为邀请人被封禁、改资料或换码而删除。
### `user_invite_validity_policies`
有效邀请门槛必须由后台配置。默认策略是累计成功充值 `80000` `COIN` 后成为有效邀请。
```sql
CREATE TABLE user_invite_validity_policies (
app_code VARCHAR(32) NOT NULL,
policy_id BIGINT NOT NULL,
policy_version VARCHAR(64) NOT NULL,
status VARCHAR(32) NOT NULL,
region_id BIGINT NOT NULL DEFAULT 0,
required_recharge_coin_amount BIGINT NOT NULL,
eligible_recharge_types JSON NOT NULL,
effective_from_ms BIGINT NOT NULL,
effective_to_ms BIGINT NULL,
created_by_user_id BIGINT NOT NULL,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, policy_id),
UNIQUE KEY uk_user_invite_validity_policy_version (app_code, policy_version),
KEY idx_user_invite_validity_policy_active (app_code, region_id, status, effective_from_ms)
);
```
字段规则:
- `required_recharge_coin_amount` 默认配置为 `80000`,单位是钱包 `COIN` 最小整数单位。
- `region_id=0` 表示全局默认策略;有区域策略时按被邀请用户注册/当前业务区域选择,具体选择规则必须写入策略说明。
- `eligible_recharge_types` 控制哪些充值类型可计入有效邀请,例如 `provider_iap``coin_seller_transfer``manual_recharge`。奖励、礼物收入、活动赠送和普通调账默认不计入。
- 策略修改只能影响新达成有效邀请的判断;已达成的 `valid` 事实不自动重算。
### `user_invite_recharge_progress`
该表保存每个被邀请用户距离有效邀请的累计进度。它不是钱包余额来源,只消费钱包充值事实。
```sql
CREATE TABLE user_invite_recharge_progress (
app_code VARCHAR(32) NOT NULL,
invited_user_id BIGINT NOT NULL,
inviter_user_id BIGINT NOT NULL,
policy_id BIGINT NOT NULL,
policy_version VARCHAR(64) NOT NULL,
required_recharge_coin_amount BIGINT NOT NULL,
accumulated_recharge_coin_amount BIGINT NOT NULL,
status VARCHAR(32) NOT NULL,
valid_at_ms BIGINT NULL,
valid_transaction_id VARCHAR(96) NOT NULL DEFAULT '',
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, invited_user_id),
KEY idx_user_invite_progress_inviter_status (app_code, inviter_user_id, status, valid_at_ms),
KEY idx_user_invite_progress_updated (app_code, status, updated_at_ms)
);
```
字段规则:
- `status` 首版使用 `pending``valid`
- `accumulated_recharge_coin_amount` 只累计符合策略的成功充值金币数量。
- `valid_transaction_id` 保存让累计金额首次达到门槛的钱包交易 ID便于审计。
- 同一个 `invited_user_id` 只能有一条进度;达到 `valid` 后不再重复增加 `valid_invite_count`
### `user_invite_event_consumption`
钱包充值事件可能重投,邀请模块必须记录消费幂等。
```sql
CREATE TABLE user_invite_event_consumption (
app_code VARCHAR(32) NOT NULL,
source VARCHAR(64) NOT NULL,
event_id VARCHAR(128) NOT NULL,
transaction_id VARCHAR(96) NOT NULL,
user_id BIGINT NOT NULL,
status VARCHAR(32) NOT NULL,
error_message VARCHAR(512) NOT NULL DEFAULT '',
consumed_at_ms BIGINT NULL,
created_at_ms BIGINT NOT NULL,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, source, event_id),
KEY idx_user_invite_event_tx (app_code, transaction_id),
KEY idx_user_invite_event_status (app_code, status, updated_at_ms)
);
```
同一 `transaction_id` 如果通过不同事件重复到达,也不能重复累计;实现时应在同一事务中检查 `event_id``transaction_id` 的已消费状态。
### `user_invite_counters`
我的页和后台列表不能实时扫关系表或钱包流水,应读取计数 read model。
```sql
CREATE TABLE user_invite_counters (
app_code VARCHAR(32) NOT NULL,
inviter_user_id BIGINT NOT NULL,
invite_count BIGINT NOT NULL DEFAULT 0,
valid_invite_count BIGINT NOT NULL DEFAULT 0,
updated_at_ms BIGINT NOT NULL,
PRIMARY KEY (app_code, inviter_user_id)
);
```
字段规则:
- `invite_count``UserInvited` 事实推进。
- `valid_invite_count``UserInviteBecameValid` 事实推进。
- 计数写入必须幂等;重复消费同一事件不能重复增加。
- 如果计数异常,允许后台重算:`invite_count``user_invite_relations` 汇总,`valid_invite_count``user_invite_recharge_progress.status='valid'` 汇总。
### `users.invite_code`
现有 `users.invite_code` 只保留“注册时提交的邀请码字符串快照”。它不能作为权威邀请关系来源,因为它没有 `inviter_user_id`,也无法处理 code 换绑、停用、跨 App 和历史审计。
新代码应优先读取 `user_invite_relations``users.invite_code` 可以继续保留用于兼容和排障。
## Transaction Rules
新用户创建事务内建议顺序:
1. 校验 provider credential。
2. 锁定或分配 `user_id` 和默认短号。
3. 创建 `users`
4. 创建用户自己的 `user_invite_codes`
5. 如果请求带 `invite_code`,解析并锁定 code owner 的 `users` 行。
6. 校验 owner active、非自己、同 `app_code`
7. 插入 `user_invite_relations`
8. 创建 `third_party_identities``auth_sessions`
9.`UserRegistered` / `UserInvited` outbox 事件。
`CompleteOnboarding` 绑定邀请码时:
1. 锁当前 `users` 行。
2. 如果 `profile_completed=true`,不允许再绑定邀请码。
3. 如果已有 `user_invite_relations`,不修改原关系。
4. 如果请求带非空 `invite_code`,解析 code锁 inviter 用户行,插入关系。
5. 更新资料、国家、区域和 `profile_completed`
6. 返回新的 access token。
关系插入和资料完成必须在同一事务内提交,避免用户看到注册完成但邀请关系丢失。
邀请关系绑定成功后,同事务或同一 outbox 消费链路需要初始化 `user_invite_recharge_progress(status=pending)` 并幂等增加 `user_invite_counters.invite_count`。如果计数更新失败,关系事实不能回滚;必须通过 `UserInvited` outbox 重试补偿计数。
## Effective Invite Flow
有效邀请不在注册链路内计算。它由钱包充值事实异步推进:
```mermaid
sequenceDiagram
participant W as wallet-service
participant WO as wallet_outbox
participant C as invite consumer
participant U as user-service invite module
participant DB as hyapp_user
W->>W: commit successful COIN recharge
W->>WO: WalletRechargeRecorded(user_id, coin_amount, recharge_type)
C->>WO: consume recharge event
C->>U: ApplyInviteRecharge(user_id, transaction_id, coin_amount)
U->>DB: find user_invite_relations by invited_user_id
alt relation exists and recharge_type eligible
U->>DB: lock user_invite_recharge_progress
U->>DB: accumulate recharge coin amount
opt first time accumulated >= required threshold
U->>DB: mark status=valid and valid_at_ms
U->>DB: increment user_invite_counters.valid_invite_count
U->>DB: write UserInviteBecameValid outbox
end
else no relation or ineligible recharge
U-->>C: idempotent skipped
end
```
处理规则:
- 钱包只负责产出充值事实,不理解邀请关系,也不直接增加邀请统计。
- 邀请模块按 `app_code + transaction_id` 对充值事件做幂等消费,同一充值事件不能重复累计。
- 被邀请用户未绑定邀请关系时,充值事件跳过,不创建关系。
- 充值事件早于邀请关系绑定时不应补算,除非后台明确执行重算任务;正常链路要求先填邀请码、后充值才算有效邀请。
- 达到门槛的判断使用累计充值金币数量,不使用当前 `COIN` 余额;用户充值后消费完仍然可以成为有效邀请。
- 如果后续接入退款/拒付事件,需要新增负向充值处理策略;首版只处理正向成功充值。
## Code Generation
邀请码生成建议:
- 使用服务端随机生成,不让客户端提交自己的 code。
- 字符集使用 `ABCDEFGHJKLMNPQRSTUVWXYZ23456789`,去掉容易混淆的 `0/O/1/I`
- 首版 8 位即可;如果发生唯一冲突,最多重试 5 次,仍失败返回内部错误并告警。
- 不把手机号、邮箱、设备号、短号直接作为邀请码。
## Caching
注册量没有明显压力前,邀请码解析直接查 MySQL。
如果后续加入 Redis
| Key | Value | TTL | Rule |
| --- | --- | --- | --- |
| `invite_code:{app_code}:{code}` | `owner_user_id,status,version` | 5-30 min | 只做加速 |
Redis miss 或异常时必须回 MySQL 或 fail-closed不能把不存在当有效。禁用邀请码、封禁用户或换码时必须主动删除缓存不能只依赖 TTL。
## Outbox And Rewards
注册链路只写事实,不同步发奖励。后续奖励通过事件消费:
- `UserRegistered`
- `UserInvited`
- `UserInviteBecameValid`
`UserInvited` payload 至少包含:
```json
{
"app_code": "lalu",
"invited_user_id": "20001",
"inviter_user_id": "10001",
"invite_code": "A1B2C3",
"source": "onboarding",
"bound_at_ms": 1777996800000
}
```
`UserInviteBecameValid` payload 至少包含:
```json
{
"app_code": "lalu",
"invited_user_id": "20001",
"inviter_user_id": "10001",
"policy_id": "9001",
"policy_version": "invite-valid-v1",
"required_recharge_coin_amount": 80000,
"accumulated_recharge_coin_amount": 80000,
"valid_transaction_id": "wtx_01",
"valid_at_ms": 1777996800000
}
```
奖励消费者必须幂等,幂等键使用 `app_code + invited_user_id + reward_type`,不能因为 outbox 重投重复发放。需要按有效邀请发奖励时,只消费 `UserInviteBecameValid`,不要消费原始 `UserInvited`
## Risk Controls
- gateway 对带邀请码的注册请求继续使用 public auth 频控。
- user-service 记录 `device_id`、IP、UA、country_by_ip用于后续识别同设备互邀、批量注册和异常渠道。
- 自己邀请自己必须拒绝。
- 同设备大量互邀、同 IP 大量注册、邀请人短时间异常增长不阻断注册主链路,但要进入风控统计或后台审核。
- 有效邀请达成前必须保留充值事件来源、充值类型和触发交易 ID便于识别币商刷量、同设备互充、异常退款和人工充值滥用。
- 后台可以冻结某个邀请人的新邀请资格或奖励资格,但不能删除历史 `user_invite_relations`
- 邀请码解析失败不要返回邀请人是否存在的细节;统一 `INVALID_INVITE_CODE`
## Admin Requirements
后台需要提供最小管理能力:
- 查询用户自己的邀请码、邀请人数、有效邀请人数、被邀请用户列表和有效达成明细。
- 启用/禁用某个用户的邀请码;禁用只影响新绑定,不删除历史关系。
- 配置有效邀请策略:门槛金币数、适用区域、可计入充值类型、生效时间和停用时间。
- 查看某个被邀请用户的充值累计进度和触发有效邀请的钱包交易 ID。
- 标记异常邀请或撤销奖励资格;撤销奖励资格不能物理删除邀请关系和有效达成记录。
- 触发计数重算任务,用于修复 `user_invite_counters` 与事实表不一致。
所有后台写操作必须经过 `hyapp-admin-server` 鉴权和审计,不能让 App 用户直接调用。
## Implementation Sequence
1. 增加 `user_invite_codes``user_invite_relations` 表。
2. 增加 `user_invite_validity_policies``user_invite_recharge_progress``user_invite_event_consumption``user_invite_counters``user_outbox`
3. 后台增加有效邀请策略配置,默认 `required_recharge_coin_amount=80000`
4. user-service 新用户创建时生成自己的邀请码。
5. `LoginThirdParty` 首次创建用户时解析可选 `invite_code` 并绑定关系。
6. `CompleteOnboarding` 增加可选 `invite_code`,仅在未完成资料且未绑定关系时处理。
7. 绑定成功后写 `UserInvited`,并通过 outbox 幂等维护 `invite_count`
8. 消费钱包 `WalletRechargeRecorded` 事件,累计被邀请用户充值金币,达到策略门槛后写 `UserInviteBecameValid` 并维护 `valid_invite_count`
9. `GetUser``GetUserOverview` 增加当前用户的邀请码、邀请人数、有效邀请人数和当前有效门槛。
10. gateway HTTP request/response 增加字段映射。
11. 加测试和基础监控。
## Test Matrix
| Scenario | Expected |
| --- | --- |
| 新用户不填邀请码 | 注册成功,无 `user_invite_relations` |
| 新用户填有效邀请码 | 注册成功,关系表写入 inviter |
| 新用户填不存在邀请码 | 返回 `INVALID_INVITE_CODE`,不创建完成关系 |
| 邀请码跨 App | 返回 `INVALID_INVITE_CODE` |
| 邀请人 disabled/banned | 返回 `INVALID_INVITE_CODE` |
| 用户尝试填自己的邀请码 | 返回 `INVALID_INVITE_CODE` |
| 已绑定关系后再次提交邀请码 | 原关系不变 |
| 已完成注册后提交邀请码 | 不修改原关系,不补填邀请人 |
| 三方身份再次登录带邀请码 | 登录成功但不修改邀请关系 |
| 并发提交两个邀请码 | 只有一条关系成功,另一条命中幂等或冲突 |
| 邀请码大小写/空格 | trim + uppercase 后解析 |
| 被邀请用户累计充值 79999 COIN | `invite_count` 已增加,`valid_invite_count` 不增加 |
| 被邀请用户累计充值达到 80000 COIN | 首次标记 `user_invite_recharge_progress.status=valid``valid_invite_count` 增加 1 |
| 被邀请用户一次充值超过门槛 | 只产生 1 个有效邀请 |
| 被邀请用户多次充值累计达到门槛 | 达标那笔交易触发有效邀请 |
| 同一充值事件重复消费 | 累计金额和有效邀请计数不重复增加 |
| 充值发生在绑定邀请码之前 | 默认不补算有效邀请,除非后台重算任务明确处理 |
| 后台把有效邀请门槛改为 100000 | 已经 valid 的历史事实不重算,新达成用户按新策略判断 |
| 我的页 overview | 返回当前用户自己的邀请码、`invite_count``valid_invite_count`,不实时扫关系或充值流水 |