TASK:教育版子账号积分刷新策略更新
This commit is contained in:
@@ -27,7 +27,7 @@ public class AccountTask {
|
|||||||
// @Scheduled(cron = "59 59 23 * * ?")
|
// @Scheduled(cron = "59 59 23 * * ?")
|
||||||
// @Scheduled(cron = "0 0 0 1 * ?")
|
// @Scheduled(cron = "0 0 0 1 * ?")
|
||||||
public void refreshCreditsMonthly() {
|
public void refreshCreditsMonthly() {
|
||||||
log.info("每月1号0点 将年费用户积分重置为 6000");
|
log.info("每月1号0点 重置教育版子账号为默认积分");
|
||||||
accountService.refreshCreditsMonthly();
|
accountService.refreshCreditsMonthly();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -528,7 +528,7 @@ public class RedisUtil {
|
|||||||
return JSON.parseObject(json, ProgressDTO.class);
|
return JSON.parseObject(json, ProgressDTO.class);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("任务进度解析失败 key={}, json={}", key, json);
|
log.warn("任务进度解析失败 key={}, json={}", key, json);
|
||||||
return new ProgressDTO(0, 0, false);
|
return new ProgressDTO(0, 0, false, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -660,7 +660,7 @@ public class RedisUtilEnhance {
|
|||||||
return JSON.parseObject(json, ProgressDTO.class);
|
return JSON.parseObject(json, ProgressDTO.class);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.warn("任务进度解析失败 key={}, json={}", key, json);
|
log.warn("任务进度解析失败 key={}, json={}", key, json);
|
||||||
return new ProgressDTO(0, 0, false);
|
return new ProgressDTO(0, 0, false, null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -385,5 +385,11 @@ public class AccountController {
|
|||||||
return Response.success("success");
|
return Response.success("success");
|
||||||
}*/
|
}*/
|
||||||
|
|
||||||
|
@GetMapping("/refreshCreditsMonthly")
|
||||||
|
@ApiOperation(value = "刷新子账号积分")
|
||||||
|
public void refreshCreditsMonthly() {
|
||||||
|
accountService.refreshCreditsMonthly();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import lombok.AllArgsConstructor;
|
|||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@AllArgsConstructor
|
@AllArgsConstructor
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@@ -11,5 +13,6 @@ public class ProgressDTO {
|
|||||||
private int total;
|
private int total;
|
||||||
private int current;
|
private int current;
|
||||||
private boolean error;
|
private boolean error;
|
||||||
|
private LocalDateTime computeTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import org.springframework.core.io.ClassPathResource;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
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<>();
|
* 每月刷新教育版子账号积分
|
||||||
// 刷新账号有效期截止之前的年付用户的积分
|
* 只刷新教育版管理员(role=7)的子账号(role=8)
|
||||||
long epochMilli = Instant.now().toEpochMilli();
|
*/
|
||||||
accountUpdateWrapper.lambda().set(Account::getCredits, CreditsEventsEnum.RESET_YEAR_CREDITS.getValue())
|
@Transactional(rollbackFor = Exception.class)
|
||||||
.eq(Account::getSystemUser,1)
|
public void refreshCreditsMonthly() {
|
||||||
// .or().eq(Account::getSystemUser,2)
|
long startTime = System.currentTimeMillis();
|
||||||
.gt(Account::getValidEndTime, epochMilli);
|
log.info("开始执行月度积分刷新任务");
|
||||||
baseMapper.update(null,accountUpdateWrapper);
|
|
||||||
|
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
|
@Override
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import org.springframework.scheduling.annotation.Async;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
import javax.annotation.Resource;
|
import javax.annotation.Resource;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@@ -57,7 +58,7 @@ public class ProductImageServiceImpl implements ProductImageService {
|
|||||||
System.out.println(">>> [asyncInitialize] 当前线程:" + Thread.currentThread().getName());
|
System.out.println(">>> [asyncInitialize] 当前线程:" + Thread.currentThread().getName());
|
||||||
|
|
||||||
String progressKey = String.valueOf(brandId);
|
String progressKey = String.valueOf(brandId);
|
||||||
ProgressDTO progressDTO = new ProgressDTO(0, 0, false);
|
ProgressDTO progressDTO = new ProgressDTO(0, 0, false, null);
|
||||||
redisUtil.setTaskProgressDTO(progressKey, progressDTO);
|
redisUtil.setTaskProgressDTO(progressKey, progressDTO);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -113,13 +114,14 @@ public class ProductImageServiceImpl implements ProductImageService {
|
|||||||
// 更新当前进度
|
// 更新当前进度
|
||||||
current++;
|
current++;
|
||||||
progressDTO.setCurrent(current);
|
progressDTO.setCurrent(current);
|
||||||
|
progressDTO.setComputeTime(LocalDateTime.now());
|
||||||
redisUtil.setTaskProgressDTO(progressKey, progressDTO);
|
redisUtil.setTaskProgressDTO(progressKey, progressDTO);
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e.getMessage());
|
log.error(e.getMessage());
|
||||||
// log.error("初始化失败", e);
|
// log.error("初始化失败", e);
|
||||||
redisUtil.setTaskProgressDTO(progressKey, new ProgressDTO(0, 0, true));
|
redisUtil.setTaskProgressDTO(progressKey, new ProgressDTO(0, 0, true, null));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user