feat: 迭代
This commit is contained in:
@@ -0,0 +1,778 @@
|
||||
# 邀请码系统设计文档
|
||||
|
||||
> **版本**: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
|
||||
Reference in New Issue
Block a user