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

162 lines
7.1 KiB
Markdown
Raw Normal View History

# 邀请码机制设计
**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