package com.ruoyi.service.impl; import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.entity.SysMenu; import com.ruoyi.domain.ModuleSearchResult; import com.ruoyi.service.ModuleSearchable; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.scheduling.annotation.Async; import org.springframework.stereotype.Service; import javax.annotation.Resource; import java.util.*; import java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.function.Function; import java.util.stream.Collectors; @Service @Slf4j public class InterfaceBasedSearchRouter { private final Map moduleSearchMap; // 模块分割符 private static final String MODULE_SEPARATOR = ","; // 全模块标识 private static final String ALL_MODULES_FLAG = "all"; /** * 自动收集所有实现ModuleSearchable接口的Bean */ @Autowired public InterfaceBasedSearchRouter(List searchServices) { this.moduleSearchMap = searchServices.stream() .collect(Collectors.toMap( ModuleSearchable::getModuleCode, Function.identity(), (existing, replacement) -> { log.warn("发现重复的模块编码: {}, 使用先注册的服务", existing.getModuleCode()); return existing; } )); log.info("已注册搜索模块: {}", moduleSearchMap.keySet()); } @Async public CompletableFuture 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,hasAttachment); long searchTime = System.currentTimeMillis() - start; ModuleSearchResult result = ModuleSearchResult.success( moduleCode, service.getModuleName(), data, data.size(), searchTime ); return CompletableFuture.completedFuture(result); } catch (Exception e) { log.error("模块[{}]搜索失败: {}", moduleCode, e.getMessage()); ModuleSearchResult result = ModuleSearchResult.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)); // 验证参数数量 if (args.length != 4) { return buildErrorResponse("参数数量错误,需要4个参数,实际收到: " + args.length, null); } // 解析模块代码 List targetModules = parseModuleCodes(moduleCode); if (targetModules.isEmpty()) { // 不选模块的情况 return handleNoModuleSelected(); } // 统一处理:单个模块、多个模块都使用同样的处理逻辑 return executeModulesSearch(targetModules, args); } /** * 统一执行模块搜索 * 无论是单个模块还是多个模块,都使用统一的数据结构返回 */ private AjaxResult executeModulesSearch(List moduleCodes, Object... args) { log.info("执行模块搜索: moduleCodes={}, 模块数量={}", moduleCodes, moduleCodes.size()); // 验证所有模块是否存在 List invalidModules = moduleCodes.stream() .filter(code -> !moduleSearchMap.containsKey(code)) .collect(Collectors.toList()); if (!invalidModules.isEmpty()) { String availableModules = String.join(", ", moduleSearchMap.keySet()); String errorMsg = String.format("以下模块不支持: %s。可用模块: [%s]", invalidModules, availableModules); return buildErrorResponse(errorMsg, moduleCodes); } // 提取搜索参数 String companion = extractCompanion(args); Date happenStartTime = extractStartTime(args); Date happenEndTime = extractEndTime(args); String hasAttachment = extractHasAttachment(args); // 异步并行搜索 List> 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(); // 收集结果 Map modulesResult = new LinkedHashMap<>(); int successCount = 0; int totalCount = 0; List errorModules = new ArrayList<>(); for (int i = 0; i < futures.size(); i++) { try { ModuleSearchResult moduleResult = futures.get(i).get(); String moduleCode = moduleCodes.get(i); if (moduleResult.isSuccess()) { Map moduleData = new HashMap<>(); moduleData.put("data", moduleResult.getData()); moduleData.put("count", moduleResult.getCount()); moduleData.put("searchTime", moduleResult.getSearchTime()); moduleData.put("success", true); modulesResult.put(moduleCode, moduleData); successCount++; totalCount += moduleResult.getCount(); } else { Map errorInfo = new HashMap<>(); errorInfo.put("error", moduleResult.getErrorMessage()); errorInfo.put("success", false); 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 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 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()); // 构建消息 String message = buildSuccessMessage(moduleCodes.size(), successCount, errorModules, totalCount); finalResult.put("message", message); return AjaxResult.success(message, finalResult); } /** * 获取模块名称 */ private String getModuleName(String moduleCode) { ModuleSearchable service = moduleSearchMap.get(moduleCode); return service != null ? service.getModuleName() : "未知模块"; } /** * 构建成功消息 */ private String buildSuccessMessage(int totalSelected, int successCount, List 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 AjaxResult buildErrorResponse(String errorMessage, List moduleCodes) { Map errorData = new HashMap<>(); errorData.put("message", errorMessage); errorData.put("success", false); 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); } /** * 解析模块代码 * 支持多种格式: * 1. 空/空白/null -> 全模块搜索 * 2. "all" -> 全模块搜索 * 3. 单个模块 -> 返回单个模块 * 4. 逗号分隔的多个模块 -> 返回模块列表 * 5. 其他特殊标识(如"none")-> 返回空列表表示不选模块 */ private List 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 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 result = new HashMap<>(); List> modules = moduleSearchMap.values().stream() .map(service -> { Map 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 String extractCompanion(Object[] args) { if (args.length > 0 && args[0] instanceof String) { return (String) args[0]; } else if (args.length > 0 && args[0] != null) { return args[0].toString(); } return null; } private Date extractStartTime(Object[] args) { return (args.length > 1 && args[1] instanceof Date) ? (Date) args[1] : null; } 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; } }