| | |
| | | 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.comm.Result; |
| | | 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.dto.SubordinateInformationDto; |
| | | import com.ruoyi.system.domain.out.HomePageStatisticsOut; |
| | | import com.ruoyi.system.domain.out.UserTeamAndPositionOut; |
| | | import com.ruoyi.system.service.UserAccountService; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.util.CollectionUtils; |
| | | 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.ArrayList; |
| | | import java.util.List; |
| | | import java.util.*; |
| | | import java.util.concurrent.CompletableFuture; |
| | | import java.util.concurrent.ConcurrentHashMap; |
| | | import java.util.concurrent.TimeUnit; |
| | | import java.util.stream.Collectors; |
| | | |
| | | /** |
| | | * @program: ruoyiim |
| | | * @description:首页统计 |
| | | * @create: 2025-10-13 17:21 |
| | | **/ |
| | | @RestController |
| | | @RequestMapping("/system/home") |
| | | public class HomePageStatisticsController { |
| | |
| | | @Autowired |
| | | UserPolicyService userPolicyService; |
| | | |
| | | /** |
| | | * 首页统计 |
| | | */ |
| | | // 使用内存缓存替代Redis |
| | | private final Map<String, CacheEntry<List<String>>> subordinateCache = new ConcurrentHashMap<>(); |
| | | private static final long CACHE_EXPIRE_MINUTES = 5; // 缓存5分钟 |
| | | |
| | | // 缓存条目类,包含数据和过期时间 |
| | | private static class CacheEntry<T> { |
| | | 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() { |
| | | public AjaxResult getUserTeamAndPosition(@RequestParam(value = "invitationCode", required = false) String invitationCode) { |
| | | HomePageStatisticsOut statisticsOut = new HomePageStatisticsOut(); |
| | | |
| | | // 今日注册 |
| | | LambdaQueryWrapper<UserAccount> todayWrapper = new LambdaQueryWrapper<>(); |
| | | todayWrapper.apply("DATE(create_time) = CURDATE()"); |
| | | statisticsOut.setTodayRegister(userAccountService.count(todayWrapper)); |
| | | // 标志是否统计所有用户 |
| | | boolean isStatisticsAll = StringUtils.isEmpty(invitationCode); |
| | | |
| | | // 总注册 |
| | | statisticsOut.setTotalRegister(userAccountService.count()); |
| | | // 所有需要统计的下级账号列表 |
| | | List<String> allSubordinateAccounts = new ArrayList<>(); |
| | | |
| | | // 今日激活 |
| | | LambdaQueryWrapper<UserPolicy> userPolicyTodayWrapper = new LambdaQueryWrapper<>(); |
| | | userPolicyTodayWrapper.eq(UserPolicy::getApprovalStatus, 1) |
| | | .apply("DATE(created_at) = CURDATE()"); |
| | | statisticsOut.setTodayActivate(userPolicyService.count(userPolicyTodayWrapper)); |
| | | if (!isStatisticsAll) { |
| | | // 如果指定了邀请码,只统计该邀请码对应的用户及其下级 |
| | | UserAccount userAccount = userAccountService.getOne(new LambdaQueryWrapper<UserAccount>() |
| | | .eq(UserAccount::getInvitationCode, invitationCode) |
| | | .select(UserAccount::getAccount) // 只查询需要的字段 |
| | | ); |
| | | if (ObjectUtil.isNotEmpty(userAccount)) { |
| | | String rootAccount = userAccount.getAccount(); |
| | | // 使用内存缓存获取所有下级账号 |
| | | allSubordinateAccounts = getCachedSubordinateAccounts(rootAccount); |
| | | } |
| | | } |
| | | // 如果 invitationCode 为 null,isStatisticsAll 为 true,将不设置 allSubordinateAccounts |
| | | // 在后续查询中,如果 allSubordinateAccounts 为空且 isStatisticsAll 为 true,则查询所有用户 |
| | | |
| | | // 总激活 |
| | | LambdaQueryWrapper<UserPolicy> userPolicyTotalWrapper = new LambdaQueryWrapper<>(); |
| | | userPolicyTotalWrapper.eq(UserPolicy::getApprovalStatus, 1); |
| | | statisticsOut.setTotalActivate(userPolicyService.count(userPolicyTotalWrapper)); |
| | | // 并行执行统计查询 |
| | | try { |
| | | List<String> finalAllSubordinateAccounts = allSubordinateAccounts; |
| | | CompletableFuture<Long> todayRegisterFuture = CompletableFuture.supplyAsync(() -> |
| | | getTodayRegister(finalAllSubordinateAccounts, isStatisticsAll)); |
| | | List<String> finalAllSubordinateAccounts1 = allSubordinateAccounts; |
| | | CompletableFuture<Long> totalRegisterFuture = CompletableFuture.supplyAsync(() -> |
| | | getTotalRegister(finalAllSubordinateAccounts1, isStatisticsAll)); |
| | | List<String> finalAllSubordinateAccounts2 = allSubordinateAccounts; |
| | | CompletableFuture<Long> todayActivateFuture = CompletableFuture.supplyAsync(() -> |
| | | getTodayActivate(finalAllSubordinateAccounts2, isStatisticsAll)); |
| | | List<String> finalAllSubordinateAccounts3 = allSubordinateAccounts; |
| | | CompletableFuture<Long> totalActivateFuture = CompletableFuture.supplyAsync(() -> |
| | | getTotalActivate(finalAllSubordinateAccounts3, isStatisticsAll)); |
| | | |
| | | // 等待所有查询完成 |
| | | 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<String> subordinateAccounts, boolean isStatisticsAll) { |
| | | LambdaQueryWrapper<UserAccount> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.apply("DATE(create_time) = CURDATE()"); |
| | | |
| | | // 如果指定了邀请码,按账号列表过滤;否则查询所有 |
| | | if (!isStatisticsAll && !subordinateAccounts.isEmpty()) { |
| | | wrapper.in(UserAccount::getAccount, subordinateAccounts); |
| | | } |
| | | // 如果 isStatisticsAll 为 true,不添加账号过滤条件,查询所有用户 |
| | | |
| | | return userAccountService.count(wrapper); |
| | | } |
| | | |
| | | private Long getTotalRegister(List<String> subordinateAccounts, boolean isStatisticsAll) { |
| | | LambdaQueryWrapper<UserAccount> wrapper = new LambdaQueryWrapper<>(); |
| | | |
| | | // 如果指定了邀请码,按账号列表过滤;否则查询所有 |
| | | if (!isStatisticsAll && !subordinateAccounts.isEmpty()) { |
| | | wrapper.in(UserAccount::getAccount, subordinateAccounts); |
| | | } |
| | | // 如果 isStatisticsAll 为 true,不添加账号过滤条件,查询所有用户 |
| | | |
| | | return userAccountService.count(wrapper); |
| | | } |
| | | |
| | | private Long getTodayActivate(List<String> subordinateAccounts, boolean isStatisticsAll) { |
| | | LambdaQueryWrapper<UserPolicy> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.eq(UserPolicy::getApprovalStatus, 1) |
| | | .apply("DATE(updated_at) = CURDATE()"); |
| | | |
| | | // 如果指定了邀请码,按账号列表过滤;否则查询所有 |
| | | if (!isStatisticsAll && !subordinateAccounts.isEmpty()) { |
| | | wrapper.in(UserPolicy::getAccount, subordinateAccounts); |
| | | } |
| | | // 如果 isStatisticsAll 为 true,不添加账号过滤条件,查询所有用户 |
| | | |
| | | return userPolicyService.count(wrapper); |
| | | } |
| | | |
| | | private Long getTotalActivate(List<String> subordinateAccounts, boolean isStatisticsAll) { |
| | | LambdaQueryWrapper<UserPolicy> wrapper = new LambdaQueryWrapper<>(); |
| | | wrapper.eq(UserPolicy::getApprovalStatus, 1); |
| | | |
| | | // 如果指定了邀请码,按账号列表过滤;否则查询所有 |
| | | if (!isStatisticsAll && !subordinateAccounts.isEmpty()) { |
| | | wrapper.in(UserPolicy::getAccount, subordinateAccounts); |
| | | } |
| | | // 如果 isStatisticsAll 为 true,不添加账号过滤条件,查询所有用户 |
| | | |
| | | return userPolicyService.count(wrapper); |
| | | } |
| | | |
| | | // 使用内存缓存的下级账号查询 |
| | | private List<String> getCachedSubordinateAccounts(String rootAccount) { |
| | | String cacheKey = "subordinate:" + rootAccount; |
| | | |
| | | // 尝试从缓存获取 |
| | | CacheEntry<List<String>> cacheEntry = subordinateCache.get(cacheKey); |
| | | if (cacheEntry != null && !cacheEntry.isExpired()) { |
| | | return new ArrayList<>(cacheEntry.getData()); // 返回副本 |
| | | } |
| | | |
| | | // 缓存不存在或已过期,查询数据库 |
| | | List<String> 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<String> getAllSubordinateAccountsOptimized(String rootAccount) { |
| | | if (StringUtils.isEmpty(rootAccount)) { |
| | | return new ArrayList<>(); |
| | | } |
| | | |
| | | List<String> result = new ArrayList<>(); |
| | | Queue<String> queue = new LinkedList<>(); |
| | | queue.offer(rootAccount); |
| | | |
| | | // 一次性查询所有用户关系,减少数据库访问 |
| | | List<UserAccount> allUsers = userAccountService.list( |
| | | new LambdaQueryWrapper<UserAccount>() |
| | | .select(UserAccount::getAccount, UserAccount::getInvitationAccount) |
| | | ); |
| | | |
| | | // 构建邀请关系映射 |
| | | Map<String, List<String>> 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<String> subordinates = invitationMap.get(currentAccount); |
| | | |
| | | if (subordinates != null) { |
| | | for (String subordinate : subordinates) { |
| | | result.add(subordinate); |
| | | queue.offer(subordinate); |
| | | } |
| | | } |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | } |