| | |
| | | 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.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 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; |
| | | |
| | |
| | | |
| | | @Async |
| | | public CompletableFuture<ModuleSearchResult> searchModuleAsync(String moduleCode, ModuleSearchable service, |
| | | String companion, Date startTime, Date endTime,String hasAttachment) { |
| | | String companion, Date startTime, Date endTime, |
| | | String hasAttachment) { |
| | | long start = System.currentTimeMillis(); |
| | | try { |
| | | List<?> data = service.search(companion, startTime, endTime,hasAttachment); |
| | | // 调用搜索方法,返回 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, service.getModuleName(), data, data.size(), searchTime |
| | | moduleCode, moduleName, data, count, searchTime |
| | | ); |
| | | return CompletableFuture.completedFuture(result); |
| | | } catch (Exception e) { |
| | | log.error("模块[{}]搜索失败: {}", moduleCode, e.getMessage()); |
| | | ModuleSearchResult result = ModuleSearchResult.error(moduleCode, e.getMessage()); |
| | | 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); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * 通用的路由搜索请求 - 统一数据结构版 |
| | | * 支持功能: |
| | | * 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)); |
| | | public AjaxResult routeSearch(PeopleSea peopleS, Integer pageNum, Integer pageSize) { |
| | | String moduleCode = null; |
| | | |
| | | // 验证参数数量 |
| | | if (args.length != 4) { |
| | | return buildErrorResponse("参数数量错误,需要4个参数,实际收到: " + args.length, 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); |
| | |
| | | } |
| | | |
| | | // 统一处理:单个模块、多个模块都使用同样的处理逻辑 |
| | | return executeModulesSearch(targetModules, args); |
| | | return executeModulesSearch(targetModules, peopleS, pageNum, pageSize); |
| | | } |
| | | |
| | | /** |
| | | * 统一执行模块搜索 |
| | | * 无论是单个模块还是多个模块,都使用统一的数据结构返回 |
| | | */ |
| | | private AjaxResult executeModulesSearch(List<String> moduleCodes, Object... args) { |
| | | private AjaxResult executeModulesSearch(List<String> moduleCodes, PeopleSea peopleS, |
| | | Integer pageNum, Integer pageSize) { |
| | | log.info("执行模块搜索: moduleCodes={}, 模块数量={}", moduleCodes, moduleCodes.size()); |
| | | |
| | | // 验证所有模块是否存在 |
| | |
| | | String availableModules = String.join(", ", moduleSearchMap.keySet()); |
| | | String errorMsg = String.format("以下模块不支持: %s。可用模块: [%s]", |
| | | invalidModules, availableModules); |
| | | return buildErrorResponse(errorMsg, moduleCodes); |
| | | return AjaxResult.error(errorMsg); |
| | | } |
| | | |
| | | // 提取搜索参数 |
| | | String companion = extractCompanion(args); |
| | | Date happenStartTime = extractStartTime(args); |
| | | Date happenEndTime = extractEndTime(args); |
| | | String hasAttachment = extractHasAttachment(args); |
| | | // 提取参数 |
| | | 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; |
| | | } |
| | | |
| | | // 并发搜索 |
| | | |
| | | List<CompletableFuture<ModuleSearchResult>> futures = moduleCodes.stream() |
| | | .map(code -> searchModuleAsync(code, moduleSearchMap.get(code), |
| | | companion, happenStartTime, happenEndTime,hasAttachment)) |
| | | companion, startTime, endTime, hasAttachment)) |
| | | .collect(Collectors.toList()); |
| | | |
| | | // 等待所有搜索完成 |
| | | // 等待完成 |
| | | 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 (int i = 0; i < futures.size(); i++) { |
| | | // 合并数据 |
| | | List<Object> allData = new ArrayList<>(); |
| | | for (CompletableFuture<ModuleSearchResult> future : futures) { |
| | | try { |
| | | ModuleSearchResult moduleResult = futures.get(i).get(); |
| | | String moduleCode = moduleCodes.get(i); |
| | | |
| | | if (moduleResult.isSuccess()) { |
| | | 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); |
| | | |
| | | modulesResult.put(moduleCode, moduleData); |
| | | |
| | | successCount++; |
| | | totalCount += moduleResult.getCount(); |
| | | } else { |
| | | Map<String, Object> errorInfo = new HashMap<>(); |
| | | errorInfo.put("error", moduleResult.getErrorMessage()); |
| | | errorInfo.put("success", false); |
| | | errorInfo.put("moduleName", getModuleName(moduleCode)); |
| | | modulesResult.put(moduleCode, errorInfo); |
| | | errorModules.add(moduleCode); |
| | | ModuleSearchResult result = future.get(); |
| | | if (result.isSuccess() && result.getData() != null) { |
| | | allData.addAll(result.getData()); |
| | | } |
| | | } 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); |
| | | // 记录错误但继续处理其他模块 |
| | | log.error("获取模块搜索结果失败", e); |
| | | } |
| | | } |
| | | |
| | | // 构建最终返回结果 |
| | | 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()); |
| | | // 对数据进行排序(如果需要) |
| | | sortData(allData); |
| | | |
| | | // 构建消息 |
| | | String message = buildSuccessMessage(moduleCodes.size(), successCount, errorModules, totalCount); |
| | | finalResult.put("message", message); |
| | | // 分页 |
| | | int startIndex = (pageNum - 1) * pageSize; |
| | | List<Object> paginatedData = allData.stream() |
| | | .skip(startIndex) |
| | | .limit(pageSize) |
| | | .collect(Collectors.toList()); |
| | | |
| | | return AjaxResult.success(message, finalResult); |
| | | // 返回结果 |
| | | 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 { |
| | | // 尝试常见的日期格式 |
| | | 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.warn("无法解析日期字符串: {}", dateStr, e); |
| | | } |
| | | |
| | | return null; |
| | | } |
| | | |
| | | /** |
| | |
| | | } |
| | | |
| | | /** |
| | | * 构建成功消息 |
| | | * 安全的参数提取方法 |
| | | */ |
| | | 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 String extractCompanion(PeopleSea peopleS) { |
| | | if (peopleS == null) { |
| | | return ""; |
| | | } |
| | | return peopleS.getPeoples() == null ? "" : peopleS.getPeoples().trim(); |
| | | } |
| | | |
| | | /** |
| | | * 构建错误响应 |
| | | */ |
| | | private AjaxResult buildErrorResponse(String errorMessage, List<String> moduleCodes) { |
| | | Map<String, Object> errorData = new HashMap<>(); |
| | | errorData.put("message", errorMessage); |
| | | errorData.put("success", false); |
| | | |
| | | if (moduleCodes != null) { |
| | | errorData.put("selectedModules", moduleCodes); |
| | | private Date extractStartTime(PeopleSea peopleS) { |
| | | if (peopleS == null) { |
| | | return null; |
| | | } |
| | | return peopleS.getStartTime(); |
| | | } |
| | | |
| | | 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()); |
| | | private Date extractEndTime(PeopleSea peopleS) { |
| | | if (peopleS == null) { |
| | | return null; |
| | | } |
| | | return peopleS.getEndTime(); |
| | | } |
| | | |
| | | return AjaxResult.error(errorMessage, errorData); |
| | | private String extractHasAttachment(PeopleSea peopleS) { |
| | | if (peopleS == null) { |
| | | return ""; |
| | | } |
| | | return peopleS.getHasAttachment() == null ? "" : peopleS.getHasAttachment().trim(); |
| | | } |
| | | |
| | | /** |
| | | * 解析模块代码 |
| | | * 支持多种格式: |
| | | * 1. 空/空白/null -> 全模块搜索 |
| | | * 2. "all" -> 全模块搜索 |
| | | * 3. 单个模块 -> 返回单个模块 |
| | | * 4. 逗号分隔的多个模块 -> 返回模块列表 |
| | | * 5. 其他特殊标识(如"none")-> 返回空列表表示不选模块 |
| | | */ |
| | | private List<String> parseModuleCodes(String moduleCode) { |
| | | if (StringUtils.isBlank(moduleCode)) { |
| | |
| | | return new ArrayList<>(moduleSearchMap.keySet()); |
| | | } |
| | | |
| | | // 处理不选模块的情况(可选扩展) |
| | | // 处理不选模块的情况 |
| | | if ("none".equalsIgnoreCase(trimmedCode) || "null".equalsIgnoreCase(trimmedCode)) { |
| | | return Collections.emptyList(); |
| | | } |
| | |
| | | } |
| | | |
| | | /** |
| | | * 不选模块的处理逻辑 - 使用统一数据结构 |
| | | * 不选模块的处理逻辑 |
| | | */ |
| | | 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())); |
| | | result.put("list", Collections.emptyList()); |
| | | result.put("total", 0); |
| | | result.put("pageNum", 1); |
| | | result.put("pageSize", 10); |
| | | |
| | | return AjaxResult.success("未选择搜索模块,请选择要搜索的模块", result); |
| | | } |
| | |
| | | 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; |
| | | |
| | | } |
| | | } |