diff --git a/src/main/java/com/ai/da/common/task/AccountTask.java b/src/main/java/com/ai/da/common/task/AccountTask.java index 82c88e7c..d7369893 100644 --- a/src/main/java/com/ai/da/common/task/AccountTask.java +++ b/src/main/java/com/ai/da/common/task/AccountTask.java @@ -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(); } diff --git a/src/main/java/com/ai/da/common/utils/RedisUtil.java b/src/main/java/com/ai/da/common/utils/RedisUtil.java index 39d7e3ad..6d2a597a 100644 --- a/src/main/java/com/ai/da/common/utils/RedisUtil.java +++ b/src/main/java/com/ai/da/common/utils/RedisUtil.java @@ -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); } } diff --git a/src/main/java/com/ai/da/common/utils/RedisUtilEnhance.java b/src/main/java/com/ai/da/common/utils/RedisUtilEnhance.java index ed1fd938..d6341b38 100644 --- a/src/main/java/com/ai/da/common/utils/RedisUtilEnhance.java +++ b/src/main/java/com/ai/da/common/utils/RedisUtilEnhance.java @@ -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); } } diff --git a/src/main/java/com/ai/da/controller/AccountController.java b/src/main/java/com/ai/da/controller/AccountController.java index b6c434f8..fdb35151 100644 --- a/src/main/java/com/ai/da/controller/AccountController.java +++ b/src/main/java/com/ai/da/controller/AccountController.java @@ -385,5 +385,11 @@ public class AccountController { return Response.success("success"); }*/ + @GetMapping("/refreshCreditsMonthly") + @ApiOperation(value = "刷新子账号积分") + public void refreshCreditsMonthly() { + accountService.refreshCreditsMonthly(); + } + } diff --git a/src/main/java/com/ai/da/model/dto/ProgressDTO.java b/src/main/java/com/ai/da/model/dto/ProgressDTO.java index c61731f9..f6434d27 100644 --- a/src/main/java/com/ai/da/model/dto/ProgressDTO.java +++ b/src/main/java/com/ai/da/model/dto/ProgressDTO.java @@ -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; } diff --git a/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java b/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java index 49d32b27..72e2b4b9 100644 --- a/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/AccountServiceImpl.java @@ -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 impl } /** - * 为年费用户每月更新积分 + * 为教育版子账号用户每月更新积分 */ - public void refreshCreditsWeekly(){ - UpdateWrapper 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 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 getValidAdminAccounts(long currentEpochMilli) { + QueryWrapper 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 adminAccounts) { + int totalProcessed = 0; + + for (Account adminAccount : adminAccounts) { + // 获取该管理员的所有子账号 + List 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 getSubAccountsByAdmin(Account adminAccount) { + QueryWrapper 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 subAccounts) { + List 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 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 batch = accountsToUpdate.subList(i, end); + + // 使用MyBatis Plus的批量更新 + // 或者实现自己的批量更新逻辑 + for (Account account : batch) { + baseMapper.updateById(account); + } + + totalUpdated += batch.size(); + log.debug("已批量更新 {} 个子账号积分", batch.size()); + } + + return totalUpdated; } @Override diff --git a/src/main/java/com/ai/da/service/impl/ProductImageServiceImpl.java b/src/main/java/com/ai/da/service/impl/ProductImageServiceImpl.java index e3fca190..a5e7c54b 100644 --- a/src/main/java/com/ai/da/service/impl/ProductImageServiceImpl.java +++ b/src/main/java/com/ai/da/service/impl/ProductImageServiceImpl.java @@ -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)); } }