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<String, ModuleSearchable> moduleSearchMap;
|
|
// 模块分割符
|
private static final String MODULE_SEPARATOR = ",";
|
|
// 全模块标识
|
private static final String ALL_MODULES_FLAG = "all";
|
|
/**
|
* 自动收集所有实现ModuleSearchable接口的Bean
|
*/
|
@Autowired
|
public InterfaceBasedSearchRouter(List<ModuleSearchable> 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<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,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<String> targetModules = parseModuleCodes(moduleCode);
|
|
if (targetModules.isEmpty()) {
|
// 不选模块的情况
|
return handleNoModuleSelected();
|
}
|
|
// 统一处理:单个模块、多个模块都使用同样的处理逻辑
|
return executeModulesSearch(targetModules, args);
|
}
|
|
/**
|
* 统一执行模块搜索
|
* 无论是单个模块还是多个模块,都使用统一的数据结构返回
|
*/
|
private AjaxResult executeModulesSearch(List<String> moduleCodes, Object... args) {
|
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());
|
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<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();
|
|
// 收集结果
|
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++) {
|
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);
|
}
|
} 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("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<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 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);
|
}
|
|
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<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 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;
|
|
}
|
}
|