package com.ruoyi.web.controller.system; import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.utils.StringUtils; import com.ruoyi.im.service.UserPolicyService; import com.ruoyi.im.service.impl.InsurancePositionServiceImpl; import com.ruoyi.im.util.UserPolicyUtils; import com.ruoyi.system.domain.InsurancePosition; import com.ruoyi.system.domain.UserAccount; import com.ruoyi.system.domain.UserPolicy; import com.ruoyi.system.domain.out.HomePageStatisticsOut; import com.ruoyi.system.service.UserAccountService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @RestController @RequestMapping("/system/home") public class HomePageStatisticsController { @Autowired UserAccountService userAccountService; @Autowired InsurancePositionServiceImpl insurancePositionService; @Autowired UserPolicyService userPolicyService; // 使用内存缓存替代Redis private final Map>> subordinateCache = new ConcurrentHashMap<>(); private static final long CACHE_EXPIRE_MINUTES = 5; // 缓存5分钟 // 缓存条目类,包含数据和过期时间 private static class CacheEntry { private final T data; private final long expireTime; public CacheEntry(T data, long expireTime) { this.data = data; this.expireTime = expireTime; } public T getData() { return data; } public boolean isExpired() { return System.currentTimeMillis() > expireTime; } } @GetMapping("/getStatistics") public AjaxResult getUserTeamAndPosition(@RequestParam(value = "invitationCode", required = false) String invitationCode) { HomePageStatisticsOut statisticsOut = new HomePageStatisticsOut(); // 所有需要统计的下级账号列表 List allSubordinateAccounts = new ArrayList<>(); if (StringUtils.isNotEmpty(invitationCode)) { UserAccount userAccount = userAccountService.getOne(new LambdaQueryWrapper() .eq(UserAccount::getInvitationCode, invitationCode) .select(UserAccount::getAccount) // 只查询需要的字段 ); if (ObjectUtil.isNotEmpty(userAccount)) { String rootAccount = userAccount.getAccount(); // 使用内存缓存获取所有下级账号 allSubordinateAccounts = getCachedSubordinateAccounts(rootAccount); } } // 如果下级账号数量为0,直接返回空结果,避免不必要的查询 if (allSubordinateAccounts.isEmpty()) { return AjaxResult.success(statisticsOut); } // 并行执行统计查询 try { List finalAllSubordinateAccounts = allSubordinateAccounts; CompletableFuture todayRegisterFuture = CompletableFuture.supplyAsync(() -> getTodayRegister(finalAllSubordinateAccounts)); List finalAllSubordinateAccounts2 = allSubordinateAccounts; CompletableFuture totalRegisterFuture = CompletableFuture.supplyAsync(() -> getTotalRegister(finalAllSubordinateAccounts2)); List finalAllSubordinateAccounts1 = allSubordinateAccounts; CompletableFuture todayActivateFuture = CompletableFuture.supplyAsync(() -> getTodayActivate(finalAllSubordinateAccounts1)); List finalAllSubordinateAccounts3 = allSubordinateAccounts; CompletableFuture totalActivateFuture = CompletableFuture.supplyAsync(() -> getTotalActivate(finalAllSubordinateAccounts3)); // 等待所有查询完成 statisticsOut.setTodayRegister(todayRegisterFuture.get()); statisticsOut.setTotalRegister(totalRegisterFuture.get()); statisticsOut.setTodayActivate(todayActivateFuture.get()); statisticsOut.setTotalActivate(totalActivateFuture.get()); } catch (Exception e) { throw new RuntimeException("统计查询失败", e); } return AjaxResult.success(statisticsOut); } // 分离的查询方法,便于并行执行 private Long getTodayRegister(List subordinateAccounts) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.apply("DATE(create_time) = CURDATE()") .in(UserAccount::getAccount, subordinateAccounts); return userAccountService.count(wrapper); } private Long getTotalRegister(List subordinateAccounts) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.in(UserAccount::getAccount, subordinateAccounts); return userAccountService.count(wrapper); } private Long getTodayActivate(List subordinateAccounts) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(UserPolicy::getApprovalStatus, 1) .in(UserPolicy::getAccount, subordinateAccounts) .apply("DATE(created_at) = CURDATE()"); return userPolicyService.count(wrapper); } private Long getTotalActivate(List subordinateAccounts) { LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); wrapper.eq(UserPolicy::getApprovalStatus, 1) .in(UserPolicy::getAccount, subordinateAccounts); return userPolicyService.count(wrapper); } // 使用内存缓存的下级账号查询 private List getCachedSubordinateAccounts(String rootAccount) { String cacheKey = "subordinate:" + rootAccount; // 尝试从缓存获取 CacheEntry> cacheEntry = subordinateCache.get(cacheKey); if (cacheEntry != null && !cacheEntry.isExpired()) { return new ArrayList<>(cacheEntry.getData()); // 返回副本 } // 缓存不存在或已过期,查询数据库 List subordinateAccounts = getAllSubordinateAccountsOptimized(rootAccount); // 存入缓存 long expireTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(CACHE_EXPIRE_MINUTES); subordinateCache.put(cacheKey, new CacheEntry<>(new ArrayList<>(subordinateAccounts), expireTime)); // 定期清理过期缓存(简单实现) cleanExpiredCache(); return subordinateAccounts; } // 清理过期缓存 private void cleanExpiredCache() { // 每100次访问清理一次缓存,避免频繁清理 if (System.currentTimeMillis() % 100 == 0) { subordinateCache.entrySet().removeIf(entry -> entry.getValue().isExpired()); } } // 优化版本:使用Queue进行广度优先搜索,避免递归深度问题 public List getAllSubordinateAccountsOptimized(String rootAccount) { if (StringUtils.isEmpty(rootAccount)) { return new ArrayList<>(); } List result = new ArrayList<>(); Queue queue = new LinkedList<>(); queue.offer(rootAccount); // 一次性查询所有用户关系,减少数据库访问 List allUsers = userAccountService.list( new LambdaQueryWrapper() .select(UserAccount::getAccount, UserAccount::getInvitationAccount) ); // 构建邀请关系映射 Map> invitationMap = allUsers.stream() .filter(user -> StringUtils.isNotEmpty(user.getInvitationAccount())) .collect(Collectors.groupingBy( UserAccount::getInvitationAccount, Collectors.mapping(UserAccount::getAccount, Collectors.toList()) )); // 广度优先遍历 while (!queue.isEmpty()) { String currentAccount = queue.poll(); List subordinates = invitationMap.get(currentAccount); if (subordinates != null) { for (String subordinate : subordinates) { result.add(subordinate); queue.offer(subordinate); } } } return result; } }