| | |
| | | import com.ruoyi.domain.ModuleSearchResult; |
| | | import com.ruoyi.service.ModuleSearchable; |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.logging.Log; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.springframework.scheduling.annotation.Async; |
| | | import org.springframework.stereotype.Service; |
| | |
| | | |
| | | private final Map<String, ModuleSearchable> moduleSearchMap; |
| | | |
| | | // 模块分割符 |
| | | private static final String MODULE_SEPARATOR = ","; |
| | | |
| | | // 全模块标识 |
| | | private static final String ALL_MODULES_FLAG = "all"; |
| | | |
| | | /** |
| | | * 自动收集所有实现ModuleSearchable接口的Bean |
| | |
| | | log.info("已注册搜索模块: {}", moduleSearchMap.keySet()); |
| | | } |
| | | |
| | | @Async // 声明此方法异步执行,将使用我们上面配置的Executor |
| | | public CompletableFuture<ModuleSearchResult> searchModuleAsync(String moduleCode, ModuleSearchable service, String companion, Date startTime, Date endTime) { |
| | | // 将原来在lambda表达式中的搜索逻辑移到这里 |
| | | @Async |
| | | public CompletableFuture<ModuleSearchResult> searchModuleAsync(String moduleCode, ModuleSearchable service, |
| | | String companion, Date startTime, Date endTime,String hasAttachment) { |
| | | long start = System.currentTimeMillis(); |
| | | try { |
| | | List<?> data = service.search(companion, startTime, endTime); |
| | | List<?> data = service.search(companion, startTime, endTime,hasAttachment); |
| | | long searchTime = System.currentTimeMillis() - start; |
| | | ModuleSearchResult result = ModuleSearchResult.success(moduleCode, service.getModuleName(), data, data.size(), searchTime); |
| | | ModuleSearchResult result = ModuleSearchResult.success( |
| | | moduleCode, service.getModuleName(), data, data.size(), searchTime |
| | | ); |
| | | return CompletableFuture.completedFuture(result); |
| | | } catch (Exception e) { |
| | | log.error("模块[{}]搜索失败: {}", moduleCode, e.getMessage()); |
| | |
| | | return CompletableFuture.completedFuture(result); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 通用的路由搜索请求(支持不同参数类型) |
| | | */ |
| | | /** |
| | | * 通用的路由搜索请求 |
| | | * 通用的路由搜索请求 - 统一数据结构版 |
| | | * 支持功能: |
| | | * 1. 单个模块: "module1" |
| | | * 2. 多个模块: "module1,module2,module3" (逗号分隔) |
| | | * 3. 全模块: "all" 或 null 或 空字符串 |
| | | * 4. 不选模块: 指定为特定值,如"none"(可选功能) |
| | | * |
| | | * 统一返回数据结构: |
| | | * { |
| | | * "success": true, |
| | | * "data": { |
| | | * "modules": { |
| | | * "module1": { "data": [...], "count": 10, "searchTime": 100, "success": true }, |
| | | * "module2": { "error": "错误信息", "success": false } |
| | | * }, |
| | | * "totalSelectedModules": 2, |
| | | * "successModules": 1, |
| | | * "errorModules": ["module2"], |
| | | * "totalRecords": 10, |
| | | * "searchTime": "2023-01-01T00:00:00.000+00:00", |
| | | * "message": "搜索完成: 成功1/2个模块,总计10条记录" |
| | | * } |
| | | * } |
| | | */ |
| | | public AjaxResult routeSearch(String moduleCode, Object... args) { |
| | | log.info("路由搜索: moduleCode={}, args={}", moduleCode, Arrays.toString(args)); |
| | | |
| | | // 全模块搜索:当moduleCode为空或特定标识时 |
| | | if (moduleCode == null || moduleCode.isEmpty() || "all".equalsIgnoreCase(moduleCode)) { |
| | | return searchAllModules(args); |
| | | // 验证参数数量 |
| | | if (args.length != 4) { |
| | | return buildErrorResponse("参数数量错误,需要4个参数,实际收到: " + args.length, null); |
| | | } |
| | | |
| | | // 单个模块搜索(原有逻辑) |
| | | ModuleSearchable searchService = moduleSearchMap.get(moduleCode); |
| | | if (searchService == null) { |
| | | String availableModules = String.join(", ", moduleSearchMap.keySet()); |
| | | return AjaxResult.error("不支持的搜索模块: " + moduleCode + "。可用模块: [" + availableModules + "]"); |
| | | // 解析模块代码 |
| | | List<String> targetModules = parseModuleCodes(moduleCode); |
| | | |
| | | if (targetModules.isEmpty()) { |
| | | // 不选模块的情况 |
| | | return handleNoModuleSelected(); |
| | | } |
| | | |
| | | try { |
| | | if (args.length == 3) { |
| | | return handleFourArgs(searchService, args); |
| | | } else { |
| | | return AjaxResult.error("不支持的参数数量,需要3个参数,实际收到: " + args.length); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("搜索执行失败: moduleCode={}", moduleCode, e); |
| | | return AjaxResult.error("搜索执行失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private AjaxResult handleFourArgs(ModuleSearchable searchService, Object[] args) { |
| | | String companion = null; |
| | | Date happenStartTime = null; |
| | | Date happenEndTime = null; |
| | | |
| | | // 处理companion参数 |
| | | if (args[0] instanceof String) { |
| | | companion = (String) args[0]; |
| | | } else if (args[0] != null) { |
| | | companion = args[0].toString(); |
| | | } |
| | | |
| | | // 处理时间参数 |
| | | if (args[1] instanceof Date) { |
| | | happenStartTime = (Date) args[1]; |
| | | } |
| | | |
| | | if (args[2] instanceof Date) { |
| | | happenEndTime = (Date) args[2]; |
| | | } |
| | | |
| | | // 判断搜索类型 |
| | | boolean hasTimeRange = happenStartTime != null && happenEndTime != null; |
| | | |
| | | List<?> result; |
| | | if (hasTimeRange) { |
| | | // 有时间范围:执行时间范围搜索 |
| | | log.info("执行时间范围搜索: companion={}, startTime={}, endTime={}", |
| | | companion, happenStartTime, happenEndTime); |
| | | result = searchService.search(companion, happenStartTime, happenEndTime); |
| | | } else { |
| | | // 无时间范围:只按companion搜索 |
| | | log.info("执行companion搜索: companion={}, 时间范围为空", companion); |
| | | result = searchService.search(companion, null, null); |
| | | } |
| | | |
| | | return AjaxResult.success("搜索成功", result); |
| | | // 统一处理:单个模块、多个模块都使用同样的处理逻辑 |
| | | return executeModulesSearch(targetModules, args); |
| | | } |
| | | |
| | | /** |
| | | * 全模块搜索:获取所有模块的数据并分类 |
| | | * 统一执行模块搜索 |
| | | * 无论是单个模块还是多个模块,都使用统一的数据结构返回 |
| | | */ |
| | | private AjaxResult searchAllModules(Object[] args) { |
| | | log.info("执行全模块搜索,参数数量: {}", args.length); |
| | | private AjaxResult executeModulesSearch(List<String> moduleCodes, Object... args) { |
| | | log.info("执行模块搜索: moduleCodes={}, 模块数量={}", moduleCodes, moduleCodes.size()); |
| | | |
| | | try { |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | int totalCount = 0; |
| | | int successCount = 0; |
| | | // 验证所有模块是否存在 |
| | | List<String> invalidModules = moduleCodes.stream() |
| | | .filter(code -> !moduleSearchMap.containsKey(code)) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 处理搜索参数 |
| | | String companion = extractCompanion(args); |
| | | Date happenStartTime = extractStartTime(args); |
| | | Date happenEndTime = extractEndTime(args); |
| | | if (!invalidModules.isEmpty()) { |
| | | String availableModules = String.join(", ", moduleSearchMap.keySet()); |
| | | String errorMsg = String.format("以下模块不支持: %s。可用模块: [%s]", |
| | | invalidModules, availableModules); |
| | | return buildErrorResponse(errorMsg, moduleCodes); |
| | | } |
| | | |
| | | System.out.println("全模块搜索 ------ 同伴: " + companion); |
| | | System.out.println("全模块搜索 ------ 开始时间: " + happenStartTime); |
| | | System.out.println("全模块搜索 ------ 结束时间: " + happenEndTime); |
| | | // 提取搜索参数 |
| | | String companion = extractCompanion(args); |
| | | Date happenStartTime = extractStartTime(args); |
| | | Date happenEndTime = extractEndTime(args); |
| | | String hasAttachment = extractHasAttachment(args); |
| | | |
| | | // 并行处理所有模块搜索(提高性能) |
| | | List<CompletableFuture<ModuleSearchResult>> futures = moduleSearchMap.entrySet().stream().map(entry -> |
| | | searchModuleAsync(entry.getKey(), |
| | | entry.getValue(), companion, |
| | | happenStartTime, happenEndTime)) |
| | | .collect(Collectors.toList()); |
| | | // 异步并行搜索 |
| | | List<CompletableFuture<ModuleSearchResult>> futures = moduleCodes.stream() |
| | | .map(code -> searchModuleAsync(code, moduleSearchMap.get(code), |
| | | companion, happenStartTime, happenEndTime,hasAttachment)) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 等待所有搜索完成 |
| | | CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); |
| | | |
| | | // 等待所有搜索完成 |
| | | CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); |
| | | // 收集结果 |
| | | Map<String, Object> modulesResult = new LinkedHashMap<>(); |
| | | int successCount = 0; |
| | | int totalCount = 0; |
| | | List<String> errorModules = new ArrayList<>(); |
| | | |
| | | // 收集结果 |
| | | for (CompletableFuture<ModuleSearchResult> future : futures) { |
| | | ModuleSearchResult moduleResult = future.get(); |
| | | for (int i = 0; i < futures.size(); i++) { |
| | | try { |
| | | ModuleSearchResult moduleResult = futures.get(i).get(); |
| | | String moduleCode = moduleCodes.get(i); |
| | | |
| | | if (moduleResult.isSuccess()) { |
| | | // 创建一个可变的HashMap并填入数据 |
| | | Map<String, Object> resultMap = new HashMap<>(); |
| | | resultMap.put("data", moduleResult.getData()); |
| | | resultMap.put("count", moduleResult.getCount()); |
| | | resultMap.put("searchTime", moduleResult.getSearchTime()); |
| | | Map<String, Object> moduleData = new HashMap<>(); |
| | | moduleData.put("data", moduleResult.getData()); |
| | | moduleData.put("count", moduleResult.getCount()); |
| | | moduleData.put("searchTime", moduleResult.getSearchTime()); |
| | | moduleData.put("success", true); |
| | | |
| | | // 将构建好的Map放入最终结果中 |
| | | result.put(moduleResult.getModuleCode(), resultMap); |
| | | modulesResult.put(moduleCode, moduleData); |
| | | |
| | | successCount++; |
| | | totalCount += moduleResult.getCount(); |
| | |
| | | Map<String, Object> errorInfo = new HashMap<>(); |
| | | errorInfo.put("error", moduleResult.getErrorMessage()); |
| | | errorInfo.put("success", false); |
| | | result.put(moduleResult.getModuleCode(), errorInfo); |
| | | errorInfo.put("moduleName", getModuleName(moduleCode)); |
| | | modulesResult.put(moduleCode, errorInfo); |
| | | errorModules.add(moduleCode); |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("获取模块[{}]搜索结果失败", moduleCodes.get(i), e); |
| | | String moduleCode = moduleCodes.get(i); |
| | | Map<String, Object> errorInfo = new HashMap<>(); |
| | | errorInfo.put("error", "获取结果异常: " + e.getMessage()); |
| | | errorInfo.put("success", false); |
| | | errorInfo.put("moduleName", getModuleName(moduleCode)); |
| | | modulesResult.put(moduleCode, errorInfo); |
| | | errorModules.add(moduleCode); |
| | | } |
| | | } |
| | | |
| | | // 构建返回结果 |
| | | Map<String, Object> finalResult = new LinkedHashMap<>(); |
| | | finalResult.put("totalModules", moduleSearchMap.size()); |
| | | finalResult.put("successModules", successCount); |
| | | finalResult.put("totalRecords", totalCount); |
| | | finalResult.put("searchTime", new Date()); |
| | | finalResult.put("modules", result); |
| | | // 构建最终返回结果 |
| | | Map<String, Object> finalResult = new LinkedHashMap<>(); |
| | | finalResult.put("modules", modulesResult); |
| | | finalResult.put("totalSelectedModules", moduleCodes.size()); |
| | | finalResult.put("successModules", successCount); |
| | | finalResult.put("errorModules", errorModules); |
| | | finalResult.put("totalRecords", totalCount); |
| | | finalResult.put("searchTime", new Date()); |
| | | |
| | | log.info("全模块搜索完成: 成功{}/{}个模块,总计{}条记录", |
| | | successCount, moduleSearchMap.size(), totalCount); |
| | | // 构建消息 |
| | | String message = buildSuccessMessage(moduleCodes.size(), successCount, errorModules, totalCount); |
| | | finalResult.put("message", message); |
| | | |
| | | return AjaxResult.success("全模块搜索成功", finalResult); |
| | | return AjaxResult.success(message, finalResult); |
| | | } |
| | | |
| | | } catch (Exception e) { |
| | | log.error("全模块搜索执行失败", e); |
| | | return AjaxResult.error("全模块搜索失败: " + e.getMessage()); |
| | | /** |
| | | * 获取模块名称 |
| | | */ |
| | | private String getModuleName(String moduleCode) { |
| | | ModuleSearchable service = moduleSearchMap.get(moduleCode); |
| | | return service != null ? service.getModuleName() : "未知模块"; |
| | | } |
| | | |
| | | /** |
| | | * 构建成功消息 |
| | | */ |
| | | private String buildSuccessMessage(int totalSelected, int successCount, |
| | | List<String> errorModules, int totalRecords) { |
| | | if (errorModules.isEmpty()) { |
| | | if (totalSelected == 1) { |
| | | return String.format("搜索成功: 找到%s条记录", totalRecords); |
| | | } else { |
| | | return String.format("搜索完成: 成功%s/%s个模块,总计%s条记录", |
| | | successCount, totalSelected, totalRecords); |
| | | } |
| | | } else { |
| | | return String.format("搜索完成: 成功%s/%s个模块,总计%s条记录,失败模块: %s", |
| | | successCount, totalSelected, totalRecords, errorModules); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 单个模块搜索包装方法 |
| | | * 构建错误响应 |
| | | */ |
| | | private ModuleSearchResult searchSingleModule(String moduleCode, ModuleSearchable service, |
| | | String companion, Date startTime, Date endTime) { |
| | | long start = System.currentTimeMillis(); |
| | | try { |
| | | List<?> data = service.search(companion, startTime, endTime); |
| | | long searchTime = System.currentTimeMillis() - start; |
| | | private AjaxResult buildErrorResponse(String errorMessage, List<String> moduleCodes) { |
| | | Map<String, Object> errorData = new HashMap<>(); |
| | | errorData.put("message", errorMessage); |
| | | errorData.put("success", false); |
| | | |
| | | return ModuleSearchResult.success(moduleCode, service.getModuleName(), |
| | | data, data.size(), searchTime); |
| | | |
| | | } catch (Exception e) { |
| | | log.error("模块[{}]搜索失败: {}", moduleCode, e.getMessage()); |
| | | return ModuleSearchResult.error(moduleCode, e.getMessage()); |
| | | if (moduleCodes != null) { |
| | | errorData.put("selectedModules", moduleCodes); |
| | | } |
| | | |
| | | errorData.put("totalSelectedModules", moduleCodes != null ? moduleCodes.size() : 0); |
| | | errorData.put("successModules", 0); |
| | | errorData.put("errorModules", moduleCodes != null ? moduleCodes : Collections.emptyList()); |
| | | errorData.put("totalRecords", 0); |
| | | errorData.put("searchTime", new Date()); |
| | | |
| | | return AjaxResult.error(errorMessage, errorData); |
| | | } |
| | | // |
| | | // /** |
| | | // * 获取所有可搜索的模块信息 |
| | | // */ |
| | | // public List<SysMenu> getAvailableModules() { |
| | | // return moduleSearchMap.values().stream() |
| | | // .map(service -> SysMenu.builder() |
| | | // .moduleCode(service.getModuleCode()) |
| | | // .moduleName(service.getModuleName()) |
| | | // .build()) |
| | | // .collect(Collectors.toList()); |
| | | // } |
| | | |
| | | /** |
| | | * 解析模块代码 |
| | | * 支持多种格式: |
| | | * 1. 空/空白/null -> 全模块搜索 |
| | | * 2. "all" -> 全模块搜索 |
| | | * 3. 单个模块 -> 返回单个模块 |
| | | * 4. 逗号分隔的多个模块 -> 返回模块列表 |
| | | * 5. 其他特殊标识(如"none")-> 返回空列表表示不选模块 |
| | | */ |
| | | private List<String> parseModuleCodes(String moduleCode) { |
| | | if (StringUtils.isBlank(moduleCode)) { |
| | | // 空/空白/null -> 全模块搜索 |
| | | return new ArrayList<>(moduleSearchMap.keySet()); |
| | | } |
| | | |
| | | String trimmedCode = moduleCode.trim(); |
| | | |
| | | // 处理全模块标识 |
| | | if (ALL_MODULES_FLAG.equalsIgnoreCase(trimmedCode)) { |
| | | return new ArrayList<>(moduleSearchMap.keySet()); |
| | | } |
| | | |
| | | // 处理不选模块的情况(可选扩展) |
| | | if ("none".equalsIgnoreCase(trimmedCode) || "null".equalsIgnoreCase(trimmedCode)) { |
| | | return Collections.emptyList(); |
| | | } |
| | | |
| | | // 检查是否包含逗号(多个模块) |
| | | if (trimmedCode.contains(MODULE_SEPARATOR)) { |
| | | String[] moduleArray = trimmedCode.split(MODULE_SEPARATOR); |
| | | return Arrays.stream(moduleArray) |
| | | .map(String::trim) |
| | | .filter(code -> !code.isEmpty()) |
| | | .collect(Collectors.toList()); |
| | | } |
| | | |
| | | // 单个模块 |
| | | return Collections.singletonList(trimmedCode); |
| | | } |
| | | |
| | | /** |
| | | * 不选模块的处理逻辑 - 使用统一数据结构 |
| | | */ |
| | | private AjaxResult handleNoModuleSelected() { |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("modules", Collections.emptyMap()); |
| | | result.put("totalSelectedModules", 0); |
| | | result.put("successModules", 0); |
| | | result.put("errorModules", Collections.emptyList()); |
| | | result.put("totalRecords", 0); |
| | | result.put("searchTime", new Date()); |
| | | result.put("message", "未选择搜索模块"); |
| | | result.put("availableModules", new ArrayList<>(moduleSearchMap.keySet())); |
| | | |
| | | return AjaxResult.success("未选择搜索模块,请选择要搜索的模块", result); |
| | | } |
| | | |
| | | /** |
| | | * 获取可用模块列表 |
| | | */ |
| | | public AjaxResult getAvailableModules() { |
| | | Map<String, Object> result = new HashMap<>(); |
| | | |
| | | List<Map<String, Object>> modules = moduleSearchMap.values().stream() |
| | | .map(service -> { |
| | | Map<String, Object> moduleInfo = new HashMap<>(); |
| | | moduleInfo.put("moduleCode", service.getModuleCode()); |
| | | moduleInfo.put("moduleName", service.getModuleName()); |
| | | return moduleInfo; |
| | | }) |
| | | .collect(Collectors.toList()); |
| | | |
| | | result.put("modules", modules); |
| | | result.put("total", modules.size()); |
| | | |
| | | return AjaxResult.success("获取可用模块成功", result); |
| | | } |
| | | |
| | | /** |
| | | * 验证模块代码是否存在 |
| | | */ |
| | | public boolean validateModule(String moduleCode) { |
| | | if (StringUtils.isBlank(moduleCode)) { |
| | | return false; |
| | | } |
| | | |
| | | // 处理多个模块的情况 |
| | | if (moduleCode.contains(MODULE_SEPARATOR)) { |
| | | String[] moduleArray = moduleCode.split(MODULE_SEPARATOR); |
| | | for (String code : moduleArray) { |
| | | String trimmedCode = code.trim(); |
| | | if (!moduleSearchMap.containsKey(trimmedCode) && |
| | | !ALL_MODULES_FLAG.equalsIgnoreCase(trimmedCode)) { |
| | | return false; |
| | | } |
| | | } |
| | | return true; |
| | | } |
| | | |
| | | // 单个模块的情况 |
| | | String trimmedCode = moduleCode.trim(); |
| | | return moduleSearchMap.containsKey(trimmedCode) || |
| | | ALL_MODULES_FLAG.equalsIgnoreCase(trimmedCode); |
| | | } |
| | | |
| | | /** |
| | | * 参数提取辅助方法 |
| | |
| | | private Date extractEndTime(Object[] args) { |
| | | return (args.length > 2 && args[2] instanceof Date) ? (Date) args[2] : null; |
| | | } |
| | | |
| | | private String extractHasAttachment(Object[] args) { |
| | | return (args.length > 3 && args[3] instanceof String) ? (String) args[3] : null; |
| | | |
| | | } |
| | | } |