21 KiB
21 KiB
邀请码系统设计文档
版本:v1.0
最后更新:2026-05-13
状态:✅ 已实现
📋 目录
系统概述
功能定位
邀请码系统是一个用户增长工具,通过邀请返现机制激励用户推广平台,实现用户裂变增长。
核心特性
- ✅ 用户注册时自动生成唯一邀请码
- ✅ 支持小程序码扫码邀请
- ✅ 邀请关系自动绑定
- ✅ 防重复绑定和自我邀请
- ✅ 邀请统计和返现管理
核心功能
1. 邀请码生成规则
格式规范:
- 长度:6位
- 字符集:数字 0-9 + 大写字母 A-Z(共36个字符)
- 示例:
A3X9K2,B7M4N1,5K8P2Z
生成时机:
- 用户注册时自动生成
- 微信授权登录(新用户)
- 微信手机号登录(新用户)
- 开发模式自动注册
唯一性保证:
- 生成后检查数据库是否存在
- 如果重复则递归重新生成
- 理论上可生成 36^6 = 2,176,782,336 个不同的邀请码
2. 邀请关系绑定
绑定时机:
- ✅ 用户注册时绑定
- ❌ 用户登录时不绑定(老用户)
绑定规则:
- 只有新注册用户才能绑定邀请关系
- 每个用户只能被邀请一次(防重复绑定)
- 用户不能使用自己的邀请码(防自我邀请)
- 邀请码必须存在且有效
绑定流程:
扫码进入 → 存储邀请码 → 用户注册 → 验证邀请码 → 创建邀请关系 → 更新统计
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:邀请人IDinvitee_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. 边界情况测试
- 没有启用的邀请活动时的处理
- 邀请码生成冲突时的重试机制
- 并发注册时的邀请码唯一性
- 老用户登录时不处理邀请码
注意事项
安全性
- 邀请码唯一性:必须确保邀请码在数据库中唯一
- 防刷机制:需要防止恶意刷邀请关系
- 数据一致性:邀请统计数据需要与实际订单数据保持一致
性能优化
- 邀请码索引:在
invite_code字段上建立唯一索引 - 查询优化:邀请列表查询需要分页和索引优化
- 缓存策略:用户邀请统计可以使用 Redis 缓存
扩展性
- 多活动支持:系统支持多个邀请活动同时进行
- 返现规则配置:返现比例可在活动配置中灵活调整
- 邀请码格式:可以根据需要调整邀请码长度和字符集
更新日志
| 日期 | 版本 | 说明 |
|---|---|---|
| 2026-05-13 | v1.0 | 初始版本,完成邀请码系统设计和实现 |
维护团队:开发团队
最后更新:2026-05-13