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