[Claude Workbench] Initial commit - preserving existing code

This commit is contained in:
Claude Workbench
2025-11-14 17:41:15 +08:00
commit 0f7bc05697
587 changed files with 103215 additions and 0 deletions

318
PromotionLevelManager.java Normal file
View File

@@ -0,0 +1,318 @@
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(); }
}
}