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 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 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 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 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 users = userMapper.selectBatchIds(userIds); Map 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 successUsers; @Singular private final Map failedUsers; @Singular private final Map skippedUsers; public int getSuccessCount() { return successUsers.size(); } public int getFailedCount() { return failedUsers.size(); } public int getSkippedCount() { return skippedUsers.size(); } } }