Files
social-app/docs/plans/2026-02-27-invite-code-design.md
T

162 lines
7.1 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.
# 邀请码机制设计
**Date**: 2026-02-27
**Status**: Approved
**Author**: User + AI
## 背景
为用户注册增加邀请码机制,支持:
- 每个用户注册后自动获得专属邀请码
- 注册时可填写他人邀请码
- 记录邀请关系和使用统计
- 支持运营邀请码(批量、限额、过期、禁用)
- 预留奖励策略配置
## 数据模型
### invite_codes 表
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| id | UUID | PK | 主键 |
| code | VARCHAR(8) | UNIQUE, NOT NULL | 邀请码 |
| owner_id | UUID | FK → profiles.id, nullable | 所属用户,NULL 为运营码 |
| max_uses | INT | nullable | 最大使用次数,NULL 无限制 |
| used_count | INT | DEFAULT 0 | 已用次数 |
| expires_at | TIMESTAMPTZ | nullable | 过期时间,NULL 永不过期 |
| status | VARCHAR(20) | NOT NULL | active / disabled |
| reward_config | JSONB | DEFAULT '{}' | 奖励策略配置 |
| created_at | TIMESTAMPTZ | NOT NULL | 创建时间 |
| updated_at | TIMESTAMPTZ | NOT NULL | 更新时间 |
| deleted_at | TIMESTAMPTZ | nullable | 软删除 |
**索引:**
- `ix_invite_codes_code` ON (code) UNIQUE
- `ix_invite_codes_owner_id` ON (owner_id)
- `ix_invite_codes_status_expires` ON (status, expires_at)
**CHECK 约束:**
- `status IN ('active', 'disabled')`
- `used_count >= 0`
- `max_uses IS NULL OR max_uses > 0`
### profiles 表变更
| 字段 | 类型 | 约束 | 说明 |
|------|------|------|------|
| referred_by | UUID | FK → profiles.id, nullable | 被谁邀请 |
**索引:**
- `ix_profiles_referred_by` ON (referred_by)
## API 变更
### POST /auth/verifications
**Request:**
```json
{
"username": "string (3-30 chars)",
"email": "string (email)",
"password": "string (min 6 chars)",
"redirect_to": "string?",
"invite_code": "string (8 chars)?" // 新增,可选
}
```
**Response:** 202 Accepted(不变)
## 注册流程
```
┌─────────────────────────────────────────────────────────────────┐
│ 1. POST /auth/verifications │
│ - 存储 username + invite_code 到 Supabase metadata │
│ - 发送 OTP 邮件 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 2. POST /auth/verifications/verify │
│ - 验证 OTP │
│ - 创建 auth.users 记录 │
│ - 触发 on_auth_user_created trigger │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 3. Trigger: on_auth_user_created │
│ a. INSERT INTO profiles (id, username, ...) │
│ b. 生成 8 位随机邀请码 │
│ c. INSERT INTO invite_codes (code, owner_id, ...) │
│ d. 从 metadata 取 invite_code,执行邀请校验逻辑 │
└─────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 4. 邀请码校验逻辑 │
│ IF invite_code 存在 AND │
│ status = 'active' AND │
│ (expires_at IS NULL OR expires_at > now()) AND │
│ (max_uses IS NULL OR used_count < max_uses) │
│ THEN │
│ UPDATE profiles SET referred_by = invite_codes.owner_id │
│ UPDATE invite_codes SET used_count = used_count + 1 │
│ END IF │
└─────────────────────────────────────────────────────────────────┘
```
## 邀请码生成规则
- 8 位随机字符串
- 字符集:`ABCDEFGHJKLMNPQRSTUVWXYZ23456789`(排除易混淆字符 0/O/1/I/L
- 唯一性:数据库 UNIQUE 约束 + 生成时冲突重试(最多 10 次)
## 使用记录查询
通过 profiles 表查询:
```sql
-- 查询某个邀请码的使用记录
SELECT p.id, p.username, p.created_at
FROM profiles p
JOIN invite_codes ic ON ic.owner_id = :owner_id
WHERE p.referred_by = ic.owner_id
ORDER BY p.created_at DESC;
-- 查询某个用户邀请了多少人
SELECT COUNT(*) FROM profiles WHERE referred_by = :user_id;
```
## 边界情况
| 场景 | 处理方式 |
|------|----------|
| 邀请码不存在 | 跳过邀请,注册正常成功 |
| 邀请码已禁用 | 跳过邀请 |
| 邀请码已过期 | 跳过邀请 |
| 邀请码已达上限 | 跳过邀请 |
| 用户自邀(用自己的码) | 不可能,用户注册时还没有邀请码 |
| 重复使用同一邀请码 | 允许(until max_uses |
## 后续扩展
1. **奖励系统**:通过 `reward_config` JSONB 字段配置不同奖励策略
2. **运营批量码**`owner_id = NULL` 的邀请码,支持市场推广
3. **邀请排行榜**:基于 `used_count` 或 profiles 关联查询
4. **邀请码回收**:软删除 `deleted_at`,保留历史记录
## 迁移计划
1. 新增迁移文件创建 `invite_codes`
2. 新增迁移文件给 `profiles` 表添加 `referred_by` 字段
3. 修改 `on_auth_user_created` trigger 增加邀请码逻辑
4. 修改 `VerificationCreateRequest` schema 添加 `invite_code` 字段
5. 修改 `create_verification` gateway 传递 `invite_code` 到 metadata
## 测试用例
1. 注册时不填邀请码 → 正常注册,生成专属邀请码
2. 注册时填写有效邀请码 → 关联邀请关系,used_count +1
3. 注册时填写无效邀请码 → 正常注册,无邀请关系
4. 邀请码达上限后使用 → 正常注册,无邀请关系
5. 运营邀请码使用 → 正常注册,无 referred_byowner_id = NULL