| | |
| | | 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.domain.PeopleSea; |
| | | 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; |
| | | import org.springframework.util.CollectionUtils; |
| | | |
| | | import java.util.Arrays; |
| | | import java.util.Date; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.*; |
| | | import java.util.concurrent.CompletableFuture; |
| | | import java.util.function.Function; |
| | | import java.util.stream.Collectors; |
| | | |
| | |
| | | public class InterfaceBasedSearchRouter { |
| | | |
| | | 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 |
| | | public CompletableFuture<ModuleSearchResult> searchModuleAsync(String moduleCode, ModuleSearchable service, |
| | | String companion, Date startTime, Date endTime, |
| | | String hasAttachment, Integer pageNum, Integer pageSize) { |
| | | long start = System.currentTimeMillis(); |
| | | try { |
| | | // 调用搜索方法,返回 List<?> |
| | | List<?> data = service.search(companion, startTime, endTime, hasAttachment); |
| | | long searchTime = System.currentTimeMillis() - start; |
| | | |
| | | int count = 0; |
| | | if (data != null) { |
| | | count = data.size(); |
| | | } |
| | | |
| | | // 获取模块名称 |
| | | String moduleName = getModuleName(moduleCode); |
| | | |
| | | ModuleSearchResult result = ModuleSearchResult.success( |
| | | moduleCode, moduleName, data, count, searchTime |
| | | ); |
| | | return CompletableFuture.completedFuture(result); |
| | | } catch (Exception e) { |
| | | log.error("模块[{}]搜索失败: {}", moduleCode, e.getMessage(), e); |
| | | String errorMessage = e.getMessage(); |
| | | if (e.getCause() != null) { |
| | | errorMessage += " (" + e.getCause().getMessage() + ")"; |
| | | } |
| | | ModuleSearchResult result = ModuleSearchResult.error(moduleCode, errorMessage); |
| | | return CompletableFuture.completedFuture(result); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 通用的路由搜索请求 |
| | | */ |
| | | public AjaxResult routeSearch(String moduleCode, Object... args) { |
| | | log.info("路由搜索: moduleCode={}, args={}", moduleCode, Arrays.toString(args)); |
| | | public AjaxResult routeSearch(PeopleSea peopleS, Integer pageNum, Integer pageSize) { |
| | | String moduleCode = null; |
| | | |
| | | ModuleSearchable searchService = moduleSearchMap.get(moduleCode); |
| | | if (searchService == null) { |
| | | // 安全处理 String[] 类型的 modules |
| | | if (peopleS != null && peopleS.getModules() != null && peopleS.getModules().length != 0) { |
| | | String[] modulesArray = peopleS.getModules(); |
| | | |
| | | // 过滤掉空字符串和空白字符 |
| | | List<String> validModules = Arrays.stream(modulesArray) |
| | | .filter(StringUtils::isNotBlank) |
| | | .map(String::trim) |
| | | .collect(Collectors.toList()); |
| | | |
| | | if (!validModules.isEmpty()) { |
| | | // 用逗号连接有效的模块代码 |
| | | moduleCode = String.join(",", validModules); |
| | | } |
| | | } |
| | | |
| | | log.info("路由搜索: moduleCode={}, peopleS={}, pageNum={}, pageSize={}", |
| | | moduleCode, peopleS, pageNum, pageSize); |
| | | |
| | | // 解析模块代码 |
| | | List<String> targetModules = parseModuleCodes(moduleCode); |
| | | |
| | | if (targetModules.isEmpty()) { |
| | | // 不选模块的情况 |
| | | return handleNoModuleSelected(); |
| | | } |
| | | |
| | | // 统一处理:单个模块、多个模块都使用同样的处理逻辑 |
| | | return executeModulesSearch(targetModules, peopleS, pageNum, pageSize); |
| | | } |
| | | |
| | | /** |
| | | * 统一执行模块搜索 |
| | | */ |
| | | private AjaxResult executeModulesSearch(List<String> moduleCodes, PeopleSea peopleS, |
| | | Integer pageNum, Integer pageSize) { |
| | | log.info("执行模块搜索: moduleCodes={}, 模块数量={}", moduleCodes, moduleCodes.size()); |
| | | |
| | | // 验证所有模块是否存在 |
| | | List<String> invalidModules = moduleCodes.stream() |
| | | .filter(code -> !moduleSearchMap.containsKey(code)) |
| | | .collect(Collectors.toList()); |
| | | |
| | | if (!invalidModules.isEmpty()) { |
| | | String availableModules = String.join(", ", moduleSearchMap.keySet()); |
| | | return AjaxResult.error("不支持的搜索模块: " + moduleCode + "。可用模块: [" + availableModules + "]"); |
| | | String errorMsg = String.format("以下模块不支持: %s。可用模块: [%s]", |
| | | invalidModules, availableModules); |
| | | return AjaxResult.error(errorMsg); |
| | | } |
| | | |
| | | // 提取参数 |
| | | String companion = extractCompanion(peopleS); |
| | | Date startTime = extractStartTime(peopleS); |
| | | Date endTime = extractEndTime(peopleS); |
| | | String hasAttachment = extractHasAttachment(peopleS); |
| | | |
| | | // 设置分页默认值 |
| | | if (pageNum == null || pageNum <= 0) { |
| | | pageNum = 1; |
| | | } |
| | | if (pageSize == null || pageSize <= 0) { |
| | | pageSize = 10; |
| | | } |
| | | |
| | | // 并发搜索 |
| | | Integer finalPageNum = pageNum; |
| | | Integer finalPageSize = pageSize; |
| | | List<CompletableFuture<ModuleSearchResult>> futures = moduleCodes.stream() |
| | | .map(code -> searchModuleAsync(code, moduleSearchMap.get(code), |
| | | companion, startTime, endTime, hasAttachment, finalPageNum, finalPageSize)) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 等待完成 |
| | | CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); |
| | | |
| | | // 合并数据 |
| | | List<Object> allData = new ArrayList<>(); |
| | | for (CompletableFuture<ModuleSearchResult> future : futures) { |
| | | try { |
| | | ModuleSearchResult result = future.get(); |
| | | if (result.isSuccess() && result.getData() != null) { |
| | | allData.addAll(result.getData()); |
| | | } |
| | | } catch (Exception e) { |
| | | // 记录错误但继续处理其他模块 |
| | | log.error("获取模块搜索结果失败", e); |
| | | } |
| | | } |
| | | |
| | | // 对数据进行排序(如果需要) |
| | | sortData(allData); |
| | | |
| | | // 分页 |
| | | int startIndex = (pageNum - 1) * pageSize; |
| | | List<Object> paginatedData = allData.stream() |
| | | .skip(startIndex) |
| | | .limit(pageSize) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 返回结果 |
| | | Map<String, Object> data = new HashMap<>(); |
| | | data.put("list", paginatedData); |
| | | data.put("total", allData.size()); |
| | | data.put("pageNum", pageNum); |
| | | data.put("pageSize", pageSize); |
| | | |
| | | return AjaxResult.success(data); |
| | | } |
| | | |
| | | /** |
| | | * 对数据进行排序 |
| | | * 默认按创建时间降序排列 |
| | | */ |
| | | private void sortData(List<Object> allData) { |
| | | if (CollectionUtils.isEmpty(allData)) { |
| | | return; |
| | | } |
| | | |
| | | // 如果数据是 Map 类型,尝试按 createTime 排序 |
| | | if (allData.get(0) instanceof Map) { |
| | | allData.sort((a, b) -> { |
| | | Map<String, Object> mapA = (Map<String, Object>) a; |
| | | Map<String, Object> mapB = (Map<String, Object>) b; |
| | | |
| | | Object timeA = mapA.get("createTime"); |
| | | Object timeB = mapB.get("createTime"); |
| | | |
| | | if (timeA instanceof Date && timeB instanceof Date) { |
| | | // 降序排列:最新的在前 |
| | | return ((Date) timeB).compareTo((Date) timeA); |
| | | } else if (timeA instanceof String && timeB instanceof String) { |
| | | // 如果时间是字符串,尝试解析 |
| | | try { |
| | | Date dateA = parseDate((String) timeA); |
| | | Date dateB = parseDate((String) timeB); |
| | | if (dateA != null && dateB != null) { |
| | | return dateB.compareTo(dateA); |
| | | } |
| | | } catch (Exception e) { |
| | | // 解析失败,不排序 |
| | | } |
| | | } |
| | | return 0; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 解析日期字符串 |
| | | */ |
| | | private Date parseDate(String dateStr) { |
| | | if (dateStr == null || dateStr.isEmpty()) { |
| | | return null; |
| | | } |
| | | |
| | | try { |
| | | // 根据参数数量进行路由 |
| | | if (args.length == 3) { |
| | | return handleFourArgs(searchService, args); |
| | | } else { |
| | | return AjaxResult.error("不支持的参数数量,需要3个参数,实际收到: " + args.length); |
| | | // 尝试常见的日期格式 |
| | | String[] formats = { |
| | | "yyyy-MM-dd'T'HH:mm:ss.SSSXXX", |
| | | "yyyy-MM-dd HH:mm:ss", |
| | | "yyyy-MM-dd" |
| | | }; |
| | | |
| | | for (String format : formats) { |
| | | try { |
| | | return new java.text.SimpleDateFormat(format).parse(dateStr); |
| | | } catch (Exception e) { |
| | | // 继续尝试下一个格式 |
| | | } |
| | | } |
| | | } catch (Exception e) { |
| | | log.error("搜索执行失败: moduleCode={}", moduleCode, e); |
| | | return AjaxResult.error("搜索执行失败: " + e.getMessage()); |
| | | log.warn("无法解析日期字符串: {}", dateStr, e); |
| | | } |
| | | |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | | * 处理3个参数的情况(companion + happenStartTime + happenEndTime) |
| | | * 获取模块名称 |
| | | */ |
| | | 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); |
| | | } |
| | | // |
| | | // /** |
| | | // * 获取所有可搜索的模块信息 |
| | | // */ |
| | | // public List<SysMenu> getAvailableModules() { |
| | | // return moduleSearchMap.values().stream() |
| | | // .map(service -> SysMenu.builder() |
| | | // .moduleCode(service.getModuleCode()) |
| | | // .moduleName(service.getModuleName()) |
| | | // .build()) |
| | | // .collect(Collectors.toList()); |
| | | // } |
| | | |
| | | /** |
| | | * 检查模块是否支持搜索 |
| | | */ |
| | | public boolean supports(String moduleCode) { |
| | | return moduleSearchMap.containsKey(moduleCode); |
| | | private String getModuleName(String moduleCode) { |
| | | ModuleSearchable service = moduleSearchMap.get(moduleCode); |
| | | return service != null ? service.getModuleName() : "未知模块"; |
| | | } |
| | | |
| | | /** |
| | | * 获取模块服务实例 |
| | | * 安全的参数提取方法 |
| | | */ |
| | | public ModuleSearchable getModuleService(String moduleCode) { |
| | | return moduleSearchMap.get(moduleCode); |
| | | private String extractCompanion(PeopleSea peopleS) { |
| | | if (peopleS == null) { |
| | | return ""; |
| | | } |
| | | return peopleS.getPeoples() == null ? "" : peopleS.getPeoples().trim(); |
| | | } |
| | | |
| | | private Date extractStartTime(PeopleSea peopleS) { |
| | | if (peopleS == null) { |
| | | return null; |
| | | } |
| | | return peopleS.getStartTime(); |
| | | } |
| | | |
| | | private Date extractEndTime(PeopleSea peopleS) { |
| | | if (peopleS == null) { |
| | | return null; |
| | | } |
| | | return peopleS.getEndTime(); |
| | | } |
| | | |
| | | private String extractHasAttachment(PeopleSea peopleS) { |
| | | if (peopleS == null) { |
| | | return ""; |
| | | } |
| | | return peopleS.getHasAttachment() == null ? "" : peopleS.getHasAttachment().trim(); |
| | | } |
| | | |
| | | /** |
| | | * 解析模块代码 |
| | | */ |
| | | 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("list", Collections.emptyList()); |
| | | result.put("total", 0); |
| | | result.put("pageNum", 1); |
| | | result.put("pageSize", 10); |
| | | |
| | | 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); |
| | | } |
| | | } |