推荐关系系统是团队模块的核心组成部分,负责管理用户之间的推荐关系、团队结构和推荐码。该系统通过建立用户间的直推和间推关系,形成多层级的团队结构,为收益分成和达人等级提供基础数据支持。
直推关系是指用户A直接推荐用户B注册,则用户A是用户B的直推上级,用户B是用户A的直推下级。
用户A ──直推──> 用户B
直推关系具有以下特点:
间推关系是指通过直推关系形成的间接推荐关系。例如,用户A推荐用户B,用户B推荐用户C,则用户A是用户C的间推上级,用户C是用户A的间推下级。
用户A ──直推──> 用户B ──直推──> 用户C
用户A ──间推──> 用户C
间推关系具有以下特点:
用户的所有直推和间推下级构成该用户的团队。团队结构是一个多层级的树状结构,用户在其中可以同时扮演上级和下级的角色。
用户A
/ \
用户B 用户C
/ \ \
用户D 用户E 用户F
在上图中:
用户推荐关系表采用简化设计,只存储用户与其直接上级(直推关系)的关系,间接关系通过缓存或实时计算获取。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| user_id | bigint | 用户ID |
| referrer_id | bigint | 直接推荐人ID |
| created_at | timestamp | 创建时间 |
| updated_at | timestamp | 更新时间 |
示例数据:
id | user_id | referrer_id | created_at
---|---------|------------|------------
1 | 2 | 1 | 2023-05-01 10:00:00
2 | 3 | 2 | 2023-05-01 11:00:00
3 | 4 | 3 | 2023-05-01 12:00:00
在上面的示例中:
通过这种链式关系,可以计算出间接推荐关系:
推荐码表存储用户的推荐码信息,用于邀请新用户注册。
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| user_id | bigint | 用户ID |
| code | varchar | 推荐码 |
| usage_count | int | 使用次数 |
| status | tinyint | 状态(1=有效,0=无效) |
| expire_time | timestamp | 过期时间,NULL表示永不过期 |
| created_at | timestamp | 创建时间 |
| updated_at | timestamp | 更新时间 |
当新用户注册并使用推荐码时,系统会建立推荐关系:
/**
* 建立推荐关系
*/
public function createReferralRelation(int $userId, int $referrerId): bool
{
return DB::transaction(function () use ($userId, $referrerId) {
// 验证用户和推荐人是否存在
if (!$this->userRepository->exists($userId) || !$this->userRepository->exists($referrerId)) {
throw new UserNotFoundException("用户或推荐人不存在");
}
// 验证是否已存在推荐关系
if ($this->referralRepository->hasReferrer($userId)) {
throw new ReferralException("用户已有推荐人,不能重复设置");
}
// 验证是否形成循环推荐
if ($this->checkCircularReferral($userId, $referrerId)) {
throw new ReferralException("不能形成循环推荐关系");
}
// 创建直推关系
$this->referralRepository->create([
'user_id' => $userId,
'referrer_id' => $referrerId
]);
// 更新推荐人的直推人数
$this->talentRepository->incrementDirectCount($referrerId);
// 清除相关缓存
$this->clearReferralCaches($userId, null, $referrerId);
// 更新推荐人的团队人数
$this->updatePromotionCounts($referrerId);
// 触发推荐关系创建事件
event(new ReferralCreatedEvent($userId, $referrerId));
// 检查并更新达人等级
$this->talentService->checkAndUpdateTalentLevel($referrerId);
return true;
});
}
/**
* 更新用户及其所有上级的团队人数
*/
private function updatePromotionCounts(int $userId): void
{
// 更新当前用户的团队人数
$directCount = $this->countDirectReferrals($userId);
$promotionCount = $this->calculatePromotionCount($userId);
$this->talentRepository->updateCounts($userId, $directCount, $promotionCount);
// 递归更新所有上级的团队人数
$directReferrer = $this->referralRepository->getDirectReferrer($userId);
if ($directReferrer) {
$this->updatePromotionCounts($directReferrer->referrer_id);
}
}
/**
* 计算用户的团队总人数
*/
private function calculatePromotionCount(int $userId): int
{
// 获取所有团队成员
$allMembers = $this->getAllPromotionMembers($userId);
return count($allMembers);
}
系统为每个用户生成唯一的推荐码,用于邀请新用户:
/**
* 生成推荐码
*/
public function generateReferralCode(int $userId): string
{
// 检查用户是否已有推荐码
$existingCode = $this->referralCodeRepository->getByUserId($userId);
if ($existingCode) {
return $existingCode->code;
}
// 生成唯一推荐码
$code = $this->generateUniqueCode();
// 保存推荐码
$this->referralCodeRepository->create([
'user_id' => $userId,
'code' => $code,
'usage_count' => 0,
'status' => 1
]);
return $code;
}
/**
* 生成唯一推荐码
*/
private function generateUniqueCode(): string
{
$characters = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
$length = 6;
do {
$code = '';
for ($i = 0; $i < $length; $i++) {
$code .= $characters[random_int(0, strlen($characters) - 1)];
}
} while ($this->referralCodeRepository->codeExists($code));
return $code;
}
系统提供多种方式查询推荐关系,使用缓存优化查询性能:
/**
* 获取用户的所有上级(包括直接和间接)
*/
public function getAllReferrers(int $userId): array
{
// 尝试从缓存获取
$cacheKey = "promotion:user:{$userId}:all_referrers";
$cachedReferrers = Redis::get($cacheKey);
if ($cachedReferrers !== null) {
return json_decode($cachedReferrers, true);
}
// 缓存不存在,计算所有上级
$allReferrers = [];
$this->calculateAllReferrers($userId, $allReferrers);
// 缓存结果
Redis::setex($cacheKey, 86400, json_encode($allReferrers)); // 缓存1天
return $allReferrers;
}
/**
* 递归计算用户的所有上级
*/
private function calculateAllReferrers(int $userId, array &$allReferrers, int $level = 0, int $maxLevel = 20): void
{
// 限制最大层级
if ($level >= $maxLevel) {
return;
}
// 获取直接上级
$directReferrer = $this->referralRepository->getDirectReferrer($userId);
if (!$directReferrer) {
return;
}
$referrerId = $directReferrer->referrer_id;
// 添加到结果集
$allReferrers[] = [
'user_id' => $referrerId,
'level' => $level == 0 ? 1 : 2, // 1=直推, 2=间推
'created_at' => $directReferrer->created_at
];
// 递归获取上级的上级
$this->calculateAllReferrers($referrerId, $allReferrers, $level + 1, $maxLevel);
}
/**
* 获取用户的所有团队成员(包括直接和间接)
*/
public function getAllPromotionMembers(int $userId, int $page = 1, int $pageSize = 20): array
{
// 尝试从缓存获取
$cacheKey = "promotion:user:{$userId}:all_members";
$cachedMembers = Redis::get($cacheKey);
$allMembers = [];
if ($cachedMembers !== null) {
$allMembers = json_decode($cachedMembers, true);
} else {
// 缓存不存在,计算所有下级
$this->calculateAllPromotionMembers($userId, $allMembers);
// 缓存结果
Redis::setex($cacheKey, 86400, json_encode($allMembers)); // 缓存1天
}
// 计算总数
$total = count($allMembers);
// 分页
$offset = ($page - 1) * $pageSize;
$pagedMembers = array_slice($allMembers, $offset, $pageSize);
// 获取成员详细信息
$members = [];
foreach ($pagedMembers as $member) {
$userInfo = $this->userRepository->find($member['user_id']);
if ($userInfo) {
$members[] = [
'user_id' => $userInfo->id,
'username' => $userInfo->username,
'avatar' => $userInfo->avatar,
'level' => $member['level'],
'created_at' => $member['created_at']
];
}
}
// 返回结果
return [
'total' => $total,
'page' => $page,
'page_size' => $pageSize,
'total_pages' => ceil($total / $pageSize),
'members' => $members
];
}
/**
* 递归计算用户的所有团队成员
*/
private function calculateAllPromotionMembers(int $userId, array &$allMembers, int $level = 0, int $maxLevel = 20): void
{
// 限制最大层级
if ($level >= $maxLevel) {
return;
}
// 获取直接下级
$directMembers = $this->referralRepository->getDirectMembers($userId);
foreach ($directMembers as $member) {
$memberId = $member->user_id;
// 添加到结果集
$allMembers[] = [
'user_id' => $memberId,
'level' => $level == 0 ? 1 : 2, // 1=直推, 2=间推
'created_at' => $member->created_at
];
// 递归获取下级的下级
$this->calculateAllPromotionMembers($memberId, $allMembers, $level + 1, $maxLevel);
}
}
/**
* 清除推荐关系相关的缓存
*/
private function clearReferralCaches(int $userId, ?int $oldReferrerId, int $newReferrerId): void
{
// 清除用户自身的缓存
Redis::del("promotion:user:{$userId}:all_referrers");
// 清除旧推荐人相关的缓存
if ($oldReferrerId) {
Redis::del("promotion:user:{$oldReferrerId}:all_members");
// 获取旧推荐人的所有上级,清除他们的团队成员缓存
$oldUpperReferrers = $this->getAllReferrers($oldReferrerId);
foreach ($oldUpperReferrers as $referrer) {
Redis::del("promotion:user:{$referrer['user_id']}:all_members");
}
}
// 清除新推荐人相关的缓存
Redis::del("promotion:user:{$newReferrerId}:all_members");
// 获取新推荐人的所有上级,清除他们的团队成员缓存
$newUpperReferrers = $this->getAllReferrers($newReferrerId);
foreach ($newUpperReferrers as $referrer) {
Redis::del("promotion:user:{$referrer['user_id']}:all_members");
}
// 获取用户的所有下级,清除他们的上级缓存
$allMembers = $this->getAllPromotionMembers($userId);
foreach ($allMembers['members'] as $member) {
Redis::del("promotion:user:{$member['user_id']}:all_referrers");
}
}
用户注册时,系统验证推荐码并获取推荐人ID。验证过程包括:
验证成功后,系统将使用返回的推荐人ID建立推荐关系。
系统会详细记录每次邀请码的使用情况,包括:
邀请码使用记录可用于:
当用户成功邀请他人注册或被邀请用户达成特定条件(如升级、完成任务等)时,系统会发放邀请奖励。奖励发放流程包括:
系统支持多种奖励类型:
系统提供多种方式查询邀请奖励记录,支持按用户、被邀请用户、奖励类型、来源和状态进行筛选:
查询结果包含详细的奖励信息,如奖励类型、数量、来源、状态等,以及相关用户的基本信息(用户名、头像等)。
系统提供多种方式查询邀请码的使用记录,支持按邀请码、邀请码所有者、使用者和使用结果等条件进行筛选:
查询结果包含详细的邀请码使用信息,如邀请码、使用者、使用时间、使用结果等,以及相关用户的基本信息。
团队模块采用只存储直接推荐关系,通过缓存或实时计算处理间接关系的策略。这种设计有以下优点:
直接关系存储 + 缓存策略在存储空间和查询效率之间取得了良好的平衡:
| 存储方式 | 存储空间 | 查询效率 | 维护成本 | 修改灵活性 |
|---|---|---|---|---|
| 仅存储直推关系(无缓存) | 低 | 低(需递归查询) | 低 | 高 |
| 仅存储直推关系(有缓存) | 低 + 缓存空间 | 高(缓存命中)/ 中(缓存未命中) | 中 | 高 |
| 扁平化存储 | 高 | 高(一次查询) | 中 | 低 |
| 直推关系 + 缓存表 | 中 | 高(一次查询) | 中 | 高 |
对于团队模块,直接关系存储 + 缓存策略是最佳选择,因为:
为了提高查询效率,系统采用多层缓存策略:
缓存内容:
缓存键设计:
// 用户的所有上级
key: promotion:user:{userId}:all_referrers
value: [referrerId1, referrerId2, ...]
// 用户的所有下级
key: promotion:user:{userId}:all_members
value: [memberId1, memberId2, ...]
缓存更新策略:
缓存过期时间:
为了进一步提高查询效率,特别是在Redis缓存失效或需要复杂查询时,系统引入了用户关系缓存表:
用户关系缓存表是一个统一的缓存表,用于存储用户之间的所有推荐关系(包括上下级关系)。通过合理设计字段和索引,一张表可以同时满足查询用户上级和下级的需求,减少数据冗余,简化维护。
表结构设计:
CREATE TABLE `promotion_user_relation_cache` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`related_user_id` bigint(20) NOT NULL COMMENT '关联用户ID(上级)',
`level` tinyint(3) unsigned NOT NULL COMMENT '关系层级:1直接,2间接',
`path` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '关系路径,格式:1,2,3',
`depth` tinyint(3) unsigned NOT NULL COMMENT '层级深度,从1开始',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
UNIQUE KEY `idx_user_relation` (`user_id`,`related_user_id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_related_user_id` (`related_user_id`),
KEY `idx_level` (`level`),
KEY `idx_depth` (`depth`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户关系缓存表';
字段说明:
| 字段名 | 类型 | 说明 |
|---|---|---|
| id | bigint | 主键ID |
| user_id | bigint | 用户ID(下级) |
| related_user_id | bigint | 关联用户ID(上级) |
| level | tinyint | 关系层级:1直接,2间接 |
| path | varchar | 关系路径,格式:1,2,3 |
| depth | tinyint | 层级深度,从1开始 |
| created_at | timestamp | 创建时间 |
| updated_at | timestamp | 更新时间 |
主要功能:
示例数据:
id | user_id | related_user_id | level | path | depth
---|---------|----------------|-------|-----------|------
1 | 4 | 3 | 1 | 3 | 1
2 | 4 | 2 | 2 | 3,2 | 2
3 | 4 | 1 | 2 | 3,2,1 | 3
4 | 3 | 2 | 1 | 2 | 1
5 | 3 | 1 | 2 | 2,1 | 2
6 | 2 | 1 | 1 | 1 | 1
在上面的示例中:
查询示例:
查询用户4的所有上级:
SELECT * FROM promotion_user_relation_cache WHERE user_id = 4;
查询用户1的所有下级:
SELECT * FROM promotion_user_relation_cache WHERE related_user_id = 1;
查询用户2的直接下级:
SELECT u.* FROM users u
JOIN promotion_user_relation_cache r ON u.id = r.user_id
WHERE r.related_user_id = 2 AND r.level = 1;
查询用户1的第2代下级:
SELECT u.* FROM users u
JOIN promotion_user_relation_cache r ON u.id = r.user_id
WHERE r.related_user_id = 1 AND r.depth = 2;
缓存表优势:
应用场景:
初始化机制:
系统启动或升级时,可执行全量初始化,确保数据完整性
/**
* 初始化用户的关系缓存
*/
public function initializeUserRelationCache(int $userId): int
{
// 删除用户现有缓存
$this->relationCacheRepository->deleteByConditions([
'user_id' => $userId
]);
// 获取直接上级
$directReferrer = $this->referralRepository->getDirectReferrer($userId);
if (!$directReferrer) {
return 0;
}
$count = 0;
$referrerId = $directReferrer->referrer_id;
// 添加直接上级缓存
$this->relationCacheRepository->create([
'user_id' => $userId,
'related_user_id' => $referrerId,
'level' => 1,
'path' => (string)$referrerId,
'depth' => 1
]);
$count++;
// 获取上级的所有上级
$upperReferrers = $this->relationCacheRepository->findByConditions([
'user_id' => $referrerId
]);
// 添加间接上级缓存
foreach ($upperReferrers as $upperReferrer) {
$this->relationCacheRepository->create([
'user_id' => $userId,
'related_user_id' => $upperReferrer->related_user_id,
'level' => 2,
'path' => $referrerId . ',' . $upperReferrer->path,
'depth' => $upperReferrer->depth + 1
]);
$count++;
}
return $count;
}
更新机制:
推荐关系变化时,级联更新所有相关用户的缓存
/**
* 更新用户关系缓存
*/
public function updateUserRelationCache(int $userId, int $oldReferrerId, int $newReferrerId): void
{
DB::transaction(function () use ($userId, $oldReferrerId, $newReferrerId) {
// 1. 删除用户及其所有下级与旧上级及其所有上级的关系缓存
if ($oldReferrerId) {
// 获取用户的所有下级ID
$downlineIds = $this->getDownlineIds($userId);
$downlineIds[] = $userId; // 包括用户自身
// 获取旧上级的所有上级ID
$upperReferrerIds = $this->getUpperReferrerIds($oldReferrerId);
$upperReferrerIds[] = $oldReferrerId; // 包括直接上级
// 删除关系缓存
foreach ($downlineIds as $downlineId) {
foreach ($upperReferrerIds as $upperReferrerId) {
$this->relationCacheRepository->deleteByConditions([
'user_id' => $downlineId,
'related_user_id' => $upperReferrerId
]);
}
}
}
// 2. 为用户及其所有下级创建与新上级及其所有上级的关系缓存
if ($newReferrerId) {
// 初始化用户的关系缓存
$this->initializeUserRelationCache($userId);
// 获取用户的所有下级
$downlines = $this->getDownlines($userId);
// 更新所有下级的关系缓存
foreach ($downlines as $downline) {
$this->initializeUserRelationCache($downline['user_id']);
}
}
});
}
校验与修复:
发现不一致时自动修复,确保数据准确性
/**
* 验证用户关系缓存
*/
private function validateUserRelationCache(int $userId): bool
{
// 获取直接上级
$directReferrer = $this->referralRepository->getDirectReferrer($userId);
if (!$directReferrer) {
// 用户没有上级,缓存应该为空
$cacheCount = $this->relationCacheRepository->countByConditions([
'user_id' => $userId
]);
return $cacheCount === 0;
}
// 获取缓存的直接上级
$cachedDirectReferrer = $this->relationCacheRepository->findByConditions([
'user_id' => $userId,
'level' => 1
])->first();
if (!$cachedDirectReferrer || $cachedDirectReferrer->related_user_id != $directReferrer->referrer_id) {
return false;
}
// 验证间接上级
// 递归计算实际的间接上级
$actualIndirectReferrers = $this->calculateIndirectReferrers($directReferrer->referrer_id);
// 获取缓存的间接上级
$cachedIndirectReferrers = $this->relationCacheRepository->findByConditions([
'user_id' => $userId,
'level' => 2
]);
// 比较间接上级数量
if (count($actualIndirectReferrers) != count($cachedIndirectReferrers)) {
return false;
}
// 比较间接上级ID
$actualReferrerIds = collect($actualIndirectReferrers)->pluck('referrer_id')->toArray();
$cachedReferrerIds = $cachedIndirectReferrers->pluck('related_user_id')->toArray();
sort($actualReferrerIds);
sort($cachedReferrerIds);
return $actualReferrerIds == $cachedReferrerIds;
}
性能优化:
查询用户的所有上级:
/**
* 获取用户的所有上级
*/
public function getAllReferrers(int $userId, int $level = 0, int $maxDepth = 20): array
{
$conditions = ['user_id' => $userId];
if ($level > 0) {
$conditions['level'] = $level;
}
if ($maxDepth > 0) {
$conditions['depth'] = ['<=', $maxDepth];
}
$relations = $this->relationCacheRepository->findByConditions($conditions);
$referrers = [];
foreach ($relations as $relation) {
$referrers[] = [
'user_id' => $relation->related_user_id,
'level' => $relation->level,
'depth' => $relation->depth,
'path' => $relation->path
];
}
return $referrers;
}
查询用户的所有团队成员:
/**
* 获取用户的所有团队成员
*/
public function getAllPromotionMembers(int $userId, int $level = 0, int $maxDepth = 20, int $page = 1, int $pageSize = 20): array
{
$conditions = ['related_user_id' => $userId];
if ($level > 0) {
$conditions['level'] = $level;
}
if ($maxDepth > 0) {
$conditions['depth'] = ['<=', $maxDepth];
}
// 获取总数
$total = $this->relationCacheRepository->countByConditions($conditions);
// 分页查询
$offset = ($page - 1) * $pageSize;
$relations = $this->relationCacheRepository->findByConditionsPaginated($conditions, $offset, $pageSize);
// 获取成员详细信息
$members = [];
foreach ($relations as $relation) {
$userInfo = $this->userRepository->find($relation->user_id);
if ($userInfo) {
$members[] = [
'user_id' => $userInfo->id,
'username' => $userInfo->username,
'avatar' => $userInfo->avatar,
'level' => $relation->level,
'depth' => $relation->depth,
'path' => $relation->path,
'created_at' => $userInfo->created_at
];
}
}
return [
'total' => $total,
'page' => $page,
'page_size' => $pageSize,
'total_pages' => ceil($total / $pageSize),
'members' => $members
];
}
查询特定代数的团队成员:
/**
* 获取用户特定代数的团队成员
*/
public function getPromotionMembersByDepth(int $userId, int $depth): array
{
$relations = $this->relationCacheRepository->findByConditions([
'related_user_id' => $userId,
'depth' => $depth
]);
$members = [];
foreach ($relations as $relation) {
$userInfo = $this->userRepository->find($relation->user_id);
if ($userInfo) {
$members[] = [
'user_id' => $userInfo->id,
'username' => $userInfo->username,
'level' => $relation->level,
'depth' => $relation->depth,
'path' => $relation->path
];
}
}
return $members;
}
统计团队规模:
/**
* 统计用户的团队规模
*/
public function countPromotionMembers(int $userId, int $level = 0): int
{
$conditions = ['related_user_id' => $userId];
if ($level > 0) {
$conditions['level'] = $level;
}
return $this->relationCacheRepository->countByConditions($conditions);
}
新用户注册并使用推荐码时,系统按以下流程建立推荐关系:
这种方式简化了关系建立流程,同时通过缓存机制保持了查询效率。
推荐码生成采用随机字符串算法,确保唯一性和安全性:
推荐码的使用流程如下:
除了纯文本推荐码外,系统还支持推荐链接和二维码:
https://example.com/register?code=ABCDEF用户可以分享推荐链接或二维码,新用户通过链接注册时自动填入推荐码。
在只存储直接推荐关系的设计下,系统可以支持修改用户的推荐关系(上级):
/**
* 更新用户的推荐关系
*/
public function updateReferralRelation(int $userId, int $newReferrerId): bool
{
return DB::transaction(function () use ($userId, $newReferrerId) {
// 验证用户和新推荐人是否存在
if (!$this->userRepository->exists($userId) || !$this->userRepository->exists($newReferrerId)) {
throw new UserNotFoundException("用户或新推荐人不存在");
}
// 验证是否形成循环推荐
if ($this->checkCircularReferral($userId, $newReferrerId)) {
throw new ReferralException("不能形成循环推荐关系");
}
// 获取旧的直接推荐人
$oldReferrer = $this->referralRepository->getDirectReferrer($userId);
$oldReferrerId = $oldReferrer ? $oldReferrer->referrer_id : null;
// 如果新旧推荐人相同,无需更新
if ($oldReferrerId == $newReferrerId) {
return true;
}
// 更新直接推荐关系
if ($oldReferrerId) {
$this->referralRepository->deleteDirectReferral($userId);
}
$this->referralRepository->createDirectReferral($userId, $newReferrerId);
// 清除相关缓存
$this->clearReferralCaches($userId, $oldReferrerId, $newReferrerId);
// 更新旧推荐人的直推人数和团队人数
if ($oldReferrerId) {
$this->updatePromotionCounts($oldReferrerId);
$this->talentService->checkAndUpdateTalentLevel($oldReferrerId);
}
// 更新新推荐人的直推人数和团队人数
$this->updatePromotionCounts($newReferrerId);
$this->talentService->checkAndUpdateTalentLevel($newReferrerId);
// 记录修改历史
$this->referralChangeRepository->create([
'user_id' => $userId,
'old_referrer_id' => $oldReferrerId,
'new_referrer_id' => $newReferrerId,
'change_time' => now(),
'change_reason' => request('reason', ''),
'changed_by' => Auth::id() ?? 0
]);
// 触发推荐关系更新事件
event(new ReferralUpdatedEvent($userId, $oldReferrerId, $newReferrerId));
return true;
});
}
为了记录推荐关系的修改历史,系统使用以下表结构:
CREATE TABLE `promotion_referral_changes` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`old_referrer_id` bigint(20) DEFAULT NULL COMMENT '旧推荐人ID',
`new_referrer_id` bigint(20) NOT NULL COMMENT '新推荐人ID',
`change_time` timestamp NOT NULL COMMENT '修改时间',
`change_reason` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '修改原因',
`changed_by` bigint(20) NOT NULL COMMENT '操作人ID',
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`id`),
KEY `idx_user_id` (`user_id`),
KEY `idx_change_time` (`change_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='推荐关系修改记录表';
为了防止频繁修改推荐关系导致系统不稳定,系统实施以下限制策略:
/**
* 验证用户是否可以修改推荐关系
*/
private function validateReferralChangeEligibility(int $userId): void
{
// 检查修改次数限制
$changeCount = $this->referralChangeRepository->countRecentChanges($userId, 180); // 180天内
if ($changeCount >= 1) {
throw new ReferralException("180天内只能修改一次推荐关系");
}
// 检查注册时间限制
$user = $this->userRepository->find($userId);
if ($user->created_at->diffInDays(now()) < 30) {
throw new ReferralException("注册30天内不能修改推荐关系");
}
// 检查团队规模限制
$promotionCount = $this->calculatePromotionCount($userId);
if ($promotionCount > 100) {
throw new ReferralException("团队规模超过100人不能修改推荐关系");
}
}
循环推荐是指用户A推荐用户B,用户B又推荐用户A的情况,或者形成更长的循环链。这种情况会导致:
系统通过以下机制检测和防止循环推荐:
/**
* 检查是否形成循环推荐
*/
private function checkCircularReferral(int $userId, int $referrerId): bool
{
// 如果用户ID和推荐人ID相同,直接形成循环
if ($userId == $referrerId) {
return true;
}
// 获取用户的所有下级
$allMembers = $this->getAllPromotionMembers($userId);
// 检查新推荐人是否在用户的下级中
foreach ($allMembers['members'] as $member) {
if ($member['user_id'] == $referrerId) {
return true;
}
}
return false;
}
此外,系统还实施以下规则:
系统实时统计用户的直推人数:
/**
* 统计用户的直推人数
*/
public function countDirectReferrals(int $userId): int
{
return $this->referralRepository->countByConditions([
'referrer_id' => $userId,
'level' => 1
]);
}
系统实时统计用户的团队总人数(包括直推和间推):
/**
* 统计用户的团队总人数
*/
public function countPromotionMembers(int $userId): int
{
return $this->referralRepository->countByConditions([
'referrer_id' => $userId
]);
}
系统支持统计团队的活跃度,用于评估团队质量:
/**
* 统计用户团队的活跃度
*/
public function calculatePromotionActivity(int $userId, int $days = 7): array
{
// 获取团队成员ID列表
$memberIds = $this->referralRepository->getPromotionMemberIds($userId);
// 统计活跃用户数
$activeCount = $this->userActivityRepository->countActiveUsers($memberIds, $days);
// 计算活跃率
$totalCount = count($memberIds);
$activeRate = $totalCount > 0 ? $activeCount / $totalCount : 0;
return [
'total_members' => $totalCount,
'active_members' => $activeCount,
'active_rate' => $activeRate,
'days' => $days
];
}
系统支持生成团队的树形结构图,直观展示推荐关系:
/**
* 生成用户的团队树形图
*/
public function generatePromotionTree(int $userId, int $maxDepth = 3): array
{
// 获取用户信息
$user = $this->userRepository->find($userId);
if (!$user) {
return [];
}
// 构建根节点
$root = [
'id' => $user->id,
'name' => $user->username,
'avatar' => $user->avatar,
'children' => []
];
// 递归构建团队树
$this->buildPromotionTreeNode($root, 1, $maxDepth);
return $root;
}
/**
* 递归构建团队树节点
*/
private function buildPromotionTreeNode(array &$node, int $currentDepth, int $maxDepth): void
{
// 达到最大深度,停止递归
if ($currentDepth >= $maxDepth) {
return;
}
// 获取直推成员
$directMembers = $this->referralRepository->findByConditions([
'referrer_id' => $node['id'],
'level' => 1
]);
// 构建子节点
foreach ($directMembers as $member) {
$user = $this->userRepository->find($member->user_id);
if ($user) {
$childNode = [
'id' => $user->id,
'name' => $user->username,
'avatar' => $user->avatar,
'children' => []
];
// 递归构建子节点
$this->buildPromotionTreeNode($childNode, $currentDepth + 1, $maxDepth);
// 添加到父节点
$node['children'][] = $childNode;
}
}
}
系统支持生成团队的关系图,展示成员之间的连接:
/**
* 生成用户的团队关系图
*/
public function generatePromotionGraph(int $userId, int $maxMembers = 50): array
{
// 获取团队成员
$members = $this->getPromotionMembers($userId, 0, 1, $maxMembers)['members'];
// 构建节点
$nodes = [];
$nodes[] = [
'id' => $userId,
'name' => $this->userRepository->find($userId)->username,
'type' => 'root'
];
foreach ($members as $member) {
$nodes[] = [
'id' => $member['user_id'],
'name' => $member['username'],
'type' => $member['level'] == 1 ? 'direct' : 'indirect'
];
}
// 构建边
$edges = [];
foreach ($members as $member) {
$edges[] = [
'source' => $member['level'] == 1 ? $userId : $this->getDirectReferrerId($member['user_id']),
'target' => $member['user_id'],
'type' => $member['level'] == 1 ? 'direct' : 'indirect'
];
}
return [
'nodes' => $nodes,
'edges' => $edges
];
}
推荐关系系统与用户系统紧密集成:
推荐关系系统为达人等级系统提供基础数据:
推荐关系系统为收益分成系统提供关系数据:
针对推荐关系的高频查询,系统采用以下优化措施:
user_id、referrer_id和level字段建立合适的索引系统采用多级缓存策略提高性能:
对于大规模数据操作,系统采用批量处理方式:
防止恶意用户刷取推荐关系:
严格验证输入数据,确保数据安全:
实施严格的权限控制,保护用户数据:
推荐关系系统是团队模块的核心组成部分,通过建立用户间的直推和间推关系,形成多层级的团队结构,为收益分成和达人等级提供基础数据支持。系统采用只存储直接推荐关系 + 缓存策略的设计,在保持较高查询效率的同时,大幅减少了存储空间,并提高了推荐关系修改的灵活性。
系统实现了推荐码生成、推荐关系修改、防循环推荐、团队统计和可视化等功能,通过缓存机制优化了查询性能,同时保持了数据的一致性。通过与用户系统、达人等级系统和收益分成系统的紧密集成,推荐关系系统为团队模块的其他功能提供了坚实的基础。
这种设计方案特别适合需要支持推荐关系修改的场景,用户可以在特定条件下更换上级,系统会自动处理相关的缓存更新和团队统计数据更新,确保数据的一致性和完整性。