Files
2026-05-21 19:01:49 +08:00

21 KiB
Raw Permalink Blame History

邀请码系统设计文档

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


📋 目录

  1. 系统概述
  2. 核心功能
  3. 技术实现
  4. 数据库设计
  5. 业务流程
  6. 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. 生成邀请码

// 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. 创建用户邀请码

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. 处理邀请关系绑定

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. 注册流程集成

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

// 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

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

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

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='营销活动表';

活动配置示例

{
  "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

请求参数

{
  phone: string;        // 手机号
  code: string;         // 验证码
  password?: string;    // 密码(可选)
  nickname?: string;    // 昵称(可选)
  inviteCode?: string;  // 邀请码(可选)
}

响应

{
  token: string;        // JWT token
  user: {
    id: number;
    phone: string;
    nickname: string;
    // ...
  }
}

2. 微信授权登录(带邀请码)

接口POST /api/app/auth/login/wechat

请求参数

{
  code: string;         // 微信授权code
  nickname?: string;    // 昵称
  avatar?: string;      // 头像
  inviteCode?: string;  // 邀请码(新用户)
}

3. 获取用户邀请信息

接口GET /api/app/invite/stats

响应

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

4. 获取邀请列表

接口GET /api/app/invite/list

响应

{
  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

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

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

功能

  • 显示用户邀请码
  • 生成小程序码(二维码)
  • 展示返现规则
  • 保存海报图片
  • 复制邀请码

关键代码

<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

// 注册
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

// 获取邀请统计
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