# 邀请码机制设计

**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_by（owner_id = NULL）
