# 邀请码系统设计文档

> **版本**：v1.0  
> **最后更新**：2026-05-13  
> **状态**：✅ 已实现

---

## 📋 目录

1. [系统概述](#系统概述)
2. [核心功能](#核心功能)
3. [技术实现](#技术实现)
4. [数据库设计](#数据库设计)
5. [业务流程](#业务流程)
6. [API接口](#api接口)
7. [前端实现](#前端实现)

---

## 系统概述

### 功能定位
邀请码系统是一个用户增长工具，通过邀请返现机制激励用户推广平台，实现用户裂变增长。

### 核心特性
- ✅ 用户注册时自动生成唯一邀请码
- ✅ 支持小程序码扫码邀请
- ✅ 邀请关系自动绑定
- ✅ 防重复绑定和自我邀请
- ✅ 邀请统计和返现管理

---

## 核心功能

### 1. 邀请码生成规则

**格式规范**：
- 长度：6位
- 字符集：数字 0-9 + 大写字母 A-Z（共36个字符）
- 示例：`A3X9K2`, `B7M4N1`, `5K8P2Z`

**生成时机**：
- 用户注册时自动生成
- 微信授权登录（新用户）
- 微信手机号登录（新用户）
- 开发模式自动注册

**唯一性保证**：
- 生成后检查数据库是否存在
- 如果重复则递归重新生成
- 理论上可生成 36^6 = 2,176,782,336 个不同的邀请码

### 2. 邀请关系绑定

**绑定时机**：
- ✅ 用户注册时绑定
- ❌ 用户登录时不绑定（老用户）

**绑定规则**：
1. 只有新注册用户才能绑定邀请关系
2. 每个用户只能被邀请一次（防重复绑定）
3. 用户不能使用自己的邀请码（防自我邀请）
4. 邀请码必须存在且有效

**绑定流程**：
```
扫码进入 → 存储邀请码 → 用户注册 → 验证邀请码 → 创建邀请关系 → 更新统计
```

### 3. 邀请统计

**统计维度**：
- `totalInvites`：累计邀请人数
- `totalOrders`：被邀请人累计订单数
- `totalCashback`：累计返现金额
- `availableBalance`：可用余额
- `withdrawnAmount`：已提现金额

**更新时机**：
- 邀请关系创建时：`totalInvites +1`
- 被邀请人下单时：`totalOrders +1`, `totalCashback += 返现金额`
- 提现时：`availableBalance -= 提现金额`, `withdrawnAmount += 提现金额`

---

## 技术实现

### 后端实现（NestJS）

#### 核心服务方法

**1. 生成邀请码**
```typescript
// apps/server/src/modules/app/auth/auth.service.ts

private generateInviteCode(): string {
  const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
  let code = '';
  for (let i = 0; i < 6; i++) {
    code += chars.charAt(Math.floor(Math.random() * chars.length));
  }
  return code;
}
```

**2. 创建用户邀请码**
```typescript
private async createUserInviteCode(userId: number): Promise<void> {
  // 1. 查找启用的邀请返现活动
  const activity = await this.activityRepo
    .createQueryBuilder('activity')
    .where('activity.type = :type', { type: 'invite_cashback' })
    .andWhere('activity.enabled = :enabled', { enabled: true })
    .orderBy('activity.id', 'DESC')
    .getOne();

  if (!activity) {
    this.logger.warn('没有启用的邀请返现活动，跳过生成邀请码');
    return;
  }

  // 2. 生成邀请码并检查唯一性
  const inviteCode = this.generateInviteCode();
  const existing = await this.inviteStatsRepo.findOne({
    where: { inviteCode },
  });

  if (existing) {
    // 递归重新生成
    return this.createUserInviteCode(userId);
  }

  // 3. 创建邀请统计记录
  const inviteStats = this.inviteStatsRepo.create({
    activityId: activity.id,
    userId,
    inviteCode,
    totalInvites: 0,
    totalOrders: 0,
    totalCashback: 0,
    availableBalance: 0,
    withdrawnAmount: 0,
  });
  await this.inviteStatsRepo.save(inviteStats);
}
```

**3. 处理邀请关系绑定**
```typescript
private async handleInviteCode(inviteeId: number, inviteCode: string): Promise<void> {
  if (!inviteCode) return;

  // 1. 检查用户是否已被邀请过
  const existingInvitation = await this.invitationRepo.findOne({
    where: { inviteeId },
  });
  if (existingInvitation) {
    this.logger.log(`用户已被邀请过: userId=${inviteeId}`);
    return;
  }

  // 2. 查找邀请人
  const inviterStats = await this.inviteStatsRepo.findOne({
    where: { inviteCode },
  });
  if (!inviterStats) {
    this.logger.warn(`邀请码不存在: ${inviteCode}`);
    return;
  }

  // 3. 防止自我邀请
  if (inviterStats.userId === inviteeId) {
    this.logger.warn(`用户不能使用自己的邀请码: userId=${inviteeId}`);
    return;
  }

  // 4. 创建邀请关系
  const invitation = this.invitationRepo.create({
    activityId: inviterStats.activityId,
    inviterId: inviterStats.userId,
    inviteeId,
    inviteCode,
  });
  await this.invitationRepo.save(invitation);

  // 5. 更新邀请人统计
  inviterStats.totalInvites += 1;
  await this.inviteStatsRepo.save(inviterStats);

  this.logger.log(`邀请关系绑定成功: inviter=${inviterStats.userId}, invitee=${inviteeId}`);
}
```

**4. 注册流程集成**
```typescript
async register(dto: RegisterDto) {
  // 1. 创建用户
  const user = await this.userRepo.save(newUser);
  
  // 2. 创建用户账户
  await this.createUserAccount(user.id);
  
  // 3. 生成用户邀请码
  await this.createUserInviteCode(user.id);
  
  // 4. 处理邀请关系绑定
  if (dto.inviteCode) {
    await this.handleInviteCode(user.id, dto.inviteCode);
  }
  
  // 5. 返回token
  return this.generateToken(user);
}
```

#### 依赖注入配置

**AuthModule**
```typescript
// apps/server/src/modules/app/auth/auth.module.ts

@Module({
  imports: [
    TypeOrmModule.forFeature([
      User,
      UserAccount,
      MktInvitation,
      MktUserInviteStats,
      MktActivity,
    ]),
    JwtModule.registerAsync({...}),
  ],
  controllers: [AuthController],
  providers: [AuthService],
  exports: [AuthService, JwtModule],
})
export class UserAuthModule {}
```

---

## 数据库设计

### 1. 邀请关系表（mkt_invitations）

```sql
CREATE TABLE `mkt_invitations` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `activity_id` int NOT NULL COMMENT '活动ID',
  `inviter_id` int NOT NULL COMMENT '邀请人用户ID',
  `invitee_id` int NOT NULL COMMENT '被邀请人用户ID',
  `invite_code` varchar(20) NOT NULL COMMENT '邀请码',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_invitee` (`invitee_id`),
  KEY `idx_inviter` (`inviter_id`),
  KEY `idx_activity` (`activity_id`),
  KEY `idx_invite_code` (`invite_code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='邀请关系表';
```

**字段说明**：
- `inviter_id`：邀请人ID
- `invitee_id`：被邀请人ID（唯一索引，保证一个用户只能被邀请一次）
- `invite_code`：使用的邀请码

### 2. 用户邀请统计表（mkt_user_invite_stats）

```sql
CREATE TABLE `mkt_user_invite_stats` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `activity_id` int NOT NULL COMMENT '活动ID',
  `user_id` int NOT NULL COMMENT '用户ID',
  `invite_code` varchar(20) NOT NULL COMMENT '邀请码',
  `total_invites` int NOT NULL DEFAULT '0' COMMENT '累计邀请人数',
  `total_orders` int NOT NULL DEFAULT '0' COMMENT '被邀请人累计订单数',
  `total_cashback` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '累计返现金额',
  `available_balance` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '可用余额',
  `withdrawn_amount` decimal(10,2) NOT NULL DEFAULT '0.00' COMMENT '已提现金额',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uk_user_activity` (`user_id`, `activity_id`),
  UNIQUE KEY `uk_invite_code` (`invite_code`),
  KEY `idx_activity` (`activity_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户邀请统计表';
```

**字段说明**：
- `invite_code`：用户的邀请码（唯一索引）
- `total_invites`：累计邀请人数
- `total_cashback`：累计返现金额
- `available_balance`：可用余额（可提现）
- `withdrawn_amount`：已提现金额

### 3. 营销活动表（mkt_activities）

```sql
CREATE TABLE `mkt_activities` (
  `id` int NOT NULL AUTO_INCREMENT COMMENT '主键',
  `type` varchar(50) NOT NULL COMMENT '活动类型：invite_cashback-邀请返现',
  `name` varchar(100) NOT NULL COMMENT '活动名称',
  `description` text COMMENT '活动描述',
  `config` json COMMENT '活动配置（JSON格式）',
  `enabled` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否启用',
  `start_time` timestamp NULL COMMENT '开始时间',
  `end_time` timestamp NULL COMMENT '结束时间',
  `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
  `updated_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
  PRIMARY KEY (`id`),
  KEY `idx_type` (`type`),
  KEY `idx_enabled` (`enabled`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='营销活动表';
```

**活动配置示例**：
```json
{
  "firstOrderRate": 0.05,    // 首单返现比例 5%
  "normalOrderRate": 0.03,   // 持续订单返现比例 3%
  "minOrderAmount": 100,     // 最低订单金额
  "maxCashback": 500         // 单笔最高返现
}
```

---

## 业务流程

### 完整邀请流程

```
┌─────────────┐
│  用户A注册  │
└──────┬──────┘
       │
       ▼
┌─────────────────────┐
│ 生成邀请码: A3X9K2  │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│ 创建邀请统计记录    │
└─────────────────────┘

       ⋮

┌─────────────────────┐
│ 用户B扫描A的小程序码│
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│ 小程序首页获取scene │
│ 参数: i=A3X9K2      │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│ 存储到本地Storage   │
│ pendingInviteCode   │
└─────────────────────┘

       ⋮

┌─────────────────────┐
│   用户B注册账号     │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│ 生成B的邀请码:      │
│ B7M4N1              │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│ 读取本地邀请码      │
│ A3X9K2              │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│ 验证邀请码有效性    │
│ - 邀请码存在        │
│ - 非自我邀请        │
│ - 未被邀请过        │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│ 创建邀请关系记录    │
│ inviter: A          │
│ invitee: B          │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│ 更新A的邀请统计     │
│ totalInvites +1     │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│ 清除本地邀请码      │
└─────────────────────┘

       ⋮

┌─────────────────────┐
│ 用户B下单支付       │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│ 计算返现金额        │
│ - 首单: 5%          │
│ - 持续: 3%          │
└──────┬──────────────┘
       │
       ▼
┌─────────────────────┐
│ 更新A的邀请统计     │
│ - totalOrders +1    │
│ - totalCashback +=  │
│ - availableBalance +=│
└─────────────────────┘
```

---

## API接口

### 1. 用户注册（带邀请码）

**接口**：`POST /api/app/auth/register`

**请求参数**：
```typescript
{
  phone: string;        // 手机号
  code: string;         // 验证码
  password?: string;    // 密码（可选）
  nickname?: string;    // 昵称（可选）
  inviteCode?: string;  // 邀请码（可选）
}
```

**响应**：
```typescript
{
  token: string;        // JWT token
  user: {
    id: number;
    phone: string;
    nickname: string;
    // ...
  }
}
```

### 2. 微信授权登录（带邀请码）

**接口**：`POST /api/app/auth/login/wechat`

**请求参数**：
```typescript
{
  code: string;         // 微信授权code
  nickname?: string;    // 昵称
  avatar?: string;      // 头像
  inviteCode?: string;  // 邀请码（新用户）
}
```

### 3. 获取用户邀请信息

**接口**：`GET /api/app/invite/stats`

**响应**：
```typescript
{
  inviteCode: string;           // 我的邀请码
  totalInvites: number;         // 累计邀请人数
  totalOrders: number;          // 被邀请人订单数
  totalCashback: number;        // 累计返现
  availableBalance: number;     // 可用余额
  withdrawnAmount: number;      // 已提现金额
}
```

### 4. 获取邀请列表

**接口**：`GET /api/app/invite/list`

**响应**：
```typescript
{
  list: [
    {
      inviteeId: number;        // 被邀请人ID
      inviteeName: string;      // 被邀请人昵称
      inviteeAvatar: string;    // 被邀请人头像
      orderCount: number;       // 订单数
      cashbackAmount: number;   // 返现金额
      createdAt: string;        // 邀请时间
    }
  ],
  total: number;
}
```

---

## 前端实现

### 小程序端（uni-app）

#### 1. 首页获取邀请码

**文件**：`apps/miniapp/src/pages/index/index.vue`

```typescript
import { onLoad } from '@dcloudio/uni-app';

onLoad((options) => {
  // 处理小程序码 scene 参数
  if (options.scene) {
    try {
      const decodedScene = decodeURIComponent(options.scene);
      const params = new URLSearchParams(decodedScene);
      const inviteCode = params.get('i');
      
      if (inviteCode) {
        // 存储邀请码到本地
        uni.setStorageSync('pendingInviteCode', inviteCode);
        console.log('保存邀请码:', inviteCode);
      }
    } catch (error) {
      console.error('解析scene参数失败:', error);
    }
  }
});
```

#### 2. 注册时传递邀请码

**文件**：`apps/miniapp/src/pages/register/index.vue`

```typescript
const handleRegister = async () => {
  // 读取本地存储的邀请码
  const inviteCode = uni.getStorageSync('pendingInviteCode');
  
  // 调用注册接口
  const res = await register({
    phone: form.phone,
    code: form.code,
    password: form.password,
    inviteCode,  // 传递邀请码
  });
  
  // 注册成功后清除邀请码
  if (inviteCode) {
    uni.removeStorageSync('pendingInviteCode');
  }
  
  // 保存token并跳转
  uni.setStorageSync('token', res.token);
  uni.switchTab({ url: '/pages/index/index' });
};
```

#### 3. 邀请海报页面

**文件**：`apps/miniapp/src/pages/invite/poster.vue`

**功能**：
- 显示用户邀请码
- 生成小程序码（二维码）
- 展示返现规则
- 保存海报图片
- 复制邀请码

**关键代码**：
```vue
<template>
  <view class="page-poster">
    <!-- 海报卡片 -->
    <view class="poster-card">
      <!-- 顶部英雄区 -->
      <view class="poster-hero">
        <text class="hero-title">邀请好友 赚现金</text>
        <text class="hero-subtitle">好友下单您赚钱</text>
      </view>
      
      <!-- 收益展示 -->
      <view class="earnings-section">
        <view class="earning-item">
          <text class="earning-value">5%</text>
          <text class="earning-label">首单返现</text>
        </view>
        <view class="earning-item">
          <text class="earning-value">3%</text>
          <text class="earning-label">持续返现</text>
        </view>
      </view>
      
      <!-- 二维码区域 -->
      <view class="qrcode-section">
        <canvas canvas-id="qrcode" class="qrcode-canvas"></canvas>
        <text class="qrcode-tip">长按识别二维码</text>
      </view>
      
      <!-- 邀请码 -->
      <view class="invite-code-section">
        <text class="invite-code-label">我的邀请码</text>
        <text class="invite-code-value">{{ inviteCode }}</text>
      </view>
    </view>
    
    <!-- 操作按钮 -->
    <view class="action-bar">
      <button class="btn-primary" @tap="saveImage">保存海报</button>
      <button class="btn-secondary" @tap="copyInviteCode">复制邀请码</button>
    </view>
  </view>
</template>

<script setup lang="ts">
import { ref, onMounted } from 'vue';
import { getInviteStats } from '@/api/user/invite';

const inviteCode = ref('');

onMounted(async () => {
  // 获取用户邀请信息
  const res = await getInviteStats();
  inviteCode.value = res.inviteCode;
  
  // 生成小程序码
  generateQRCode();
});

const generateQRCode = () => {
  // 使用 canvas 生成小程序码
  // scene 参数格式: i=邀请码
  const scene = `i=${inviteCode.value}`;
  // ... 生成二维码逻辑
};

const copyInviteCode = () => {
  uni.setClipboardData({
    data: inviteCode.value,
    success: () => {
      uni.showToast({ title: '邀请码已复制', icon: 'success' });
    },
  });
};
</script>
```

#### 4. API封装

**文件**：`apps/miniapp/src/api/user/auth.ts`

```typescript
// 注册
export function register(data: {
  phone: string;
  code: string;
  password?: string;
  nickname?: string;
  inviteCode?: string;
}) {
  return post('/api/app/auth/register', data);
}

// 微信授权登录
export function loginByWechat(
  code: string,
  nickname?: string,
  avatar?: string,
  inviteCode?: string
) {
  return post('/api/app/auth/login/wechat', {
    code,
    nickname,
    avatar,
    inviteCode,
  });
}
```

**文件**：`apps/miniapp/src/api/user/invite.ts`

```typescript
// 获取邀请统计
export function getInviteStats() {
  return get('/api/app/invite/stats');
}

// 获取邀请列表
export function getInviteList(params: {
  page: number;
  pageSize: number;
}) {
  return get('/api/app/invite/list', params);
}
```

---

## 测试场景

### 功能测试

#### 1. 邀请码生成测试
- [ ] 用户注册后自动生成邀请码
- [ ] 邀请码格式正确（6位，0-9A-Z）
- [ ] 邀请码唯一性（不重复）
- [ ] 微信授权登录新用户生成邀请码

#### 2. 邀请关系绑定测试
- [ ] 扫码进入小程序，邀请码正确存储
- [ ] 注册时邀请码正确传递
- [ ] 邀请关系正确创建
- [ ] 邀请人统计正确更新（totalInvites +1）
- [ ] 防重复绑定（已被邀请的用户不能再次绑定）
- [ ] 防自我邀请（不能使用自己的邀请码）
- [ ] 无效邀请码处理（邀请码不存在）

#### 3. 邀请统计测试
- [ ] 邀请人数统计正确
- [ ] 订单数统计正确
- [ ] 返现金额计算正确
- [ ] 可用余额更新正确

#### 4. 边界情况测试
- [ ] 没有启用的邀请活动时的处理
- [ ] 邀请码生成冲突时的重试机制
- [ ] 并发注册时的邀请码唯一性
- [ ] 老用户登录时不处理邀请码

---

## 注意事项

### 安全性
1. **邀请码唯一性**：必须确保邀请码在数据库中唯一
2. **防刷机制**：需要防止恶意刷邀请关系
3. **数据一致性**：邀请统计数据需要与实际订单数据保持一致

### 性能优化
1. **邀请码索引**：在 `invite_code` 字段上建立唯一索引
2. **查询优化**：邀请列表查询需要分页和索引优化
3. **缓存策略**：用户邀请统计可以使用 Redis 缓存

### 扩展性
1. **多活动支持**：系统支持多个邀请活动同时进行
2. **返现规则配置**：返现比例可在活动配置中灵活调整
3. **邀请码格式**：可以根据需要调整邀请码长度和字符集

---

## 更新日志

| 日期 | 版本 | 说明 |
|------|------|------|
| 2026-05-13 | v1.0 | 初始版本，完成邀请码系统设计和实现 |

---

**维护团队**：开发团队  
**最后更新**：2026-05-13
