Files
1818web-hoduan/PromotionLevelManager.java
2025-11-14 17:41:15 +08:00

319 lines
12 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.dora.manager;
import com.dora.entity.RevenueConfig;
import com.dora.entity.User;
import com.dora.event.PromotionLevelChangedEvent;
import com.dora.mapper.RevenueConfigMapper;
import com.dora.mapper.UserMapper;
import lombok.Builder;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import lombok.Singular;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
/**
* 推广等级统一管理器
* 解决多个地方更新推广等级的问题
*/
@Component
@RequiredArgsConstructor
@Slf4j
public class PromotionLevelManager {
private final UserMapper userMapper;
private final RevenueConfigMapper revenueConfigMapper;
private final ApplicationEventPublisher eventPublisher;
@Autowired
@Lazy
private PromotionLevelManager self;
// 用户级别的锁,防止并发更新同一用户
private final ConcurrentHashMap<Long, Lock> userLocks = new ConcurrentHashMap<>();
/**
* 推广等级计算策略枚举
*/
public enum CalculationStrategy {
PAID_FANS_ONLY("paid_fans", "仅付费粉丝数"),
TOTAL_FANS_ONLY("total_fans", "仅总粉丝数"),
WEIGHTED_AVERAGE("weighted", "加权平均");
@Getter
private final String code;
@Getter
private final String description;
CalculationStrategy(String code, String description) {
this.code = code;
this.description = description;
}
}
/**
* 推广等级更新结果
*/
@Getter
@Builder
public static class PromotionLevelUpdateResult {
private final boolean updated;
private final Integer oldLevel;
private final Integer newLevel;
private final String triggerSource;
private final LocalDateTime updateTime;
}
/**
* 统一的推广等级更新方法
*
* @param userId 用户ID
* @param strategy 计算策略
* @param triggerSource 触发源
* @return 更新结果
*/
@Transactional
public PromotionLevelUpdateResult updatePromotionLevel(
Long userId,
CalculationStrategy strategy,
String triggerSource) {
Lock userLock = userLocks.computeIfAbsent(userId, k -> new ReentrantLock());
userLock.lock();
try {
log.info("开始更新用户推广等级 - userId: {}, strategy: {}, source: {}",
userId, strategy.getCode(), triggerSource);
// 1. 获取用户当前信息
User user = userMapper.selectById(userId);
if (user == null) {
log.warn("用户不存在 - userId: {}", userId);
return createFailedResult(triggerSource);
}
Integer currentLevel = user.getPromotionLevel();
// 2. 根据策略计算新等级
int newLevel = calculateLevelByStrategy(user, strategy);
// 3. 检查等级是否发生变化
if (Objects.equals(currentLevel, newLevel)) {
log.debug("用户推广等级未发生变化 - userId: {}, level: {}", userId, currentLevel);
return createUnchangedResult(currentLevel, triggerSource);
}
// 4. 更新数据库
int updatedRows = userMapper.updatePromotionLevel(userId, newLevel);
if (updatedRows == 0) {
log.warn("更新用户推广等级失败,可能存在并发更新 - userId: {}", userId);
//可以选择返回失败或者重新尝试
return createFailedResult(triggerSource);
}
// 5. 记录审计日志
recordAuditLog(userId, currentLevel, newLevel, strategy, triggerSource);
// 6. 发布等级变化事件
publishLevelChangedEvent(userId, currentLevel, newLevel, triggerSource);
log.info("用户推广等级更新成功 - userId: {}, level: {} -> {}, strategy: {}, source: {}",
userId, currentLevel, newLevel, strategy.getCode(), triggerSource);
return createSuccessResult(currentLevel, newLevel, triggerSource);
} finally {
userLock.unlock();
// 当锁没有其他等待线程时从map中移除避免内存泄漏
userLocks.compute(userId, (k, v) -> (v != null && v.hasQueuedThreads()) ? v : null);
}
}
/**
* 根据策略计算推广等级
*/
private int calculateLevelByStrategy(User user, CalculationStrategy strategy) {
int fansCount;
Long userId = user.getId();
switch (strategy) {
case TOTAL_FANS_ONLY:
fansCount = userMapper.countFansByInviterId(userId);
break;
case WEIGHTED_AVERAGE:
// 加权策略:付费粉丝权重 * 2 + 总粉丝权重 * 1
// TODO: 可以优化为一次数据库查询返回两个字段
int paidFans = userMapper.countPaidFansByInviterId(userId);
int totalFans = userMapper.countFansByInviterId(userId);
fansCount = (paidFans * 2) + totalFans;
break;
case PAID_FANS_ONLY:
default:
fansCount = userMapper.countPaidFansByInviterId(userId);
}
return calculatePromotionLevel(fansCount);
}
/**
* 根据粉丝数计算推广等级
*/
private int calculatePromotionLevel(int fansCount) {
try {
List<RevenueConfig> configs = self.getPromotionRevenueConfigs();
return configs.stream()
.filter(config -> config.getMinFans() != null && fansCount >= config.getMinFans())
.mapToInt(RevenueConfig::getLevel)
.max()
.orElse(1);
} catch (Exception e) {
log.error("计算推广等级失败 - fansCount: {}", fansCount, e);
return 1; // 默认返回1级
}
}
/**
* 获取并缓存推广收益配置
*/
@Cacheable("revenueConfigs")
public List<RevenueConfig> getPromotionRevenueConfigs() {
return revenueConfigMapper.selectByConfigType("promotion");
}
/**
* 记录审计日志
*/
private void recordAuditLog(Long userId, Integer oldLevel, Integer newLevel,
CalculationStrategy strategy, String triggerSource) {
// 这里可以记录到专门的审计日志表
String strategyCode = (strategy != null) ? strategy.getCode() : "admin_override";
log.info("推广等级变化审计 - userId: {}, level: {} -> {}, strategy: {}, source: {}, time: {}",
userId, oldLevel, newLevel, strategyCode, triggerSource, LocalDateTime.now());
}
/**
* 发布等级变化事件
*/
private void publishLevelChangedEvent(Long userId, Integer oldLevel, Integer newLevel, String triggerSource) {
try {
PromotionLevelChangedEvent event = new PromotionLevelChangedEvent(
this, userId, oldLevel, newLevel, triggerSource);
eventPublisher.publishEvent(event);
} catch (Exception e) {
log.error("发布推广等级变化事件失败 - userId: {}", userId, e);
}
}
/**
* 批量更新推广等级(管理员操作)
*/
@Transactional
public BatchUpdateResult batchUpdatePromotionLevel(List<Long> userIds, Integer targetLevel, String adminId) {
log.info("管理员批量更新推广等级 - adminId: {}, userCount: {}, targetLevel: {}",
adminId, userIds.size(), targetLevel);
BatchUpdateResult.BatchUpdateResultBuilder resultBuilder = BatchUpdateResult.builder();
if (userIds == null || userIds.isEmpty()) {
return resultBuilder.build();
}
List<User> users = userMapper.selectBatchIds(userIds);
Map<Long, User> userMap = users.stream().collect(Collectors.toMap(User::getId, user -> user));
for (Long userId : userIds) {
User user = userMap.get(userId);
if (user == null) {
resultBuilder.failedUser(userId, "用户不存在");
continue;
}
Integer oldLevel = user.getPromotionLevel();
if (Objects.equals(oldLevel, targetLevel)) {
resultBuilder.skippedUser(userId, "等级未变化");
continue;
}
int updatedRows = userMapper.updatePromotionLevel(userId, targetLevel);
if (updatedRows > 0) {
resultBuilder.successUser(userId);
recordAuditLog(userId, oldLevel, targetLevel, null, "admin_batch_update:" + adminId);
publishLevelChangedEvent(userId, oldLevel, targetLevel, "admin_batch_update");
} else {
resultBuilder.failedUser(userId, "数据库更新失败");
}
}
BatchUpdateResult result = resultBuilder.build();
log.info("批量更新推广等级完成 - 成功: {}, 失败: {}, 跳过: {}",
result.getSuccessCount(), result.getFailedCount(), result.getSkippedCount());
return result;
}
// 辅助方法创建结果对象
private PromotionLevelUpdateResult createSuccessResult(Integer oldLevel, Integer newLevel, String triggerSource) {
return PromotionLevelUpdateResult.builder()
.updated(true)
.oldLevel(oldLevel)
.newLevel(newLevel)
.triggerSource(triggerSource)
.updateTime(LocalDateTime.now())
.build();
}
private PromotionLevelUpdateResult createUnchangedResult(Integer level, String triggerSource) {
return PromotionLevelUpdateResult.builder()
.updated(false)
.oldLevel(level)
.newLevel(level)
.triggerSource(triggerSource)
.updateTime(LocalDateTime.now())
.build();
}
private PromotionLevelUpdateResult createFailedResult(String triggerSource) {
return PromotionLevelUpdateResult.builder()
.updated(false)
.triggerSource(triggerSource)
.updateTime(LocalDateTime.now())
.build();
}
/**
* 批量更新结果
*/
@Getter
@Builder
public static class BatchUpdateResult {
@Singular
private final List<Long> successUsers;
@Singular
private final Map<Long, String> failedUsers;
@Singular
private final Map<Long, String> skippedUsers;
public int getSuccessCount() { return successUsers.size(); }
public int getFailedCount() { return failedUsers.size(); }
public int getSkippedCount() { return skippedUsers.size(); }
}
}