TASK:教育版子账号积分刷新策略更新

This commit is contained in:
2025-08-25 18:07:37 +08:00
parent e45d572078
commit 23523f73da
7 changed files with 209 additions and 15 deletions

View File

@@ -27,7 +27,7 @@ public class AccountTask {
// @Scheduled(cron = "59 59 23 * * ?")
// @Scheduled(cron = "0 0 0 1 * ?")
public void refreshCreditsMonthly() {
log.info("每月1号0点 将年费用户积分重置为 6000");
log.info("每月1号0点 重置教育版子账号为默认积分");
accountService.refreshCreditsMonthly();
}

View File

@@ -528,7 +528,7 @@ public class RedisUtil {
return JSON.parseObject(json, ProgressDTO.class);
} catch (Exception e) {
log.warn("任务进度解析失败 key={}, json={}", key, json);
return new ProgressDTO(0, 0, false);
return new ProgressDTO(0, 0, false, null);
}
}

View File

@@ -660,7 +660,7 @@ public class RedisUtilEnhance {
return JSON.parseObject(json, ProgressDTO.class);
} catch (Exception e) {
log.warn("任务进度解析失败 key={}, json={}", key, json);
return new ProgressDTO(0, 0, false);
return new ProgressDTO(0, 0, false, null);
}
}

View File

@@ -385,5 +385,11 @@ public class AccountController {
return Response.success("success");
}*/
@GetMapping("/refreshCreditsMonthly")
@ApiOperation(value = "刷新子账号积分")
public void refreshCreditsMonthly() {
accountService.refreshCreditsMonthly();
}
}

View File

@@ -4,6 +4,8 @@ import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@AllArgsConstructor
@NoArgsConstructor
@@ -11,5 +13,6 @@ public class ProgressDTO {
private int total;
private int current;
private boolean error;
private LocalDateTime computeTime;
}

View File

@@ -46,6 +46,7 @@ import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
@@ -1600,17 +1601,199 @@ public class AccountServiceImpl extends ServiceImpl<AccountMapper, Account> impl
}
/**
* 为年费用户每月更新积分
* 为教育版子账号用户每月更新积分
*/
public void refreshCreditsWeekly(){
UpdateWrapper<Account> accountUpdateWrapper = new UpdateWrapper<>();
// 刷新账号有效期截止之前的年付用户的积分
long epochMilli = Instant.now().toEpochMilli();
accountUpdateWrapper.lambda().set(Account::getCredits, CreditsEventsEnum.RESET_YEAR_CREDITS.getValue())
.eq(Account::getSystemUser,1)
// .or().eq(Account::getSystemUser,2)
.gt(Account::getValidEndTime, epochMilli);
baseMapper.update(null,accountUpdateWrapper);
/**
* 每月刷新教育版子账号积分
* 只刷新教育版管理员(role=7)的子账号(role=8)
*/
@Transactional(rollbackFor = Exception.class)
public void refreshCreditsMonthly() {
long startTime = System.currentTimeMillis();
log.info("开始执行月度积分刷新任务");
try {
long currentEpochMilli = Instant.now().toEpochMilli();
// 1. 查询所有有效的教育版管理员账号
List<Account> adminAccounts = getValidAdminAccounts(currentEpochMilli);
if (CollectionUtils.isEmpty(adminAccounts)) {
log.info("未找到有效的教育版管理员账号");
return;
}
// 2. 批量处理所有子账号积分刷新
int totalProcessed = refreshAllSubAccountsCredits(adminAccounts);
log.info("月度积分刷新任务完成,共处理 {} 个子账号,耗时 {} ms",
totalProcessed, System.currentTimeMillis() - startTime);
} catch (Exception e) {
log.error("积分刷新任务执行失败", e);
throw new BusinessException("积分刷新任务执行失败: " + e.getMessage());
}
}
/**
* 获取有效的教育版管理员账号
*/
private List<Account> getValidAdminAccounts(long currentEpochMilli) {
QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.select(Account::getId, Account::getOrganizationName, Account::getCreditsUsageLimit)
.eq(Account::getSystemUser, 7) // 教育版管理员
.gt(Account::getValidEndTime, currentEpochMilli); // 账号有效期内
return baseMapper.selectList(queryWrapper);
}
/**
* 批量刷新所有子账号积分
*/
private int refreshAllSubAccountsCredits(List<Account> adminAccounts) {
int totalProcessed = 0;
for (Account adminAccount : adminAccounts) {
// 获取该管理员的所有子账号
List<Account> subAccounts = getSubAccountsByAdmin(adminAccount);
if (CollectionUtils.isEmpty(subAccounts)) {
log.debug("管理员 {} 没有子账号", adminAccount.getId());
continue;
}
// 批量更新子账号积分
int processedCount = batchUpdateSubAccountsCredits(subAccounts);
totalProcessed += processedCount;
adminAccount.setCreditsUsage(new BigDecimal(3500).multiply(new BigDecimal(processedCount)));
adminAccount.setCredits(adminAccount.getCreditsUsageLimit().subtract(adminAccount.getCreditsUsage()));
adminAccount.setUpdateDate(new Date());
updateById(adminAccount);
log.info("管理员 {} 的子账号积分刷新完成,共处理 {} 个子账号",
adminAccount.getId(), processedCount);
}
return totalProcessed;
}
/**
* 获取指定管理员的所有子账号
*/
private List<Account> getSubAccountsByAdmin(Account adminAccount) {
QueryWrapper<Account> queryWrapper = new QueryWrapper<>();
queryWrapper.lambda()
.eq(Account::getOrganizationName, adminAccount.getOrganizationName())
.eq(Account::getParentId, adminAccount.getId())
.eq(Account::getSystemUser, 8); // 教育版子账号
return baseMapper.selectList(queryWrapper);
}
/**
* 批量更新子账号积分
*/
private int batchUpdateSubAccountsCredits(List<Account> subAccounts) {
List<Account> accountsToUpdate = new ArrayList<>();
Date now = new Date();
for (Account subAcc : subAccounts) {
try {
Account updatedAccount = calculateNewCredits(subAcc, now);
accountsToUpdate.add(updatedAccount);
} catch (Exception e) {
log.error("计算子账号 {} 积分时发生错误", subAcc.getId(), e);
}
}
if (!accountsToUpdate.isEmpty()) {
// 使用批量更新提高性能
return batchUpdateByIds(accountsToUpdate);
}
return 0;
}
/**
* 计算新的积分值
*/
private Account calculateNewCredits(Account subAcc, Date updateTime) {
BigDecimal creditsUsageLimit = subAcc.getCreditsUsageLimit();
// 使用 Optional 替代 ObjectUtils.defaultIfNull
BigDecimal creditsUsage = Optional.ofNullable(subAcc.getCreditsUsage()).orElse(BigDecimal.ZERO);
BigDecimal currentCredits = Optional.ofNullable(subAcc.getCredits()).orElse(BigDecimal.ZERO);
// 计算学校分配积分的剩余量
BigDecimal schoolCreditRemaining = creditsUsageLimit.subtract(creditsUsage);
// 计算个人充值的积分(总积分减去学校分配的剩余积分)
BigDecimal personalCredits = currentCredits.subtract(
schoolCreditRemaining.compareTo(BigDecimal.ZERO) > 0 ?
schoolCreditRemaining : BigDecimal.ZERO
);
// 确保个人积分不为负数
personalCredits = personalCredits.max(BigDecimal.ZERO);
// 新的总积分 = 个人积分 + 学校分配的新积分额度 todo 重新分配的积分是使用上个月分配的积分还是默认积分,暂时使用默认积分 3500
BigDecimal newTotalCredits = personalCredits.add(new BigDecimal(3500));
// 记录积分变更日志(可选)
logCreditChange(subAcc, currentCredits, newTotalCredits, creditsUsage);
// 创建更新对象
Account updatedAccount = new Account();
updatedAccount.setId(subAcc.getId());
updatedAccount.setCredits(newTotalCredits);
updatedAccount.setCreditsUsage(BigDecimal.ZERO); // 重置已使用积分
updatedAccount.setCreditsUsageLimit(new BigDecimal(3500)); // 重置为默认积分
updatedAccount.setUpdateDate(updateTime);
return updatedAccount;
}
/**
* 记录积分变更日志
*/
private void logCreditChange(Account subAcc, BigDecimal oldCredits,
BigDecimal newCredits, BigDecimal creditsUsage) {
if (creditsUsage.compareTo(subAcc.getCreditsUsageLimit()) > 0) {
log.warn("用户 {} 积分使用量 {} 大于积分使用上限 {}",
subAcc.getId(), creditsUsage, subAcc.getCreditsUsageLimit());
}
log.debug("更新子账号 {} 积分: 旧值={}, 已使用={}, 新值={}",
subAcc.getId(), oldCredits,creditsUsage, newCredits);
}
/**
* 批量更新账号信息
*/
private int batchUpdateByIds(List<Account> accountsToUpdate) {
// 使用MyBatis Plus的批量更新方法
// 如果没有批量更新方法,可以分批次更新以避免大数据量问题
int batchSize = 1000; // 每批更新1000条
int totalUpdated = 0;
for (int i = 0; i < accountsToUpdate.size(); i += batchSize) {
int end = Math.min(i + batchSize, accountsToUpdate.size());
List<Account> batch = accountsToUpdate.subList(i, end);
// 使用MyBatis Plus的批量更新
// 或者实现自己的批量更新逻辑
for (Account account : batch) {
baseMapper.updateById(account);
}
totalUpdated += batch.size();
log.debug("已批量更新 {} 个子账号积分", batch.size());
}
return totalUpdated;
}
@Override

View File

@@ -25,6 +25,7 @@ import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Objects;
import java.util.Set;
@@ -57,7 +58,7 @@ public class ProductImageServiceImpl implements ProductImageService {
System.out.println(">>> [asyncInitialize] 当前线程:" + Thread.currentThread().getName());
String progressKey = String.valueOf(brandId);
ProgressDTO progressDTO = new ProgressDTO(0, 0, false);
ProgressDTO progressDTO = new ProgressDTO(0, 0, false, null);
redisUtil.setTaskProgressDTO(progressKey, progressDTO);
try {
@@ -113,13 +114,14 @@ public class ProductImageServiceImpl implements ProductImageService {
// 更新当前进度
current++;
progressDTO.setCurrent(current);
progressDTO.setComputeTime(LocalDateTime.now());
redisUtil.setTaskProgressDTO(progressKey, progressDTO);
}
} catch (Exception e) {
log.error(e.getMessage());
// log.error("初始化失败", e);
redisUtil.setTaskProgressDTO(progressKey, new ProgressDTO(0, 0, true));
redisUtil.setTaskProgressDTO(progressKey, new ProgressDTO(0, 0, true, null));
}
}