diff --git a/src/main/java/com/example/workutils/WorkUtilsApplication.java b/src/main/java/com/example/workutils/WorkUtilsApplication.java index c8aca9b..8e85e68 100644 --- a/src/main/java/com/example/workutils/WorkUtilsApplication.java +++ b/src/main/java/com/example/workutils/WorkUtilsApplication.java @@ -1,7 +1,7 @@ package com.example.workutils; import com.example.workutils.system.tool.Tool; -import com.example.workutils.system.tool.Registry; +import com.example.workutils.system.tool.ToolRegistry; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; @@ -16,10 +16,10 @@ import java.util.Scanner; @Slf4j public class WorkUtilsApplication implements CommandLineRunner { - private final Registry registry; + private final ToolRegistry toolRegistry; - public WorkUtilsApplication(Registry registry) { - this.registry = registry; + public WorkUtilsApplication(ToolRegistry toolRegistry) { + this.toolRegistry = toolRegistry; } public static void main(String[] args) { @@ -37,7 +37,7 @@ public class WorkUtilsApplication implements CommandLineRunner { private void showInteractiveMenu() { Scanner scanner = new Scanner(System.in); - registry.runMainMenu(scanner); + toolRegistry.runMainMenu(scanner); } /** @@ -47,7 +47,7 @@ public class WorkUtilsApplication implements CommandLineRunner { private void processCommand(String... args) throws Exception { String command = args[0].toLowerCase(); - Optional toolOpt = registry.getToolByCommand(command); + Optional toolOpt = toolRegistry.getToolByCommand(command); if (toolOpt.isPresent()) { Tool tool = toolOpt.get(); try { diff --git a/src/main/java/com/example/workutils/biz/output/menu/BatchMenu.java b/src/main/java/com/example/workutils/biz/batch/BatchTool.java similarity index 85% rename from src/main/java/com/example/workutils/biz/output/menu/BatchMenu.java rename to src/main/java/com/example/workutils/biz/batch/BatchTool.java index 2b7bf3b..5c7ebfb 100644 --- a/src/main/java/com/example/workutils/biz/output/menu/BatchMenu.java +++ b/src/main/java/com/example/workutils/biz/batch/BatchTool.java @@ -1,7 +1,7 @@ -package com.example.workutils.biz.output.menu; +package com.example.workutils.biz.batch; -import com.example.workutils.biz.mybatis.export.MyBatisExportService; -import com.example.workutils.biz.scanner.service.ScanService; +import com.example.workutils.biz.mybatis.MyBatisExportService; +import com.example.workutils.biz.scanner.ScannerService; import com.example.workutils.system.tool.Tool; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -12,13 +12,13 @@ import java.util.List; import java.util.Scanner; /** - * 批量处理菜单 + * 批量处理工具 * 仅负责菜单展示和用户交互,业务逻辑委托给各Service */ @Slf4j @Component @RequiredArgsConstructor -public class BatchMenu implements Tool { +public class BatchTool implements Tool { @FunctionalInterface public interface ExceptionRunnable { @@ -38,7 +38,7 @@ public class BatchMenu implements Tool { "4. 关键词扫描" }; - private final ScanService scanService; + private final ScannerService scannerService; private final MyBatisExportService myBatisExportService; @Override @@ -89,9 +89,9 @@ public class BatchMenu implements Tool { executeStep("1. 导出SQL...", () -> myBatisExportService.exportSQL()); executeStep("2. 扫描Dubbo接口...", - () -> scanService.scanDubboInterfacesWithDefaultName("@Reference|@Service| scannerService.scanDubboInterfacesWithDefaultName("@Reference|@Service| scanService.scanRedisKeysWithDefaultName("redis\\.opsFor")); + () -> scannerService.scanRedisKeysWithDefaultName("redis\\.opsFor")); printSuccess("所有检查"); log.info("所有检查执行完成"); @@ -115,17 +115,17 @@ public class BatchMenu implements Tool { break; case "2": executeStep("执行Dubbo接口扫描...", - () -> scanService.scanDubboInterfacesWithDefaultName("@Reference|@Service")); + () -> scannerService.scanDubboInterfacesWithDefaultName("@Reference|@Service")); break; case "3": executeStep("执行Redis操作扫描...", - () -> scanService.scanRedisKeysWithDefaultName("redis\\.opsFor")); + () -> scannerService.scanRedisKeysWithDefaultName("redis\\.opsFor")); break; case "4": System.out.print("请输入关键词: "); String keyword = scanner.nextLine().trim(); executeStep("执行关键词扫描...", - () -> scanService.scanKeywordsWithDefaultName(keyword)); + () -> scannerService.scanKeywordsWithDefaultName(keyword)); break; default: System.out.println("跳过无效选项: " + choice); diff --git a/src/main/java/com/example/workutils/biz/git/GitService.java b/src/main/java/com/example/workutils/biz/git/GitService.java new file mode 100644 index 0000000..848623f --- /dev/null +++ b/src/main/java/com/example/workutils/biz/git/GitService.java @@ -0,0 +1,24 @@ +package com.example.workutils.biz.git; + +import com.example.workutils.biz.git.internal.GitCommandExecutor; + +import java.util.List; +import java.util.Scanner; + +/** + * Git 服务接口 + */ +public interface GitService { + + void checkBranchChanges(String repoRoot, String branchPairs); + + void checkoutAndPull(String repoRoot, String targetBranch); + + List checkCorruptedRepos(); + + List checkCorruptedRepos(Scanner scanner); + + boolean fixCorruptedRepo(String repoPath); + + List listCorruptedRepos(); +} diff --git a/src/main/java/com/example/workutils/biz/git/service/impl/GitServiceImpl.java b/src/main/java/com/example/workutils/biz/git/GitServiceImpl.java similarity index 85% rename from src/main/java/com/example/workutils/biz/git/service/impl/GitServiceImpl.java rename to src/main/java/com/example/workutils/biz/git/GitServiceImpl.java index 183843b..7492441 100644 --- a/src/main/java/com/example/workutils/biz/git/service/impl/GitServiceImpl.java +++ b/src/main/java/com/example/workutils/biz/git/GitServiceImpl.java @@ -1,9 +1,9 @@ -package com.example.workutils.biz.git.service.impl; +package com.example.workutils.biz.git; -import com.example.workutils.biz.git.model.GitCommandDTO; -import com.example.workutils.biz.git.service.GitService; -import com.example.workutils.biz.git.util.GitCommandExecutor; -import com.example.workutils.biz.scanner.service.FileOperationService; +import com.example.workutils.biz.git.internal.GitCommandExecutor; +import com.example.workutils.biz.git.internal.strategy.GitStrategyFactory; +import com.example.workutils.biz.git.model.GitCommand; +import com.example.workutils.system.support.FileOperationService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -32,15 +32,15 @@ public class GitServiceImpl implements GitService { @Override public void checkBranchChanges(String repoRoot, String branchPairs) { - GitCommandDTO commandDTO = GitCommandDTO.create(repoRoot, branchPairs, - GitCommandDTO.GitCommandType.CHECK_BRANCH_CHANGES); + GitCommand commandDTO = GitCommand.create(repoRoot, branchPairs, + GitCommand.GitCommandType.CHECK_BRANCH_CHANGES); strategyFactory.executeCommand(commandDTO); } @Override public void checkoutAndPull(String repoRoot, String targetBranch) { - GitCommandDTO commandDTO = GitCommandDTO.create(repoRoot, targetBranch, - GitCommandDTO.GitCommandType.CHECKOUT_AND_PULL); + GitCommand commandDTO = GitCommand.create(repoRoot, targetBranch, + GitCommand.GitCommandType.CHECKOUT_AND_PULL); strategyFactory.executeCommand(commandDTO); } @@ -75,11 +75,9 @@ public class GitServiceImpl implements GitService { log.info("检测仓库: {}", repoPath); System.out.printf("[%d/%d] 检测: %s ...%n", checkedCount + 1, totalCount, repoName); - // 检测仓库健康状态 GitCommandExecutor.RepoHealthResult healthResult = GitCommandExecutor.checkRepoHealth(repoPath); checkedCount++; - // 处理检测结果 if (healthResult.isHealthy()) { try { fileOperationService.updateProjectStatus(repoPath, FileOperationService.ProjectStatus.NORMAL); @@ -97,7 +95,6 @@ public class GitServiceImpl implements GitService { } } - // 检查用户是否按了键(使用System.in.available()非阻塞检测) try { if (System.in.available() > 0) { int ch = System.in.read(); @@ -107,10 +104,7 @@ public class GitServiceImpl implements GitService { isAborted = true; break; } else { - // 消耗掉缓冲区中的其他字符 - while (System.in.available() > 0) { - System.in.read(); - } + while (System.in.available() > 0) { System.in.read(); } } } } catch (Exception e) { @@ -137,7 +131,6 @@ public class GitServiceImpl implements GitService { public boolean fixCorruptedRepo(String repoPath) { log.info("开始修复损坏的仓库: {}", repoPath); - // 获取远程仓库URL String remoteUrl = GitCommandExecutor.getRemoteUrl(repoPath); if (remoteUrl == null) { log.error("无法获取远程仓库URL: {}", repoPath); @@ -145,12 +138,10 @@ public class GitServiceImpl implements GitService { } log.info("远程仓库URL: {}", remoteUrl); - // 执行克隆 GitCommandExecutor.CommandResult result = GitCommandExecutor.cloneRepo(remoteUrl, repoPath, DEFAULT_BRANCH); if (result.isSuccess()) { log.info("仓库修复成功: {}", repoPath); - // 更新配置文件状态为NORMAL try { fileOperationService.updateProjectStatus(repoPath, FileOperationService.ProjectStatus.NORMAL); } catch (IOException e) { diff --git a/src/main/java/com/example/workutils/biz/git/menu/GitMenu.java b/src/main/java/com/example/workutils/biz/git/GitTool.java similarity index 87% rename from src/main/java/com/example/workutils/biz/git/menu/GitMenu.java rename to src/main/java/com/example/workutils/biz/git/GitTool.java index 988508d..ec47d25 100644 --- a/src/main/java/com/example/workutils/biz/git/menu/GitMenu.java +++ b/src/main/java/com/example/workutils/biz/git/GitTool.java @@ -1,7 +1,6 @@ -package com.example.workutils.biz.git.menu; +package com.example.workutils.biz.git; -import com.example.workutils.biz.git.service.GitService; -import com.example.workutils.biz.git.util.GitCommandExecutor; +import com.example.workutils.biz.git.internal.GitCommandExecutor; import com.example.workutils.system.tool.Tool; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -13,13 +12,12 @@ import java.util.Scanner; import java.util.stream.Collectors; /** - * Git仓库健康检测菜单 - * 仅负责菜单展示和用户交互,业务逻辑委托给GitService + * Git仓库健康检测工具入口(符合 Tool 规范) */ @Slf4j @Component @RequiredArgsConstructor -public class GitMenu implements Tool { +public class GitTool implements Tool { private static final String[] MENU_OPTIONS = { "1. 检测所有仓库健康状态", @@ -47,15 +45,9 @@ public class GitMenu implements Tool { @Override public void handleChoice(int choice, Scanner scanner) throws Exception { switch (choice) { - case 1: - checkAllReposHealth(scanner); - break; - case 2: - fixCorruptedRepos(scanner); - break; - default: - printInvalidOption(); - break; + case 1: checkAllReposHealth(scanner); break; + case 2: fixCorruptedRepos(scanner); break; + default: printInvalidOption(); break; } } @@ -143,15 +135,8 @@ public class GitMenu implements Tool { } return indices; } - return parseCommaSeparatedInput(input).stream() - .map(s -> { - try { - return Integer.parseInt(s); - } catch (NumberFormatException e) { - return -1; - } - }) + .map(s -> { try { return Integer.parseInt(s); } catch (NumberFormatException e) { return -1; } }) .filter(i -> i >= 1 && i <= repoCount) .collect(Collectors.toList()); } diff --git a/src/main/java/com/example/workutils/biz/git/util/GitCommandController.java b/src/main/java/com/example/workutils/biz/git/internal/GitCommandController.java similarity index 99% rename from src/main/java/com/example/workutils/biz/git/util/GitCommandController.java rename to src/main/java/com/example/workutils/biz/git/internal/GitCommandController.java index d65a482..95027bf 100644 --- a/src/main/java/com/example/workutils/biz/git/util/GitCommandController.java +++ b/src/main/java/com/example/workutils/biz/git/internal/GitCommandController.java @@ -1,4 +1,4 @@ -package com.example.workutils.biz.git.util; +package com.example.workutils.biz.git.internal; import lombok.extern.slf4j.Slf4j; diff --git a/src/main/java/com/example/workutils/biz/git/util/GitCommandExecutor.java b/src/main/java/com/example/workutils/biz/git/internal/GitCommandExecutor.java similarity index 81% rename from src/main/java/com/example/workutils/biz/git/util/GitCommandExecutor.java rename to src/main/java/com/example/workutils/biz/git/internal/GitCommandExecutor.java index 2652bba..5bbfc21 100644 --- a/src/main/java/com/example/workutils/biz/git/util/GitCommandExecutor.java +++ b/src/main/java/com/example/workutils/biz/git/internal/GitCommandExecutor.java @@ -1,4 +1,4 @@ -package com.example.workutils.biz.git.util; +package com.example.workutils.biz.git.internal; import lombok.extern.slf4j.Slf4j; @@ -18,35 +18,22 @@ import java.util.stream.Collectors; @Slf4j public class GitCommandExecutor { - private static final long DEFAULT_COMMAND_TIMEOUT_SECONDS = 20; // 默认超时 - private static final int DEFAULT_THREAD_POOL_SIZE = 8; // 默认线程池大小 - private static final long MIN_COMMAND_TIMEOUT_SECONDS = 10; // 最小超时 - private static final long MAX_COMMAND_TIMEOUT_SECONDS = 300; // 最大超时(5分钟) + private static final long DEFAULT_COMMAND_TIMEOUT_SECONDS = 20; + private static final int DEFAULT_THREAD_POOL_SIZE = 8; + private static final long MIN_COMMAND_TIMEOUT_SECONDS = 10; + private static final long MAX_COMMAND_TIMEOUT_SECONDS = 300; - // 线程池管理 private static volatile ExecutorService executorService; private static final Object threadPoolLock = new Object(); - /** - * 路径格式化工具方法 - 统一路径格式 - */ private static String normalizePath(String path) { if (path == null || path.trim().isEmpty()) { return path; } - - // 统一使用正斜杠,Git在Windows下也能正确处理正斜杠路径 return path.replace("\\", "/"); } - /** - * 执行 Git 命令 - * @param repoRoot 仓库根目录 - * @param commands 命令列表 - * @return 执行结果 - */ public static CommandResult executeCommands(String repoRoot, List commands) { - // 规范化路径格式 String normalizedRepoRoot = normalizePath(repoRoot); log.debug("Using normalized repo path: {}", normalizedRepoRoot); @@ -75,14 +62,7 @@ public class GitCommandExecutor { return result; } - /** - * 执行单个命令 - * @param workingDir 工作目录 - * @param command 命令 - * @param processConsumer 可选的Process消费者,用于获取Process对象以便中断(可为空) - */ private static CommandResult executeCommand(String workingDir, String command, Consumer processConsumer) throws IOException, InterruptedException { - // 再次确保路径格式正确 String normalizedWorkingDir = normalizePath(workingDir); log.info("Executing command in directory {}: {}", normalizedWorkingDir, command); @@ -99,14 +79,12 @@ public class GitCommandExecutor { Process process = processBuilder.start(); - // 如果提供了Process消费者,将Process传递出去 if (processConsumer != null) { processConsumer.accept(process); } CommandResult result = new CommandResult(); - // 读取输出流(使用异步读取避免缓冲区满导致进程阻塞) ExecutorService readerPool = Executors.newFixedThreadPool(2); CountDownLatch latch = new CountDownLatch(2); @@ -149,14 +127,12 @@ public class GitCommandExecutor { long timeout = calculateDynamicTimeout(workingDir, command); try { - // 等待进程完成或超时 boolean finished = process.waitFor(timeout, TimeUnit.SECONDS); if (!finished) { - // 尝试优雅终止 process.destroy(); try { - Thread.sleep(2000); // 等待2秒让进程优雅退出 + Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } @@ -167,7 +143,6 @@ public class GitCommandExecutor { } } - // 收集输出 result.setOutputLines(outputFuture.get()); result.setErrorLines(errorFuture.get()); @@ -190,42 +165,27 @@ public class GitCommandExecutor { return result; } - /** - * 执行单个命令(兼容旧版本) - */ private static CommandResult executeCommand(String workingDir, String command) throws IOException, InterruptedException { return executeCommand(workingDir, command, null); } - /** - * 批量执行Git命令(支持多仓库)- 并发优化版本 - */ public static List executeGitCommandsInRepos(String reposPath, List repoNames, List gitCommands) { return executeGitCommandsInRepos(reposPath, repoNames, gitCommands, DEFAULT_THREAD_POOL_SIZE); } - /** - * 批量执行Git命令(支持多仓库)- 自定义并发度 - */ public static List executeGitCommandsInRepos(String reposPath, List repoNames, List gitCommands, int concurrency) { - // 规范化基础路径 String normalizedReposPath = normalizePath(reposPath); - - // 计算最优线程池大小 int optimalPoolSize = calculateOptimalThreadPoolSize(repoNames.size(), concurrency); log.info("Using thread pool size: {} for {} repositories", optimalPoolSize, repoNames.size()); - // 创建线程池(根据最优大小调整) ExecutorService threadPool = Executors.newFixedThreadPool(optimalPoolSize); try { - // 使用并发执行 List> futures = repoNames.stream() .map(repoName -> CompletableFuture.supplyAsync(() -> executeGitCommandsForRepo(normalizedReposPath, repoName, gitCommands), threadPool)) .collect(Collectors.toList()); - // 等待所有任务完成并收集结果 return futures.stream() .map(future -> { try { @@ -239,7 +199,6 @@ public class GitCommandExecutor { } }) .collect(Collectors.toList()); - } finally { threadPool.shutdown(); try { @@ -253,11 +212,7 @@ public class GitCommandExecutor { } } - /** - * 为单个仓库执行Git命令 - */ private static RepoResult executeGitCommandsForRepo(String normalizedReposPath, String repoName, List gitCommands) { - // 使用规范化路径拼接,确保格式统一 String repoPath = normalizedReposPath + "/" + repoName; RepoResult repoResult = new RepoResult(repoName, repoPath); @@ -281,20 +236,13 @@ public class GitCommandExecutor { return repoResult; } - /** - * 批量执行Git命令(支持多仓库)- 串行版本(保持向后兼容) - */ public static List executeGitCommandsInReposSequential(String reposPath, List repoNames, List gitCommands) { List results = new ArrayList<>(); - - // 规范化基础路径 String normalizedReposPath = normalizePath(reposPath); - for (String repoName : repoNames) { RepoResult repoResult = executeGitCommandsForRepo(normalizedReposPath, repoName, gitCommands); results.add(repoResult); } - return results; } @@ -302,27 +250,18 @@ public class GitCommandExecutor { return System.getProperty("os.name").toLowerCase().contains("win"); } - /** - * 动态计算命令超时时间 - * 根据仓库大小、命令复杂度和网络状况调整 - */ private static long calculateDynamicTimeout(String workingDir, String command) { long baseTimeout = DEFAULT_COMMAND_TIMEOUT_SECONDS; - - // 根据命令复杂度调整 if (command.contains("fetch") || command.contains("clone") || command.contains("pull")) { - baseTimeout *= 2; // 网络操作时间加倍 + baseTimeout *= 2; } else if (command.contains("merge") || command.contains("rebase")) { - baseTimeout *= 1.5; // 复杂操作时间增加50% + baseTimeout = (long)(baseTimeout * 1.5); } - - // 根据仓库大小调整 try { if (workingDir != null && !workingDir.isEmpty()) { File repoDir = new File(workingDir); if (repoDir.exists()) { long repoSize = calculateDirectorySize(repoDir); - // 每100MB增加10秒超时时间 long sizeMultiplier = repoSize / (100 * 1024 * 1024); if (sizeMultiplier > 0) { baseTimeout += sizeMultiplier * 10; @@ -332,27 +271,20 @@ public class GitCommandExecutor { } catch (Exception e) { log.debug("Failed to calculate repository size for timeout", e); } - - // 确保超时时间在合理范围内 return Math.max(MIN_COMMAND_TIMEOUT_SECONDS, Math.min(baseTimeout, MAX_COMMAND_TIMEOUT_SECONDS)); } - /** - * 计算目录大小(递归) - */ private static long calculateDirectorySize(File directory) { long size = 0; if (directory.isFile()) { return directory.length(); } - File[] files = directory.listFiles(); if (files != null) { for (File file : files) { if (file.isFile()) { size += file.length(); } else if (file.isDirectory() && !file.getName().equals(".git")) { - // 递归计算子目录大小,但排除.git目录 size += calculateDirectorySize(file); } } @@ -360,28 +292,15 @@ public class GitCommandExecutor { return size; } - /** - * 计算最优线程池大小 - * 考虑仓库数量、CPU核心数和用户指定的并发度 - */ private static int calculateOptimalThreadPoolSize(int repoCount, int userConcurrency) { int cpuCores = Runtime.getRuntime().availableProcessors(); - - // 基于CPU核心数的推荐大小(通常为CPU核心数的1-2倍) int cpuBasedSize = cpuCores * 2; - - // 基于仓库数量的大小(不超过仓库数量) int repoBasedSize = Math.min(repoCount, cpuBasedSize); - - // 取用户指定并发度、CPU推荐大小和仓库数量的最小值,但至少为1 return Math.max(1, Math.min(userConcurrency, repoBasedSize)); } - // ==================== 仓库健康检测相关方法 ==================== + // ==================== 仓库健康检测 ==================== - /** - * 仓库健康检测结果 - */ public static class RepoHealthResult { private String repoPath; private boolean isHealthy; @@ -403,27 +322,19 @@ public class GitCommandExecutor { public String getCorruptionReason() { return corruptionReason; } } - /** - * 检查仓库健康状态 - * @param repoPath 仓库路径 - * @return 健康检测结果 - */ public static RepoHealthResult checkRepoHealth(String repoPath) { String normalizedPath = normalizePath(repoPath); File repoDir = new File(normalizedPath); - // 检查目录是否存在 if (!repoDir.exists()) { return new RepoHealthResult(repoPath, false, "仓库目录不存在"); } - // 检查.git目录是否存在 File gitDir = new File(normalizedPath + "/.git"); if (!gitDir.exists() || !gitDir.isDirectory()) { return new RepoHealthResult(repoPath, false, ".git目录缺失或损坏"); } - // 检查git rev-parse是否成功 try { CommandResult result = executeCommand(normalizedPath, "git rev-parse --git-dir"); if (!result.isSuccess()) { @@ -433,12 +344,10 @@ public class GitCommandExecutor { return new RepoHealthResult(repoPath, false, "执行git命令失败: " + e.getMessage()); } - // 检查git fsck是否成功 try { CommandResult result = executeCommand(normalizedPath, "git fsck --full"); if (!result.isSuccess()) { String errorMsg = result.getErrorMessage(); - // 如果有错误输出,说明仓库可能损坏 if (result.getErrorLines() != null && !result.getErrorLines().isEmpty()) { return new RepoHealthResult(repoPath, false, "fsck检测失败: " + String.join("; ", result.getErrorLines())); } @@ -448,7 +357,6 @@ public class GitCommandExecutor { return new RepoHealthResult(repoPath, false, "执行fsck失败: " + e.getMessage()); } - // 检查git status是否正常 try { CommandResult result = executeCommand(normalizedPath, "git status"); if (!result.isSuccess()) { @@ -461,11 +369,6 @@ public class GitCommandExecutor { return new RepoHealthResult(repoPath, true, null); } - /** - * 获取远程仓库URL - * @param repoPath 仓库路径 - * @return 远程仓库URL,失败返回null - */ public static String getRemoteUrl(String repoPath) { String normalizedPath = normalizePath(repoPath); try { @@ -475,10 +378,8 @@ public class GitCommandExecutor { return null; } - // 解析fetch行的URL(第一行通常是fetch) for (String line : result.getOutputLines()) { if (line.contains("(fetch)")) { - // 格式: origin http://xxx/xxx.git (fetch) String[] parts = line.trim().split("\\s+"); if (parts.length >= 2) { return parts[1]; @@ -486,7 +387,6 @@ public class GitCommandExecutor { } } - // 如果没有fetch,取第一行 return result.getOutputLines().get(0).trim().split("\\s+")[1]; } catch (Exception e) { @@ -495,20 +395,12 @@ public class GitCommandExecutor { } } - /** - * 克隆仓库 - * @param remoteUrl 远程仓库URL - * @param targetPath 目标路径 - * @param branch 分支名 - * @return 克隆结果 - */ public static CommandResult cloneRepo(String remoteUrl, String targetPath, String branch) { String normalizedTarget = normalizePath(targetPath); String command = String.format("git clone -b %s %s \"%s\"", branch, remoteUrl, normalizedTarget); log.info("执行克隆命令: {}", command); try { - // 先删除目标目录(如果存在) File targetDir = new File(normalizedTarget); if (targetDir.exists()) { deleteDirectory(targetDir); @@ -523,9 +415,6 @@ public class GitCommandExecutor { } } - /** - * 递归删除目录 - */ private static void deleteDirectory(File directory) { if (directory.exists()) { File[] files = directory.listFiles(); @@ -542,9 +431,6 @@ public class GitCommandExecutor { } } - /** - * 获取或创建线程池(单例模式,线程安全) - */ private static ExecutorService getOrCreateExecutorService() { if (executorService == null) { synchronized (threadPoolLock) { @@ -558,9 +444,6 @@ public class GitCommandExecutor { return executorService; } - /** - * 关闭线程池(用于应用程序关闭时清理资源) - */ public static void shutdownThreadPool() { synchronized (threadPoolLock) { if (executorService != null && !executorService.isShutdown()) { @@ -582,9 +465,6 @@ public class GitCommandExecutor { } } - /** - * 命令执行结果 - */ public static class CommandResult { private boolean success; private String errorMessage; @@ -598,15 +478,9 @@ public class GitCommandExecutor { this.errorMessage = errorMessage; } - public void addOutputLine(String line) { - outputLines.add(line); - } + public void addOutputLine(String line) { outputLines.add(line); } + public void addErrorLine(String line) { errorLines.add(line); } - public void addErrorLine(String line) { - errorLines.add(line); - } - - // Getters and setters public boolean isSuccess() { return success; } public void setSuccess(boolean success) { this.success = success; } public String getErrorMessage() { return errorMessage; } @@ -616,18 +490,10 @@ public class GitCommandExecutor { public List getErrorLines() { return errorLines; } public void setErrorLines(List errorLines) { this.errorLines = errorLines; } - public String getOutput() { - return String.join("\n", outputLines); - } - - public String getErrors() { - return String.join("\n", errorLines); - } + public String getOutput() { return String.join("\n", outputLines); } + public String getErrors() { return String.join("\n", errorLines); } } - /** - * 仓库执行结果 - */ public static class RepoResult { private String repoName; private String repoPath; @@ -641,7 +507,6 @@ public class GitCommandExecutor { this.repoPath = repoPath; } - // Getters and setters public String getRepoName() { return repoName; } public void setRepoName(String repoName) { this.repoName = repoName; } public String getRepoPath() { return repoPath; } @@ -655,4 +520,4 @@ public class GitCommandExecutor { public List getErrorLines() { return errorLines; } public void setErrorLines(List errorLines) { this.errorLines = errorLines; } } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/workutils/biz/git/util/GitRepositoryManager.java b/src/main/java/com/example/workutils/biz/git/internal/GitRepositoryManager.java similarity index 99% rename from src/main/java/com/example/workutils/biz/git/util/GitRepositoryManager.java rename to src/main/java/com/example/workutils/biz/git/internal/GitRepositoryManager.java index 443e2d3..fc8f016 100644 --- a/src/main/java/com/example/workutils/biz/git/util/GitRepositoryManager.java +++ b/src/main/java/com/example/workutils/biz/git/internal/GitRepositoryManager.java @@ -1,4 +1,4 @@ -package com.example.workutils.biz.git.util; +package com.example.workutils.biz.git.internal; import lombok.extern.slf4j.Slf4j; @@ -685,4 +685,4 @@ public class GitRepositoryManager { log.info("Results saved to: {} (Mode: {}, Success: {}/{})", filePath, isConcurrent ? "Concurrent" : "Sequential", successCount, results.size()); } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/workutils/biz/git/util/GitResultCache.java b/src/main/java/com/example/workutils/biz/git/internal/GitResultCache.java similarity index 99% rename from src/main/java/com/example/workutils/biz/git/util/GitResultCache.java rename to src/main/java/com/example/workutils/biz/git/internal/GitResultCache.java index 2fd06eb..e71d219 100644 --- a/src/main/java/com/example/workutils/biz/git/util/GitResultCache.java +++ b/src/main/java/com/example/workutils/biz/git/internal/GitResultCache.java @@ -1,4 +1,4 @@ -package com.example.workutils.biz.git.util; +package com.example.workutils.biz.git.internal; import lombok.extern.slf4j.Slf4j; @@ -265,4 +265,4 @@ public class GitResultCache { clearCache(); log.info("Git result cache shutdown completed"); } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/workutils/biz/git/service/impl/strategy/CheckBranchChangesStrategy.java b/src/main/java/com/example/workutils/biz/git/internal/strategy/CheckBranchChangesStrategy.java similarity index 79% rename from src/main/java/com/example/workutils/biz/git/service/impl/strategy/CheckBranchChangesStrategy.java rename to src/main/java/com/example/workutils/biz/git/internal/strategy/CheckBranchChangesStrategy.java index 38a5925..10a2605 100644 --- a/src/main/java/com/example/workutils/biz/git/service/impl/strategy/CheckBranchChangesStrategy.java +++ b/src/main/java/com/example/workutils/biz/git/internal/strategy/CheckBranchChangesStrategy.java @@ -1,9 +1,8 @@ -package com.example.workutils.biz.git.service.impl.strategy; +package com.example.workutils.biz.git.internal.strategy; -import com.example.workutils.biz.git.model.GitCommandDTO; -import com.example.workutils.biz.git.service.GitCommandStrategy; -import com.example.workutils.biz.git.util.GitCommandExecutor; -import com.example.workutils.biz.git.util.GitRepositoryManager; +import com.example.workutils.biz.git.internal.GitCommandExecutor; +import com.example.workutils.biz.git.internal.GitRepositoryManager; +import com.example.workutils.biz.git.model.GitCommand; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -20,26 +19,19 @@ import java.util.stream.Collectors; @Slf4j public class CheckBranchChangesStrategy implements GitCommandStrategy { - /** - * 统一路径格式处理方法 - */ private String normalizePath(String path) { if (path == null || path.trim().isEmpty()) { return path; } - // 统一使用正斜杠,Git在Windows下也能正确处理正斜杠路径 return path.replace("\\", "/"); } - /** - * 安全的路径拼接方法 - */ private String joinPath(String basePath, String relativePath) { return normalizePath(basePath) + "/" + relativePath; } @Override - public void execute(GitCommandDTO commandDTO) { + public void execute(GitCommand commandDTO) { System.out.println("正在检查分支变更情况..."); System.out.println("仓库根目录: " + commandDTO.getRepoRoot()); System.out.println("分支对: " + commandDTO.getBranchPairs()); @@ -54,7 +46,6 @@ public class CheckBranchChangesStrategy implements GitCommandStrategy { String sourceBranch = branchPair[0].trim(); String targetBranch = branchPair[1].trim(); - // 获取所有仓库 List repositories = GitRepositoryManager.getGitRepositories(commandDTO.getRepoRoot()); if (repositories.isEmpty()) { @@ -64,24 +55,19 @@ public class CheckBranchChangesStrategy implements GitCommandStrategy { System.out.println("找到 " + repositories.size() + " 个仓库,开始检查..."); - // 执行分支比较 List results = repositories.stream() .map(repoName -> { - // 判断根目录本身是否是Git仓库 File rootGitDir = new File(normalizePath(commandDTO.getRepoRoot()), ".git"); String repoPath; if (rootGitDir.exists() && rootGitDir.isDirectory()) { - // 根目录本身就是Git仓库 repoPath = normalizePath(commandDTO.getRepoRoot()); } else { - // 子目录是Git仓库 - 使用安全的路径拼接 repoPath = joinPath(commandDTO.getRepoRoot(), repoName); } GitCommandExecutor.RepoResult result = new GitCommandExecutor.RepoResult(repoName, repoPath); try { - // 比较两个分支 GitCommandExecutor.CommandResult compareResult = GitRepositoryManager.compareBranches( repoPath, sourceBranch, targetBranch); @@ -89,7 +75,6 @@ public class CheckBranchChangesStrategy implements GitCommandStrategy { result.setOutputLines(compareResult.getOutputLines()); result.setErrorLines(compareResult.getErrorLines()); result.setErrorMessage(compareResult.getErrorMessage()); - } catch (Exception e) { result.setSuccess(false); result.setErrorMessage("比较分支失败: " + e.getMessage()); @@ -99,12 +84,10 @@ public class CheckBranchChangesStrategy implements GitCommandStrategy { }) .collect(Collectors.toList()); - // 保存结果到文件 String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); String outputFile = "logs/branch_changes_" + timestamp + ".txt"; GitRepositoryManager.saveResultsToFile(outputFile, results, "分支变更检查结果", true); - // 显示简要结果 System.out.println("\n=== 检查结果摘要 ==="); long successCount = results.stream().mapToLong(r -> r.isSuccess() ? 1 : 0).sum(); System.out.println("总仓库数: " + results.size()); @@ -119,7 +102,7 @@ public class CheckBranchChangesStrategy implements GitCommandStrategy { } @Override - public GitCommandDTO.GitCommandType getSupportedCommandType() { - return GitCommandDTO.GitCommandType.CHECK_BRANCH_CHANGES; + public GitCommand.GitCommandType getSupportedCommandType() { + return GitCommand.GitCommandType.CHECK_BRANCH_CHANGES; } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/workutils/biz/git/service/impl/strategy/CheckoutAndPullStrategy.java b/src/main/java/com/example/workutils/biz/git/internal/strategy/CheckoutAndPullStrategy.java similarity index 73% rename from src/main/java/com/example/workutils/biz/git/service/impl/strategy/CheckoutAndPullStrategy.java rename to src/main/java/com/example/workutils/biz/git/internal/strategy/CheckoutAndPullStrategy.java index 2c992b0..acf3bee 100644 --- a/src/main/java/com/example/workutils/biz/git/service/impl/strategy/CheckoutAndPullStrategy.java +++ b/src/main/java/com/example/workutils/biz/git/internal/strategy/CheckoutAndPullStrategy.java @@ -1,9 +1,8 @@ -package com.example.workutils.biz.git.service.impl.strategy; +package com.example.workutils.biz.git.internal.strategy; -import com.example.workutils.biz.git.model.GitCommandDTO; -import com.example.workutils.biz.git.service.GitCommandStrategy; -import com.example.workutils.biz.git.util.GitCommandExecutor; -import com.example.workutils.biz.git.util.GitRepositoryManager; +import com.example.workutils.biz.git.internal.GitCommandExecutor; +import com.example.workutils.biz.git.internal.GitRepositoryManager; +import com.example.workutils.biz.git.model.GitCommand; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -13,35 +12,26 @@ import java.time.format.DateTimeFormatter; import java.util.List; import java.util.stream.Collectors; - /** - * 切换并拉取分支策略 - 并发优化版本 - */ - @Component - @Slf4j - public class CheckoutAndPullStrategy implements GitCommandStrategy { - - private static final int DEFAULT_CONCURRENCY = 8; // 默认并发度 - - /** - * 统一路径格式处理方法 - */ - private String normalizePath(String path) { - if (path == null || path.trim().isEmpty()) { - return path; - } - // 统一使用正斜杠,Git在Windows下也能正确处理正斜杠路径 - return path.replace("\\", "/"); - } - - /** - * 安全的路径拼接方法 - */ - private String joinPath(String basePath, String relativePath) { - return normalizePath(basePath) + "/" + relativePath; +/** + * 切换并拉取分支策略 + */ +@Component +@Slf4j +public class CheckoutAndPullStrategy implements GitCommandStrategy { + + private String normalizePath(String path) { + if (path == null || path.trim().isEmpty()) { + return path; } + return path.replace("\\", "/"); + } + + private String joinPath(String basePath, String relativePath) { + return normalizePath(basePath) + "/" + relativePath; + } @Override - public void execute(GitCommandDTO commandDTO) { + public void execute(GitCommand commandDTO) { System.out.println("正在切换并拉取最新代码..."); System.out.println("仓库根目录: " + commandDTO.getRepoRoot()); System.out.println("目标分支: " + commandDTO.getTargetBranch()); @@ -49,7 +39,6 @@ import java.util.stream.Collectors; try { String targetBranch = commandDTO.getTargetBranch().trim(); - // 获取所有仓库 List repositories = GitRepositoryManager.getGitRepositories(commandDTO.getRepoRoot()); if (repositories.isEmpty()) { @@ -59,31 +48,25 @@ import java.util.stream.Collectors; System.out.println("找到 " + repositories.size() + " 个仓库,开始切换并拉取..."); - // 切换并拉取每个仓库的分支 List results = repositories.stream() .map(repoName -> { - // 判断根目录本身是否是Git仓库 File rootGitDir = new File(normalizePath(commandDTO.getRepoRoot()), ".git"); String repoPath; if (rootGitDir.exists() && rootGitDir.isDirectory()) { - // 根目录本身就是Git仓库 repoPath = normalizePath(commandDTO.getRepoRoot()); } else { - // 子目录是Git仓库 - 使用安全的路径拼接 repoPath = joinPath(commandDTO.getRepoRoot(), repoName); } GitCommandExecutor.RepoResult result = new GitCommandExecutor.RepoResult(repoName, repoPath); try { - // 检查是否有未提交的更改 if (GitRepositoryManager.hasUncommittedChanges(repoPath)) { result.setSuccess(false); result.setErrorMessage("仓库存在未提交的更改,请先提交或暂存"); return result; } - // 执行分支切换并拉取 GitCommandExecutor.CommandResult pullResult = GitRepositoryManager.checkoutAndPull(repoPath, targetBranch); @@ -92,7 +75,6 @@ import java.util.stream.Collectors; result.setErrorLines(pullResult.getErrorLines()); result.setErrorMessage(pullResult.getErrorMessage()); - // 验证操作是否成功 if (pullResult.isSuccess()) { String currentBranch = GitRepositoryManager.getCurrentBranch(repoPath); if (targetBranch.equals(currentBranch)) { @@ -106,7 +88,6 @@ import java.util.stream.Collectors; result.setErrorMessage("分支切换验证失败,当前分支: " + currentBranch); } } - } catch (Exception e) { result.setSuccess(false); result.setErrorMessage("切换并拉取分支失败: " + e.getMessage()); @@ -116,12 +97,10 @@ import java.util.stream.Collectors; }) .collect(Collectors.toList()); - // 保存结果到文件 String timestamp = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss")); String outputFile = "logs/checkout_and_pull_" + timestamp + ".txt"; GitRepositoryManager.saveResultsToFile(outputFile, results, "分支切换并拉取结果", true); - // 显示简要结果 System.out.println("\n=== 操作结果摘要 ==="); long successCount = results.stream().mapToLong(r -> r.isSuccess() ? 1 : 0).sum(); System.out.println("总仓库数: " + results.size()); @@ -129,7 +108,6 @@ import java.util.stream.Collectors; System.out.println("操作失败: " + (results.size() - successCount)); System.out.println("详细结果已保存到: " + outputFile); - // 显示失败的仓库 List failedResults = results.stream() .filter(r -> !r.isSuccess()) .collect(Collectors.toList()); @@ -148,7 +126,7 @@ import java.util.stream.Collectors; } @Override - public GitCommandDTO.GitCommandType getSupportedCommandType() { - return GitCommandDTO.GitCommandType.CHECKOUT_AND_PULL; + public GitCommand.GitCommandType getSupportedCommandType() { + return GitCommand.GitCommandType.CHECKOUT_AND_PULL; } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/workutils/biz/git/internal/strategy/GitCommandStrategy.java b/src/main/java/com/example/workutils/biz/git/internal/strategy/GitCommandStrategy.java new file mode 100644 index 0000000..e7b065b --- /dev/null +++ b/src/main/java/com/example/workutils/biz/git/internal/strategy/GitCommandStrategy.java @@ -0,0 +1,13 @@ +package com.example.workutils.biz.git.internal.strategy; + +import com.example.workutils.biz.git.model.GitCommand; + +/** + * Git 命令策略接口 + */ +public interface GitCommandStrategy { + + void execute(GitCommand commandDTO); + + GitCommand.GitCommandType getSupportedCommandType(); +} diff --git a/src/main/java/com/example/workutils/biz/git/service/impl/GitStrategyFactory.java b/src/main/java/com/example/workutils/biz/git/internal/strategy/GitStrategyFactory.java similarity index 61% rename from src/main/java/com/example/workutils/biz/git/service/impl/GitStrategyFactory.java rename to src/main/java/com/example/workutils/biz/git/internal/strategy/GitStrategyFactory.java index 836e6e6..a1121af 100644 --- a/src/main/java/com/example/workutils/biz/git/service/impl/GitStrategyFactory.java +++ b/src/main/java/com/example/workutils/biz/git/internal/strategy/GitStrategyFactory.java @@ -1,8 +1,6 @@ -package com.example.workutils.biz.git.service.impl; +package com.example.workutils.biz.git.internal.strategy; -import com.example.workutils.biz.git.model.GitCommandDTO; -import com.example.workutils.biz.git.service.GitCommandStrategy; -import com.example.workutils.biz.git.service.impl.strategy.*; +import com.example.workutils.biz.git.model.GitCommand; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -17,18 +15,14 @@ import java.util.Set; @Slf4j public class GitStrategyFactory { - private final Map strategyMap = new HashMap<>(); + private final Map strategyMap = new HashMap<>(); public GitStrategyFactory() { - // 初始化所有策略 registerStrategy(new CheckBranchChangesStrategy()); registerStrategy(new CheckoutAndPullStrategy()); log.debug("Git策略工厂已初始化,共注册{}个策略", strategyMap.size()); } - /** - * 注册策略 - */ private void registerStrategy(GitCommandStrategy strategy) { strategyMap.put(strategy.getSupportedCommandType(), strategy); log.debug("注册Git策略: {} -> {}", @@ -36,10 +30,7 @@ public class GitStrategyFactory { strategy.getClass().getSimpleName()); } - /** - * 获取策略 - */ - public GitCommandStrategy getStrategy(GitCommandDTO.GitCommandType commandType) { + public GitCommandStrategy getStrategy(GitCommand.GitCommandType commandType) { GitCommandStrategy strategy = strategyMap.get(commandType); if (strategy == null) { throw new IllegalArgumentException("不支持的Git命令类型: " + commandType); @@ -47,18 +38,12 @@ public class GitStrategyFactory { return strategy; } - /** - * 执行Git命令 - */ - public void executeCommand(GitCommandDTO commandDTO) { + public void executeCommand(GitCommand commandDTO) { GitCommandStrategy strategy = getStrategy(commandDTO.getCommandType()); strategy.execute(commandDTO); } - /** - * 获取所有支持的命令类型 - */ - public Set getSupportedCommandTypes() { + public Set getSupportedCommandTypes() { return strategyMap.keySet(); } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/workutils/biz/git/model/GitCommandDTO.java b/src/main/java/com/example/workutils/biz/git/model/GitCommand.java similarity index 91% rename from src/main/java/com/example/workutils/biz/git/model/GitCommandDTO.java rename to src/main/java/com/example/workutils/biz/git/model/GitCommand.java index 3d43682..a001ddb 100644 --- a/src/main/java/com/example/workutils/biz/git/model/GitCommandDTO.java +++ b/src/main/java/com/example/workutils/biz/git/model/GitCommand.java @@ -6,7 +6,7 @@ import lombok.Data; * Git 命令传输对象 */ @Data -public class GitCommandDTO { +public class GitCommand { private String repoRoot; private String targetBranch; @@ -15,8 +15,8 @@ public class GitCommandDTO { private String scriptPath; private GitCommandType commandType; - public static GitCommandDTO create(String repoRoot, String value, GitCommandType type) { - GitCommandDTO dto = new GitCommandDTO(); + public static GitCommand create(String repoRoot, String value, GitCommandType type) { + GitCommand dto = new GitCommand(); dto.setRepoRoot(repoRoot); dto.setCommandType(type); switch (type) { @@ -56,4 +56,4 @@ public class GitCommandDTO { CHECKOUT_AND_MERGE, CHECKOUT_AND_PULL } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/workutils/biz/git/service/GitCommandStrategy.java b/src/main/java/com/example/workutils/biz/git/service/GitCommandStrategy.java deleted file mode 100644 index 8384120..0000000 --- a/src/main/java/com/example/workutils/biz/git/service/GitCommandStrategy.java +++ /dev/null @@ -1,19 +0,0 @@ -package com.example.workutils.biz.git.service; - -import com.example.workutils.biz.git.model.GitCommandDTO; - -/** - * Git 命令策略接口 - */ -public interface GitCommandStrategy { - - /** - * 执行 Git 命令 - */ - void execute(GitCommandDTO commandDTO); - - /** - * 获取支持的命令类型 - */ - GitCommandDTO.GitCommandType getSupportedCommandType(); -} \ No newline at end of file diff --git a/src/main/java/com/example/workutils/biz/git/service/GitService.java b/src/main/java/com/example/workutils/biz/git/service/GitService.java deleted file mode 100644 index 7b3b95d..0000000 --- a/src/main/java/com/example/workutils/biz/git/service/GitService.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.example.workutils.biz.git.service; - -import com.example.workutils.biz.git.util.GitCommandExecutor; - -import java.util.List; -import java.util.Scanner; - -/** - * Git 服务接口 - */ -public interface GitService { - - /** - * 检查分支变更情况 - */ - void checkBranchChanges(String repoRoot, String branchPairs); - - /** - * 切换并拉取最新代码 - */ - void checkoutAndPull(String repoRoot, String targetBranch); - - /** - * 检测所有仓库健康状态 - * @return 损坏的仓库列表 - */ - List checkCorruptedRepos(); - - /** - * 检测所有仓库健康状态(支持用户中断) - * @param scanner 用户输入扫描器(可为空) - * @return 损坏的仓库列表 - */ - List checkCorruptedRepos(Scanner scanner); - - /** - * 修复损坏的仓库(删除并重新克隆master分支) - * @param repoPath 仓库路径 - * @return 是否修复成功 - */ - boolean fixCorruptedRepo(String repoPath); - - /** - * 获取损坏仓库列表(从配置文件中读取标记为CORRUPTED的) - * @return 损坏仓库路径列表 - */ - List listCorruptedRepos(); - -} \ No newline at end of file diff --git a/src/main/java/com/example/workutils/biz/mybatis/export/MyBatisExportService.java b/src/main/java/com/example/workutils/biz/mybatis/MyBatisExportService.java similarity index 61% rename from src/main/java/com/example/workutils/biz/mybatis/export/MyBatisExportService.java rename to src/main/java/com/example/workutils/biz/mybatis/MyBatisExportService.java index 4a80ce8..f78fece 100644 --- a/src/main/java/com/example/workutils/biz/mybatis/export/MyBatisExportService.java +++ b/src/main/java/com/example/workutils/biz/mybatis/MyBatisExportService.java @@ -1,9 +1,9 @@ -package com.example.workutils.biz.mybatis.export; +package com.example.workutils.biz.mybatis; -import com.example.workutils.biz.scanner.config.ScanConfigBO; -import com.example.workutils.biz.mybatis.model.MybatisTagBO; -import com.example.workutils.biz.output.OutputService; -import com.example.workutils.biz.scanner.service.FileOperationService; +import com.example.workutils.biz.mybatis.model.MybatisTag; +import com.example.workutils.biz.scanner.config.ScanConfig; +import com.example.workutils.system.support.FileOperationService; +import com.example.workutils.system.support.OutputService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -19,127 +19,71 @@ import java.util.regex.Pattern; /** * MyBatis SQL导出服务 * 负责从MyBatis XML文件中提取SQL并按各种条件导出 - * - * @author system - * @version 1.0 */ @Service @Slf4j public class MyBatisExportService { - // ==================== 依赖注入 ==================== - @Autowired private FileOperationService fileOperationService; @Autowired private OutputService outputService; - // ==================== 常量定义 ==================== - - /** SQL标签类型 */ private static final String[] SQL_TAG_TYPES = {"select", "insert", "update", "delete"}; - - /** 导出文件的表头 */ + private static final String[] EXPORT_HEADERS = { "项目名", "文件路径", "SQL ID", "SQL类型", "原始SQL文本" }; - - /** 表名匹配模式 - 匹配FROM、JOIN、UPDATE、INSERT INTO、DELETE FROM等SQL语句中的表名 */ + private static final String[] TABLE_PATTERNS = { "(?i)\\bFROM\\s+([`\"']?)(\\w+)\\1", - "(?i)\\bJOIN\\s+([`\"']?)(\\w+)\\1", + "(?i)\\bJOIN\\s+([`\"']?)(\\w+)\\1", "(?i)\\bUPDATE\\s+([`\"']?)(\\w+)\\1", "(?i)\\bINSERT\\s+INTO\\s+([`\"']?)(\\w+)\\1", "(?i)\\bDELETE\\s+FROM\\s+([`\"']?)(\\w+)\\1", "(?i)\\bMERGE\\s+INTO\\s+([`\"']?)(\\w+)\\1" }; - // ==================== 公共接口方法 ==================== - - /** - * 导出所有SQL - * - * @throws Exception 导出异常 - */ public void exportSQL() throws Exception { exportWithFilter("SQL导出", null, null); } - - /** - * 按表名模式导出SQL - * - * @param tableNamePattern 表名正则表达式模式 - * @throws Exception 导出异常 - */ + public void exportSQLByTable(String tableNamePattern) throws Exception { - exportWithFilter("表名SQL清单_" + tableNamePattern, + exportWithFilter("表名SQL清单_" + tableNamePattern, new TableFilter(tableNamePattern), null); } - - /** - * 按多个表名模式导出SQL - * - * @param tableNamePatterns 表名模式列表 - * @throws Exception 导出异常 - */ + public void exportSQLByTables(List tableNamePatterns) throws Exception { if (tableNamePatterns == null || tableNamePatterns.isEmpty()) { log.warn("未提供表名模式"); return; } - - // 将多个表名模式合并为一个正则表达式 String combinedPattern = String.join("|", tableNamePatterns.stream() .map(pattern -> "(" + pattern + ")") .toArray(String[]::new)); - - exportWithFilter("多表SQL清单_" + String.join("_", tableNamePatterns), + exportWithFilter("多表SQL清单_" + String.join("_", tableNamePatterns), new TableFilter(combinedPattern), null); } - - /** - * 按关键字导出SQL - * - * @param keywordPattern 关键字正则表达式模式 - * @throws Exception 导出异常 - */ + public void exportSQLByKeyword(String keywordPattern) throws Exception { - exportWithFilter("关键字SQL清单_" + keywordPattern, + exportWithFilter("关键字SQL清单_" + keywordPattern, new KeywordFilter(keywordPattern), null); } - - /** - * 按SQL标签ID导出SQL - * - * @param tagIdPattern 标签ID正则表达式模式 - * @throws Exception 导出异常 - */ + public void exportSQLByTagId(String tagIdPattern) throws Exception { - exportWithFilter("标签ID SQL清单_" + tagIdPattern, + exportWithFilter("标签ID SQL清单_" + tagIdPattern, new TagIdFilter(tagIdPattern), null); } - // ==================== 核心导出逻辑 ==================== - - /** - * 使用过滤器导出SQL - * - * @param fileNamePrefix 文件名前缀 - * @param filter SQL过滤器 - * @param processStrategy 处理策略 - * @throws Exception 导出异常 - */ - private void exportWithFilter(String fileNamePrefix, SqlFilter filter, + private void exportWithFilter(String fileNamePrefix, SqlFilter filter, ProcessStrategy processStrategy) throws Exception { - // 获取所有项目路径 List projectPaths = fileOperationService.getProjectPaths(); if (projectPaths.isEmpty()) { log.warn("未找到项目路径"); return; } - // 处理所有项目 List allRecords = new ArrayList<>(); AtomicInteger processedCount = new AtomicInteger(0); @@ -154,39 +98,26 @@ public class MyBatisExportService { } } - // 输出到文件 String fileName = outputService.generateOutputFileName(fileNamePrefix, "csv"); outputService.writeRecordsToCsv(allRecords, fileName, Arrays.asList(EXPORT_HEADERS)); - log.info("导出完成。已处理 {} 个项目,共 {} 条记录", + log.info("导出完成。已处理 {} 个项目,共 {} 条记录", processedCount.get(), allRecords.size()); } - - /** - * 处理单个项目 - * - * @param projectPath 项目路径 - * @param filter SQL过滤器 - * @param processStrategy 处理策略 - * @return 处理结果记录列表 - * @throws Exception 处理异常 - */ - private List processProject(String projectPath, SqlFilter filter, + + private List processProject(String projectPath, SqlFilter filter, ProcessStrategy processStrategy) throws Exception { List records = new ArrayList<>(); String projectName = fileOperationService.extractProjectName(projectPath); - // 扫描XML文件 - ScanConfigBO config = ScanConfigBO.forXmlFiles(); + ScanConfig config = ScanConfig.forXmlFiles(); List xmlFiles = fileOperationService.scanFiles(projectPath, config); for (Path xmlFile : xmlFiles) { try { - List tags = parseXmlFile(xmlFile); - for (MybatisTagBO tag : tags) { - // 应用过滤器 + List tags = parseXmlFile(xmlFile); + for (MybatisTag tag : tags) { if (filter == null || filter.matches(tag)) { - // 应用处理策略 if (processStrategy == null || processStrategy.process(tag)) { String[] record = buildRecord(tag, projectName, xmlFile); records.add(record); @@ -201,69 +132,38 @@ public class MyBatisExportService { return records; } - // ==================== XML解析相关方法 ==================== - - /** - * 解析XML文件,提取SQL标签 - * - * @param xmlFile XML文件路径 - * @return SQL标签列表 - * @throws Exception 解析异常 - */ - private List parseXmlFile(Path xmlFile) throws Exception { - List tags = new ArrayList<>(); + private List parseXmlFile(Path xmlFile) throws Exception { + List tags = new ArrayList<>(); String content = fileOperationService.readFileContent(xmlFile); - // 解析各种SQL标签 for (String tagType : SQL_TAG_TYPES) { tags.addAll(parseSqlTags(content, tagType)); } return tags; } - - /** - * 解析指定类型的SQL标签 - * - * @param content XML内容 - * @param tagName 标签名称 - * @return 解析到的标签列表 - */ - private List parseSqlTags(String content, String tagName) { - List tags = new ArrayList<>(); + + private List parseSqlTags(String content, String tagName) { + List tags = new ArrayList<>(); String regex = "<" + tagName + "\\b[^>]*>(.*?)"; Pattern pattern = Pattern.compile(regex, Pattern.DOTALL | Pattern.CASE_INSENSITIVE); Matcher matcher = pattern.matcher(content); while (matcher.find()) { - MybatisTagBO tag = new MybatisTagBO(); + MybatisTag tag = new MybatisTag(); String tagContent = matcher.group(0); - - // 设置基本信息 tag.setTagName(tagName); tag.setTagOriginalXmlText(tagContent); - - // 提取ID extractTagAttribute(tagContent, "id", tag::setTagId); - - // 提取parameterType extractTagAttribute(tagContent, "parameterType", tag::setParameterType); - tags.add(tag); } return tags; } - - /** - * 从标签内容中提取指定属性 - * - * @param tagContent 标签内容 - * @param attributeName 属性名称 - * @param setter 属性设置器 - */ - private void extractTagAttribute(String tagContent, String attributeName, + + private void extractTagAttribute(String tagContent, String attributeName, java.util.function.Consumer setter) { String regex = attributeName + "\\s*=\\s*[\"']([^\"']+)[\"']"; Pattern pattern = Pattern.compile(regex, Pattern.CASE_INSENSITIVE); @@ -273,17 +173,7 @@ public class MyBatisExportService { } } - // ==================== 记录构建 ==================== - - /** - * 构建导出记录 - * - * @param tag SQL标签 - * @param projectName 项目名称 - * @param xmlFile XML文件路径 - * @return 导出记录数组 - */ - private String[] buildRecord(MybatisTagBO tag, String projectName, Path xmlFile) { + private String[] buildRecord(MybatisTag tag, String projectName, Path xmlFile) { return new String[] { projectName, xmlFile.toString(), @@ -293,115 +183,85 @@ public class MyBatisExportService { }; } - // ==================== 过滤器接口和实现 ==================== - - /** - * SQL过滤器接口 - */ + // ==================== 过滤器 ==================== + private interface SqlFilter { - boolean matches(MybatisTagBO tag); + boolean matches(MybatisTag tag); } - - /** - * 表名过滤器 - */ + private static class TableFilter implements SqlFilter { private final Pattern tablePattern; - + public TableFilter(String pattern) { this.tablePattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); } - + @Override - public boolean matches(MybatisTagBO tag) { + public boolean matches(MybatisTag tag) { if (tag == null || tag.getTagOriginalXmlText() == null) { return false; } - String sqlText = removeSqlComments(tag.getTagOriginalXmlText()); - for (String pattern : TABLE_PATTERNS) { Pattern p = Pattern.compile(pattern); Matcher m = p.matcher(sqlText); while (m.find()) { - String tableName = m.group(2); // 获取表名组 + String tableName = m.group(2); if (tableName != null && tablePattern.matcher(tableName).matches()) { return true; } } } - return false; } - - /** - * 移除SQL注释 - * - * @param sqlText SQL文本 - * @return 移除注释后的SQL文本 - */ + private String removeSqlComments(String sqlText) { String cleanText = sqlText.replaceAll("--.*", ""); cleanText = cleanText.replaceAll("/\\*.*?\\*/", ""); return cleanText; } } - - /** - * 关键字过滤器 - */ + private static class KeywordFilter implements SqlFilter { private final Pattern keywordPattern; - + public KeywordFilter(String pattern) { this.keywordPattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); } - + @Override - public boolean matches(MybatisTagBO tag) { + public boolean matches(MybatisTag tag) { if (tag == null || tag.getTagOriginalXmlText() == null) { return false; } - String cleanSqlText = removeSqlComments(tag.getTagOriginalXmlText()); - Matcher matcher = keywordPattern.matcher(cleanSqlText); - return matcher.find(); + return keywordPattern.matcher(cleanSqlText).find(); } - + private String removeSqlComments(String sqlText) { String cleanText = sqlText.replaceAll("--.*", ""); cleanText = cleanText.replaceAll("/\\*.*?\\*/", ""); return cleanText; } } - - /** - * 标签ID过滤器 - */ + private static class TagIdFilter implements SqlFilter { private final Pattern tagIdPattern; - + public TagIdFilter(String pattern) { this.tagIdPattern = Pattern.compile(pattern, Pattern.CASE_INSENSITIVE); } - + @Override - public boolean matches(MybatisTagBO tag) { + public boolean matches(MybatisTag tag) { if (tag == null || tag.getTagId() == null) { return false; } - - Matcher matcher = tagIdPattern.matcher(tag.getTagId()); - return matcher.matches(); + return tagIdPattern.matcher(tag.getTagId()).matches(); } } - // ==================== 处理策略接口 ==================== - - /** - * 处理策略接口(预留扩展) - */ private interface ProcessStrategy { - boolean process(MybatisTagBO tag); + boolean process(MybatisTag tag); } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/workutils/biz/mybatis/menu/MyBatisMenu.java b/src/main/java/com/example/workutils/biz/mybatis/MyBatisExportTool.java similarity index 86% rename from src/main/java/com/example/workutils/biz/mybatis/menu/MyBatisMenu.java rename to src/main/java/com/example/workutils/biz/mybatis/MyBatisExportTool.java index 384d81e..8cba9df 100644 --- a/src/main/java/com/example/workutils/biz/mybatis/menu/MyBatisMenu.java +++ b/src/main/java/com/example/workutils/biz/mybatis/MyBatisExportTool.java @@ -1,6 +1,5 @@ -package com.example.workutils.biz.mybatis.menu; +package com.example.workutils.biz.mybatis; -import com.example.workutils.biz.mybatis.export.MyBatisExportService; import com.example.workutils.system.tool.Tool; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -10,13 +9,12 @@ import java.util.Arrays; import java.util.Scanner; /** - * MyBatis SQL导出菜单 - * 仅负责菜单展示和用户交互,业务逻辑委托给MyBatisExportService + * MyBatis SQL导出工具入口(符合 Tool 规范) */ @Slf4j @Component @RequiredArgsConstructor -public class MyBatisMenu implements Tool { +public class MyBatisExportTool implements Tool { private static final String[] MENU_OPTIONS = { "1. 导出所有SQL", @@ -87,24 +85,12 @@ public class MyBatisMenu implements Tool { @Override public void handleChoice(int choice, Scanner scanner) throws Exception { switch (choice) { - case 1: - exportAllSql(); - break; - case 2: - exportByTableName(scanner); - break; - case 3: - exportByTableNames(scanner); - break; - case 4: - exportByKeyword(scanner); - break; - case 5: - exportByTagId(scanner); - break; - default: - printInvalidOption(); - break; + case 1: exportAllSql(); break; + case 2: exportByTableName(scanner); break; + case 3: exportByTableNames(scanner); break; + case 4: exportByKeyword(scanner); break; + case 5: exportByTagId(scanner); break; + default: printInvalidOption(); break; } } diff --git a/src/main/java/com/example/workutils/biz/mybatis/menu/MyBatisGeneratorMenu.java b/src/main/java/com/example/workutils/biz/mybatis/MyBatisGeneratorTool.java similarity index 78% rename from src/main/java/com/example/workutils/biz/mybatis/menu/MyBatisGeneratorMenu.java rename to src/main/java/com/example/workutils/biz/mybatis/MyBatisGeneratorTool.java index acdfe28..685cfb6 100644 --- a/src/main/java/com/example/workutils/biz/mybatis/menu/MyBatisGeneratorMenu.java +++ b/src/main/java/com/example/workutils/biz/mybatis/MyBatisGeneratorTool.java @@ -1,4 +1,4 @@ -package com.example.workutils.biz.mybatis.menu; +package com.example.workutils.biz.mybatis; import com.example.workutils.biz.mybatis.generator.MyBatisGeneratorService; import com.example.workutils.system.tool.Tool; @@ -11,13 +11,12 @@ import java.util.List; import java.util.Scanner; /** - * MyBatis逆向工程菜单 - * 仅负责菜单展示和用户交互,业务逻辑委托给MyBatisGeneratorService + * MyBatis逆向工程工具入口(符合 Tool 规范) */ @Slf4j @Component @RequiredArgsConstructor -public class MyBatisGeneratorMenu implements Tool { +public class MyBatisGeneratorTool implements Tool { private static final String[] MENU_OPTIONS = { "1. 根据表名生成(单表)", @@ -48,36 +47,19 @@ public class MyBatisGeneratorMenu implements Tool { @Override public void handleChoice(int choice, Scanner scanner) throws Exception { switch (choice) { - case 1: - generateBySingleTable(scanner); - break; - case 2: - generateByMultipleTables(scanner); - break; - case 3: - generateByPattern(scanner); - break; - case 4: - showAllTables(); - break; - case 5: - showConfiguration(); - break; - default: - printInvalidOption(); - break; + case 1: generateBySingleTable(scanner); break; + case 2: generateByMultipleTables(scanner); break; + case 3: generateByPattern(scanner); break; + case 4: showAllTables(); break; + case 5: showConfiguration(); break; + default: printInvalidOption(); break; } } private void generateBySingleTable(Scanner scanner) throws Exception { System.out.print("请输入表名: "); String tableName = scanner.nextLine().trim(); - - if (tableName.isEmpty()) { - System.out.println("表名不能为空"); - return; - } - + if (tableName.isEmpty()) { System.out.println("表名不能为空"); return; } log.info("开始生成表 [{}] 的Mapper文件", tableName); String result = generatorService.generateByTableNames(Arrays.asList(tableName), true); System.out.println(result); @@ -87,17 +69,11 @@ public class MyBatisGeneratorMenu implements Tool { private void generateByMultipleTables(Scanner scanner) throws Exception { System.out.print("请输入表名(多个用逗号分隔): "); String input = scanner.nextLine().trim(); - - if (input.isEmpty()) { - System.out.println("表名不能为空"); - return; - } - + if (input.isEmpty()) { System.out.println("表名不能为空"); return; } List tableNames = Arrays.asList(input.split(",")); for (int i = 0; i < tableNames.size(); i++) { tableNames.set(i, tableNames.get(i).trim()); } - log.info("开始生成 {} 个表的Mapper文件", tableNames.size()); String result = generatorService.generateByTableNames(tableNames, true); System.out.println(result); @@ -107,30 +83,19 @@ public class MyBatisGeneratorMenu implements Tool { private void generateByPattern(Scanner scanner) throws Exception { System.out.print("请输入表名通配符(如 user_%): "); String pattern = scanner.nextLine().trim(); - - if (pattern.isEmpty()) { - System.out.println("通配符不能为空"); - return; - } - + if (pattern.isEmpty()) { System.out.println("通配符不能为空"); return; } List matchedTables = generatorService.queryTablesByPattern(pattern); if (matchedTables.isEmpty()) { System.out.println("未找到匹配通配符 [" + pattern + "] 的表"); return; } - System.out.println("匹配到 " + matchedTables.size() + " 张表:"); for (int i = 0; i < matchedTables.size(); i++) { System.out.println(" " + (i + 1) + ". " + matchedTables.get(i)); } - System.out.print("是否继续生成?(y/n): "); String confirm = scanner.nextLine().trim().toLowerCase(); - if (!"y".equals(confirm)) { - System.out.println("已取消生成"); - return; - } - + if (!"y".equals(confirm)) { System.out.println("已取消生成"); return; } log.info("开始根据通配符 [{}] 生成 {} 张表的Mapper文件", pattern, matchedTables.size()); String result = generatorService.generateByTableNames(matchedTables, true); System.out.println(result); @@ -159,9 +124,7 @@ public class MyBatisGeneratorMenu implements Tool { } private String maskPassword(String url) { - if (url == null) { - return ""; - } + if (url == null) return ""; return url.replaceAll("password=[^&]*", "password=***"); } } diff --git a/src/main/java/com/example/workutils/biz/mybatis/model/MybatisTag.java b/src/main/java/com/example/workutils/biz/mybatis/model/MybatisTag.java new file mode 100644 index 0000000..93d4f90 --- /dev/null +++ b/src/main/java/com/example/workutils/biz/mybatis/model/MybatisTag.java @@ -0,0 +1,19 @@ +package com.example.workutils.biz.mybatis.model; + +import lombok.Data; + +/** + * MyBatis标签数据对象 + */ +@Data +public class MybatisTag { + + private String tagId; + private String tagName; + private String parameterType; + private String resultMap; + private String tagOriginalXmlText; + private String includeTagOriginalXmlText; + private String namespace; + private String simpleNamespace; +} diff --git a/src/main/java/com/example/workutils/biz/mybatis/model/MybatisTagBO.java b/src/main/java/com/example/workutils/biz/mybatis/model/MybatisTagBO.java deleted file mode 100644 index 2e4c866..0000000 --- a/src/main/java/com/example/workutils/biz/mybatis/model/MybatisTagBO.java +++ /dev/null @@ -1,63 +0,0 @@ -package com.example.workutils.biz.mybatis.model; - -import lombok.Data; - -/** - * MyBatis标签数据传输对象 - * 用于封装从MyBatis XML文件中解析的SQL标签信息 - * - * @author system - * @version 1.0 - */ -@Data -public class MybatisTagBO { - - /** - * 标签ID - * 对应MyBatis XML中SQL标签的id属性,用于唯一标识一个SQL语句 - */ - private String tagId; - - /** - * 标签名称 - * SQL标签类型,如select、insert、update、delete等 - */ - private String tagName; - - /** - * 参数类型 - * 对应MyBatis XML中SQL标签的parameterType属性,指定传入参数的Java类型 - */ - private String parameterType; - - /** - * 结果集映射ID - * 对应MyBatis XML中SQL标签的resultMap属性,指定结果集的映射规则 - */ - private String resultMap; - - /** - * 标签原始XML文本 - * 保留SQL标签的完整XML内容,包括标签、属性和SQL语句 - * 注意:保留原始格式,包括换行和缩进 - */ - private String tagOriginalXmlText; - - /** - * Include标签原始XML文本 - * 存储包含的SQL片段引用信息 - */ - private String includeTagOriginalXmlText; - - /** - * 命名空间 - * MyBatis Mapper接口的全限定名,对应XML的namespace属性 - */ - private String namespace; - - /** - * 简化命名空间 - * 命名空间的简化版本,通常只包含类名 - */ - private String simpleNamespace; -} diff --git a/src/main/java/com/example/workutils/biz/mybatis/service/XMLMapperChecker.java b/src/main/java/com/example/workutils/biz/mybatis/service/XMLMapperChecker.java deleted file mode 100644 index e6c5d51..0000000 --- a/src/main/java/com/example/workutils/biz/mybatis/service/XMLMapperChecker.java +++ /dev/null @@ -1,49 +0,0 @@ -package com.example.workutils.service; - -import org.w3c.dom.Document; -import org.w3c.dom.NodeList; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import java.io.File; - -public class XMLMapperChecker { - - public static String getErrorText(String filePath) { - try { - checkXMLMapper(filePath); - } catch (Throwable e) { - return e.getMessage(); - } - return null; - } - - private static void checkXMLMapper(String filePath) { - File xmlFile = new File(filePath); - if (xmlFile.length() == 0) { - throw new RuntimeException("文件为空"); - } - - DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); - try { - DocumentBuilder dBuilder = dbFactory.newDocumentBuilder(); - Document doc = dBuilder.parse(xmlFile); - doc.getDocumentElement().normalize(); - - System.out.println(filePath + "标签检查完毕,未发现问题"); - } catch (Exception e) { - throw new RuntimeException("标签缺失"); - } - } - - // 检查单个标签是否存在且唯一 - private static void checkSingleTag(Document doc, String tagName) { - NodeList nodeList = doc.getElementsByTagName(tagName); - if (nodeList.getLength() == 0) { - throw new RuntimeException("缺少<" + tagName + ">标签"); - } else if (nodeList.getLength() > 1) { - new RuntimeException("存在多个<" + tagName + ">标签"); - } - } - -} \ No newline at end of file diff --git a/src/main/java/com/example/workutils/biz/scanner/service/ScanService.java b/src/main/java/com/example/workutils/biz/scanner/ScannerService.java similarity index 77% rename from src/main/java/com/example/workutils/biz/scanner/service/ScanService.java rename to src/main/java/com/example/workutils/biz/scanner/ScannerService.java index a4a561b..0ea3e6f 100644 --- a/src/main/java/com/example/workutils/biz/scanner/service/ScanService.java +++ b/src/main/java/com/example/workutils/biz/scanner/ScannerService.java @@ -1,8 +1,9 @@ -package com.example.workutils.biz.scanner.service; +package com.example.workutils.biz.scanner; -import com.example.workutils.biz.scanner.config.ScanConfigBO; +import com.example.workutils.biz.scanner.config.ScanConfig; import com.example.workutils.system.config.WorkUtilsProperties; -import com.example.workutils.biz.output.OutputService; +import com.example.workutils.system.support.FileOperationService; +import com.example.workutils.system.support.OutputService; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -19,7 +20,7 @@ import java.util.regex.Pattern; @Service @Slf4j -public class ScanService { +public class ScannerService { private final ExecutorService executorService = Executors.newFixedThreadPool(10); @Autowired @@ -52,7 +53,6 @@ public class ScanService { futures.add(future); } - // 等待所有扫描完成 for (CompletableFuture future : futures) { ProjectScanResult projectResult = future.get(); result.addProjectResult(projectResult); @@ -65,7 +65,7 @@ public class ScanService { ProjectScanResult projectResult = new ProjectScanResult(projectPath, new ArrayList<>()); String projectName = fileOperationService.extractProjectName(projectPath); - ScanConfigBO config = getConfigForScanType(scanType); + ScanConfig config = getConfigForScanType(scanType); List files = fileOperationService.scanFiles(projectPath, config); for (Path file : files) { @@ -108,10 +108,7 @@ public class ScanService { match.setLineContent(line.trim()); match.setMatchText(matcher.group()); match.setScanType(scanType); - - // 根据扫描类型提取额外信息 extractExtraInfo(match, line, scanType); - matches.add(match); } } @@ -134,7 +131,6 @@ public class ScanService { } private void extractDubboInfo(MatchResult match, String line) { - // 提取Dubbo服务信息 if (line.contains("@Reference")) { match.setAdditionalInfo("Dubbo Reference"); } else if (line.contains("@Service")) { @@ -145,7 +141,6 @@ public class ScanService { } private void extractRedisInfo(MatchResult match, String line) { - // 提取Redis Key信息 Pattern redisPattern = Pattern.compile("redis\\.opsFor[^\\s]+\\s*\\.\\s*([^\\s(]+)"); Matcher matcher = redisPattern.matcher(line); if (matcher.find()) { @@ -154,31 +149,27 @@ public class ScanService { } private void extractKeywordInfo(MatchResult match, String line) { - // 提取关键词相关信息 match.setAdditionalInfo("关键词匹配"); } - private ScanConfigBO getConfigForScanType(ScanType scanType) { + private ScanConfig getConfigForScanType(ScanType scanType) { switch (scanType) { case DUBBO: - return ScanConfigBO.forJavaFiles(); + return ScanConfig.forJavaFiles(); case REDIS_KEY: - return ScanConfigBO.forJavaFiles(); + return ScanConfig.forJavaFiles(); case KEYWORD: - return ScanConfigBO.defaultConfig(); + return ScanConfig.defaultConfig(); default: - return ScanConfigBO.defaultConfig(); + return ScanConfig.defaultConfig(); } } public void exportScanResults(ScanResult result, String outputFileName) throws Exception { List records = new ArrayList<>(); - - // 添加表头 String[] headers = {"项目名", "文件路径", "行号", "匹配内容", "完整行内容", "附加信息", "扫描类型"}; records.add(headers); - // 添加数据 for (ProjectScanResult projectResult : result.getProjectResults()) { for (MatchResult match : projectResult.getMatches()) { String[] record = { @@ -194,14 +185,11 @@ public class ScanService { } } - // 输出到文件,确保目录存在 fileOperationService.ensureDirectoryExists(properties.getTodayExportPath()); outputService.writeRecordsToCsv(records, outputFileName); - log.info("Scan results exported to: {}", outputFileName); } - // 兼容旧版本的便捷方法 public void scanKeywords(String pattern, String outputFile) throws Exception { ScanResult result = scanAllProjects(ScanType.KEYWORD, pattern); exportScanResults(result, outputFile); @@ -217,33 +205,30 @@ public class ScanService { exportScanResults(result, outputFile); } - // 新增方法:使用默认文件名进行关键词扫描 public void scanKeywordsWithDefaultName(String pattern) throws Exception { ScanResult result = scanAllProjects(ScanType.KEYWORD, pattern); String defaultFileName = outputService.generateOutputFileName("keyword_scan_result", "csv"); exportScanResults(result, defaultFileName); } - // 新增方法:使用默认文件名进行Redis Key扫描 public void scanRedisKeysWithDefaultName(String pattern) throws Exception { ScanResult result = scanAllProjects(ScanType.REDIS_KEY, pattern); String defaultFileName = outputService.generateOutputFileName("redis_key_scan_result", "csv"); exportScanResults(result, defaultFileName); } - // 新增方法:使用默认文件名进行Dubbo接口扫描 public void scanDubboInterfacesWithDefaultName(String pattern) throws Exception { ScanResult result = scanAllProjects(ScanType.DUBBO, pattern); String defaultFileName = outputService.generateOutputFileName("dubbo_scan_result", "csv"); exportScanResults(result, defaultFileName); } - public enum ScanType { KEYWORD, // 关键词扫描 - REDIS_KEY, // Redis Key扫描 - DUBBO // Dubbo接口扫描 + public enum ScanType { + KEYWORD, + REDIS_KEY, + DUBBO } - // 结果数据模型 public static class ScanResult { private List projectResults = new ArrayList<>(); @@ -271,13 +256,8 @@ public class ScanService { this.matches = matches; } - public String getProjectPath() { - return projectPath; - } - - public List getMatches() { - return matches; - } + public String getProjectPath() { return projectPath; } + public List getMatches() { return matches; } } public static class MatchResult { @@ -289,61 +269,19 @@ public class ScanService { private String additionalInfo; private ScanType scanType; - // Getters and Setters - public String getProjectName() { - return projectName; - } - - public void setProjectName(String projectName) { - this.projectName = projectName; - } - - public String getFilePath() { - return filePath; - } - - public void setFilePath(String filePath) { - this.filePath = filePath; - } - - public int getLineNumber() { - return lineNumber; - } - - public void setLineNumber(int lineNumber) { - this.lineNumber = lineNumber; - } - - public String getLineContent() { - return lineContent; - } - - public void setLineContent(String lineContent) { - this.lineContent = lineContent; - } - - public String getMatchText() { - return matchText; - } - - public void setMatchText(String matchText) { - this.matchText = matchText; - } - - public String getAdditionalInfo() { - return additionalInfo; - } - - public void setAdditionalInfo(String additionalInfo) { - this.additionalInfo = additionalInfo; - } - - public ScanType getScanType() { - return scanType; - } - - public void setScanType(ScanType scanType) { - this.scanType = scanType; - } + public String getProjectName() { return projectName; } + public void setProjectName(String projectName) { this.projectName = projectName; } + public String getFilePath() { return filePath; } + public void setFilePath(String filePath) { this.filePath = filePath; } + public int getLineNumber() { return lineNumber; } + public void setLineNumber(int lineNumber) { this.lineNumber = lineNumber; } + public String getLineContent() { return lineContent; } + public void setLineContent(String lineContent) { this.lineContent = lineContent; } + public String getMatchText() { return matchText; } + public void setMatchText(String matchText) { this.matchText = matchText; } + public String getAdditionalInfo() { return additionalInfo; } + public void setAdditionalInfo(String additionalInfo) { this.additionalInfo = additionalInfo; } + public ScanType getScanType() { return scanType; } + public void setScanType(ScanType scanType) { this.scanType = scanType; } } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/workutils/biz/scanner/menu/ScanMenu.java b/src/main/java/com/example/workutils/biz/scanner/ScannerTool.java similarity index 80% rename from src/main/java/com/example/workutils/biz/scanner/menu/ScanMenu.java rename to src/main/java/com/example/workutils/biz/scanner/ScannerTool.java index fb36db5..a9aa8b0 100644 --- a/src/main/java/com/example/workutils/biz/scanner/menu/ScanMenu.java +++ b/src/main/java/com/example/workutils/biz/scanner/ScannerTool.java @@ -1,6 +1,5 @@ -package com.example.workutils.biz.scanner.menu; +package com.example.workutils.biz.scanner; -import com.example.workutils.biz.scanner.service.ScanService; import com.example.workutils.system.tool.Tool; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -9,20 +8,19 @@ import org.springframework.stereotype.Component; import java.util.Scanner; /** - * 扫描功能菜单 - * 仅负责菜单展示和用户交互,业务逻辑委托给ScanService + * 扫描工具入口(符合 Tool 规范) */ @Slf4j @Component @RequiredArgsConstructor -public class ScanMenu implements Tool { +public class ScannerTool implements Tool { private static final String[] MENU_OPTIONS = { "1. 关键词扫描", "0. 返回上级菜单" }; - private final ScanService scanService; + private final ScannerService scannerService; @Override public String getId() { @@ -58,13 +56,13 @@ public class ScanMenu implements Tool { switch (type) { case "keyword": - scanService.scanKeywords(pattern, outputFile); + scannerService.scanKeywords(pattern, outputFile); break; case "redis": - scanService.scanRedisKeys(pattern, outputFile); + scannerService.scanRedisKeys(pattern, outputFile); break; case "dubbo": - scanService.scanDubboInterfaces(pattern, outputFile); + scannerService.scanDubboInterfaces(pattern, outputFile); break; default: System.err.println("未知扫描类型: " + type); @@ -99,7 +97,7 @@ public class ScanMenu implements Tool { } log.info("扫描关键词: {}", keyword); - scanService.scanKeywordsWithDefaultName(keyword); + scannerService.scanKeywordsWithDefaultName(keyword); printSuccess("关键词扫描"); } diff --git a/src/main/java/com/example/workutils/biz/scanner/config/ScanConfig.java b/src/main/java/com/example/workutils/biz/scanner/config/ScanConfig.java new file mode 100644 index 0000000..eb89812 --- /dev/null +++ b/src/main/java/com/example/workutils/biz/scanner/config/ScanConfig.java @@ -0,0 +1,41 @@ +package com.example.workutils.biz.scanner.config; + +import lombok.Data; + +/** + * 扫描配置数据对象 + */ +@Data +public class ScanConfig { + + /** 是否启用扫描 */ + private boolean enabled = true; + + /** 是否包含子目录 */ + private boolean includeSubdirectories = true; + + /** 扫描的文件扩展名 */ + private String[] fileExtensions = {".java", ".xml", ".properties"}; + + /** 排除的目录名称 */ + private String[] excludeDirectories = {"target", ".git", "node_modules"}; + + /** 最大文件大小限制,默认10MB */ + private int maxFileSize = 10 * 1024 * 1024; + + public static ScanConfig defaultConfig() { + return new ScanConfig(); + } + + public static ScanConfig forXmlFiles() { + ScanConfig config = new ScanConfig(); + config.setFileExtensions(new String[]{".xml"}); + return config; + } + + public static ScanConfig forJavaFiles() { + ScanConfig config = new ScanConfig(); + config.setFileExtensions(new String[]{".java"}); + return config; + } +} diff --git a/src/main/java/com/example/workutils/biz/scanner/config/ScanConfigBO.java b/src/main/java/com/example/workutils/biz/scanner/config/ScanConfigBO.java deleted file mode 100644 index ec92462..0000000 --- a/src/main/java/com/example/workutils/biz/scanner/config/ScanConfigBO.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.example.workutils.biz.scanner.config; - -import lombok.Data; - -/** - * 扫描配置数据对象 - * 封装文件扫描过程中的各种配置参数 - * - * @author system - * @version 1.0 - */ -@Data -public class ScanConfigBO { - - /** 是否启用扫描 */ - private boolean enabled = true; - - /** 是否包含子目录 */ - private boolean includeSubdirectories = true; - - /** 扫描的文件扩展名,默认扫描Java、XML和属性文件 */ - private String[] fileExtensions = {".java", ".xml", ".properties"}; - - /** 排除的目录名称,默认排除target、git和node_modules目录 */ - private String[] excludeDirectories = {"target", ".git", "node_modules"}; - - /** 最大文件大小限制,默认10MB */ - private int maxFileSize = 10 * 1024 * 1024; - - /** - * 创建默认扫描配置 - * - * @return 默认配置实例 - */ - public static ScanConfigBO defaultConfig() { - return new ScanConfigBO(); - } - - /** - * 创建XML文件扫描配置 - * - * @return XML文件扫描配置 - */ - public static ScanConfigBO forXmlFiles() { - ScanConfigBO config = new ScanConfigBO(); - config.setFileExtensions(new String[]{".xml"}); - return config; - } - - /** - * 创建Java文件扫描配置 - * - * @return Java文件扫描配置 - */ - public static ScanConfigBO forJavaFiles() { - ScanConfigBO config = new ScanConfigBO(); - config.setFileExtensions(new String[]{".java"}); - return config; - } -} \ No newline at end of file diff --git a/src/main/java/com/example/workutils/system/ConsoleHelper.java b/src/main/java/com/example/workutils/system/support/ConsoleHelper.java similarity index 90% rename from src/main/java/com/example/workutils/system/ConsoleHelper.java rename to src/main/java/com/example/workutils/system/support/ConsoleHelper.java index bdcf0ff..fb7d21f 100644 --- a/src/main/java/com/example/workutils/system/ConsoleHelper.java +++ b/src/main/java/com/example/workutils/system/support/ConsoleHelper.java @@ -1,10 +1,9 @@ -package com.example.workutils.system; +package com.example.workutils.system.support; import java.util.Scanner; /** * 控制台交互工具类 - * 提取 MenuRunner 和 AbstractTool 中重复的输入/输出方法 */ public final class ConsoleHelper { diff --git a/src/main/java/com/example/workutils/biz/scanner/service/FileOperationService.java b/src/main/java/com/example/workutils/system/support/FileOperationService.java similarity index 68% rename from src/main/java/com/example/workutils/biz/scanner/service/FileOperationService.java rename to src/main/java/com/example/workutils/system/support/FileOperationService.java index ea0abba..f7aaa9b 100644 --- a/src/main/java/com/example/workutils/biz/scanner/service/FileOperationService.java +++ b/src/main/java/com/example/workutils/system/support/FileOperationService.java @@ -1,6 +1,6 @@ -package com.example.workutils.biz.scanner.service; +package com.example.workutils.system.support; -import com.example.workutils.biz.scanner.config.ScanConfigBO; +import com.example.workutils.biz.scanner.config.ScanConfig; import com.example.workutils.system.config.WorkUtilsProperties; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -18,51 +18,24 @@ import java.util.stream.Stream; /** * 文件操作服务 * 负责处理文件和目录的读取、扫描等操作 - * - * @author system - * @version 1.0 */ @Service @Slf4j public class FileOperationService { - // ==================== 依赖注入 ==================== - @Autowired private WorkUtilsProperties properties; - // ==================== 常量定义 ==================== - - /** UTF-8字符集 */ private static final java.nio.charset.Charset UTF8_CHARSET = java.nio.charset.StandardCharsets.UTF_8; - - /** 默认文件大小限制:10MB */ private static final long DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024; - - /** 系统行分隔符 */ private static final String LINE_SEPARATOR = System.lineSeparator(); // ==================== 文件读取相关方法 ==================== - - /** - * 按行读取文件内容 - * - * @param filePath 文件路径 - * @return 文件行列表 - * @throws IOException 读取异常 - */ + public List readLinesFromFile(String filePath) throws IOException { - Path path = Paths.get(filePath); - return readLinesFromFile(path); + return readLinesFromFile(Paths.get(filePath)); } - /** - * 按行读取文件内容 - * - * @param filePath 文件路径 - * @return 文件行列表 - * @throws IOException 读取异常 - */ public List readLinesFromFile(Path filePath) throws IOException { try { return Files.readAllLines(filePath, UTF8_CHARSET); @@ -77,16 +50,8 @@ public class FileOperationService { } } - /** - * 读取文件完整内容(保留换行符) - * - * @param filePath 文件路径 - * @return 文件内容字符串 - * @throws IOException 读取异常 - */ public String readFileContent(Path filePath) throws IOException { try { - // 读取文件行并使用系统行分隔符重新组合,保持原始格式 List lines = Files.readAllLines(filePath, UTF8_CHARSET); return String.join(LINE_SEPARATOR, lines); } catch (java.nio.charset.MalformedInputException e) { @@ -102,66 +67,32 @@ public class FileOperationService { } // ==================== 文件名生成相关方法 ==================== - - /** - * 生成带时间戳的文件名 - * - * @param baseDirectory 基础目录 - * @param prefix 文件名前缀 - * @param extension 文件扩展名 - * @return 生成的文件名 - */ + public String generateFileName(String baseDirectory, String prefix, String extension) { String timestamp = java.time.LocalDateTime.now() .format(java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")); return String.format("%s/%s_%s.%s", baseDirectory, prefix, timestamp, extension); } - /** - * 生成带时间戳的文件名(使用配置的导出基础路径) - * 格式:导出根目录/yyyyMMdd/prefix_yyyyMMddHHmmss.extension - * - * @param prefix 文件名前缀 - * @param extension 文件扩展名 - * @return 生成的文件完整路径 - */ public String generateFileName(String prefix, String extension) { return properties.resolveExportPath(prefix, extension).toString(); } - /** - * 提取项目名称 - * - * @param projectPath 项目路径 - * @return 项目名称 - */ public String extractProjectName(String projectPath) { if (projectPath == null || projectPath.isEmpty()) { return "unknown"; } - Path path = Paths.get(projectPath); String fileName = path.getFileName().toString(); - - // 移除路径末尾的分隔符 if (fileName.endsWith("/") || fileName.endsWith("\\")) { fileName = fileName.substring(0, fileName.length() - 1); } - return fileName.isEmpty() ? "unknown" : fileName; } // ==================== 文件扫描相关方法 ==================== - - /** - * 扫描指定路径下的文件 - * - * @param rootPath 根路径 - * @param config 扫描配置 - * @return 文件路径列表 - * @throws IOException 扫描异常 - */ - public List scanFiles(String rootPath, ScanConfigBO config) throws IOException { + + public List scanFiles(String rootPath, ScanConfig config) throws IOException { List result = new ArrayList<>(); Path root = Paths.get(rootPath); @@ -178,16 +109,8 @@ public class FileOperationService { return result; } - - /** - * 递归扫描文件 - * - * @param root 根目录 - * @param config 扫描配置 - * @param result 结果列表 - * @throws IOException 扫描异常 - */ - private void scanFilesRecursively(Path root, ScanConfigBO config, List result) throws IOException { + + private void scanFilesRecursively(Path root, ScanConfig config, List result) throws IOException { List excludeDirs = Arrays.asList(config.getExcludeDirectories()); List extensions = Arrays.asList(config.getFileExtensions()); @@ -223,25 +146,16 @@ public class FileOperationService { } }); } - - /** - * 非递归扫描文件(只扫描直接子目录) - * - * @param root 根目录 - * @param config 扫描配置 - * @param result 结果列表 - * @throws IOException 扫描异常 - */ - private void scanFilesNonRecursively(Path root, ScanConfigBO config, List result) throws IOException { + + private void scanFilesNonRecursively(Path root, ScanConfig config, List result) throws IOException { if (!Files.isDirectory(root)) { return; } - try (Stream stream = Files.list(root)) { stream.filter(file -> { try { - return shouldIncludeFile(file, - Arrays.asList(config.getFileExtensions()), + return shouldIncludeFile(file, + Arrays.asList(config.getFileExtensions()), config.getMaxFileSize()); } catch (Exception e) { log.warn("检查文件时出错: " + file, e); @@ -253,46 +167,26 @@ public class FileOperationService { } } - /** - * 判断文件是否应该包含在扫描结果中 - * - * @param file 文件路径 - * @param extensions 允许的文件扩展名列表 - * @param maxSize 最大文件大小限制 - * @return 是否包含 - * @throws IOException 检查异常 - */ private boolean shouldIncludeFile(Path file, List extensions, long maxSize) throws IOException { - // 检查文件大小 if (Files.size(file) > maxSize) { log.debug("文件过大,跳过: {} (大小: {} bytes)", file, Files.size(file)); return false; } - - // 检查文件扩展名 String fileName = file.getFileName().toString(); if (extensions.isEmpty()) { - // 如果没有指定扩展名限制,则包含所有文件 return true; } - - return extensions.stream().anyMatch(ext -> + return extensions.stream().anyMatch(ext -> fileName.toLowerCase().endsWith(ext.toLowerCase())); } // ==================== 项目路径相关方法 ==================== - /** - * 项目状态枚举 - */ public enum ProjectStatus { NORMAL, CORRUPTED } - /** - * 项目路径信息(含状态) - */ public static class ProjectPathInfo { private String path; private ProjectStatus status; @@ -316,20 +210,11 @@ public class FileOperationService { public String getCorruptionReason() { return corruptionReason; } public void setCorruptionReason(String corruptionReason) { this.corruptionReason = corruptionReason; } - /** - * 转换为配置文件格式 - */ public String toConfigLine() { return path + "|" + status.name(); } } - /** - * 获取所有项目路径(兼容旧格式,无状态) - * - * @return 项目路径列表 - * @throws IOException 读取异常 - */ public List getProjectPaths() throws IOException { Path projectPathsFile = properties.getProjectPathsFilePath(); if (!Files.exists(projectPathsFile)) { @@ -341,22 +226,14 @@ public class FileOperationService { .map(String::trim) .filter(line -> !line.isEmpty() && !line.startsWith("#")) .map(line -> { - // 兼容旧格式:只包含路径,没有状态 if (!line.contains("|")) { return line; } - // 新格式:path|status return line.split("\\|")[0]; }) .collect(Collectors.toList()); } - /** - * 获取所有项目路径(含状态) - * - * @return 项目路径信息列表 - * @throws IOException 读取异常 - */ public List getProjectPathsWithStatus() throws IOException { Path projectPathsFile = properties.getProjectPathsFilePath(); if (!Files.exists(projectPathsFile)) { @@ -368,11 +245,9 @@ public class FileOperationService { .map(String::trim) .filter(line -> !line.isEmpty() && !line.startsWith("#")) .map(line -> { - // 兼容旧格式:只包含路径,没有状态 if (!line.contains("|")) { return new ProjectPathInfo(line, ProjectStatus.NORMAL); } - // 新格式:path|status String[] parts = line.split("\\|"); String path = parts[0].trim(); ProjectStatus status; @@ -386,13 +261,6 @@ public class FileOperationService { .collect(Collectors.toList()); } - /** - * 更新项目状态 - * - * @param path 项目路径 - * @param status 新状态 - * @throws IOException 写入异常 - */ public void updateProjectStatus(String path, ProjectStatus status) throws IOException { List projects = getProjectPathsWithStatus(); for (ProjectPathInfo project : projects) { @@ -404,12 +272,6 @@ public class FileOperationService { saveProjectPaths(projects); } - /** - * 保存项目路径列表到文件 - * - * @param projects 项目列表 - * @throws IOException 写入异常 - */ public void saveProjectPaths(List projects) throws IOException { String content = projects.stream() .map(ProjectPathInfo::toConfigLine) @@ -418,65 +280,29 @@ public class FileOperationService { } // ==================== 文件写入相关方法 ==================== - - /** - * 写入内容到文件 - * - * @param path 文件路径 - * @param content 内容 - * @throws IOException 写入异常 - */ + public void writeToFile(Path path, String content) throws IOException { - // 确保父目录存在 Files.createDirectories(path.getParent()); - - // 写入文件 - Files.write(path, content.getBytes(), - StandardOpenOption.CREATE, + Files.write(path, content.getBytes(), + StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING); - log.debug("文件写入成功: {}", path); } - /** - * 写入内容到文件 - * - * @param filePath 文件路径字符串 - * @param content 内容 - * @throws IOException 写入异常 - */ public void writeToFile(String filePath, String content) throws IOException { writeToFile(Paths.get(filePath), content); } // ==================== 文件检查相关方法 ==================== - - /** - * 检查文件是否存在 - * - * @param filePath 文件路径字符串 - * @return 是否存在 - */ + public boolean fileExists(String filePath) { return Files.exists(Paths.get(filePath)); } - /** - * 检查文件是否存在 - * - * @param path 文件路径 - * @return 是否存在 - */ public boolean fileExists(Path path) { return Files.exists(path); } - /** - * 确保目录存在,如果不存在则创建 - * - * @param path 目录路径 - * @throws IOException 创建异常 - */ public void ensureDirectoryExists(Path path) throws IOException { if (!Files.exists(path)) { Files.createDirectories(path); @@ -484,13 +310,7 @@ public class FileOperationService { } } - /** - * 确保目录存在,如果不存在则创建 - * - * @param path 目录路径字符串 - * @throws IOException 创建异常 - */ public void ensureDirectoryExists(String path) throws IOException { ensureDirectoryExists(Paths.get(path)); } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/workutils/biz/output/OutputService.java b/src/main/java/com/example/workutils/system/support/OutputService.java similarity index 59% rename from src/main/java/com/example/workutils/biz/output/OutputService.java rename to src/main/java/com/example/workutils/system/support/OutputService.java index 5f5b752..e37f0d4 100644 --- a/src/main/java/com/example/workutils/biz/output/OutputService.java +++ b/src/main/java/com/example/workutils/system/support/OutputService.java @@ -1,10 +1,9 @@ -package com.example.workutils.biz.output; +package com.example.workutils.system.support; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.write.metadata.WriteSheet; import com.example.workutils.system.config.WorkUtilsProperties; -import com.example.workutils.biz.scanner.service.FileOperationService; import com.opencsv.CSVWriter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; @@ -13,22 +12,18 @@ import org.springframework.stereotype.Service; import java.io.FileWriter; import java.io.IOException; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.Collections; import java.util.List; /** * 输出服务 * 负责将数据输出到CSV、Excel等格式的文件 - * - * @author system - * @version 1.0 */ @Service @Slf4j public class OutputService { - // ==================== 依赖注入 ==================== - @Autowired private FileOperationService fileOperationService; @@ -36,20 +31,11 @@ public class OutputService { private WorkUtilsProperties properties; // ==================== CSV输出相关方法 ==================== - - /** - * 将记录列表写入CSV文件 - * - * @param records 记录列表 - * @param outputPath 输出文件路径 - * @param headers 表头数组 - * @throws IOException 写入异常 - */ + public void writeRecordsToCsv(List records, String outputPath, String... headers) throws IOException { Path filePath; - // 如果 outputPath 已经是完整路径(包含导出根目录),直接使用 if (outputPath.startsWith(properties.getExportBasePath().toString())) { - filePath = java.nio.file.Paths.get(outputPath); + filePath = Paths.get(outputPath); } else { filePath = properties.resolveExportPath(outputPath); } @@ -66,39 +52,14 @@ public class OutputService { log.info("CSV文件写入成功: {}", filePath); } - /** - * 将记录列表写入CSV文件(重载方法) - * - * @param records 记录列表 - * @param outputPath 输出文件路径 - * @param headers 表头列表 - * @throws IOException 写入异常 - */ public void writeRecordsToCsv(List records, String outputPath, List headers) throws IOException { writeRecordsToCsv(records, outputPath, headers.toArray(new String[0])); } - /** - * 将单条记录写入CSV文件 - * - * @param record 记录 - * @param outputPath 输出文件路径 - * @param headers 表头数组 - * @throws IOException 写入异常 - */ public void writeSingleRecordToCsv(String[] record, String outputPath, String... headers) throws IOException { writeRecordsToCsv(Collections.singletonList(record), outputPath, headers); } - /** - * 追加记录到CSV文件 - * 如果文件不存在,则创建文件并写入表头 - * - * @param record 记录 - * @param outputPath 输出文件路径 - * @param headers 表头数组 - * @throws IOException 写入异常 - */ public void appendToCsv(String[] record, String outputPath, String... headers) throws IOException { Path filePath = properties.resolveExportPath(outputPath); fileOperationService.ensureDirectoryExists(filePath.getParent()); @@ -118,52 +79,21 @@ public class OutputService { } // ==================== Excel输出相关方法 ==================== - - /** - * 将数据写入Excel文件(单Sheet) - * - * @param data 数据列表 - * @param outputPath 输出文件路径 - * @param modelClass 数据模型类 - * @param 数据类型 - * @throws IOException 写入异常 - */ + public void writeToExcel(List data, String outputPath, Class modelClass) throws IOException { Path filePath = properties.resolveExportPath(outputPath); fileOperationService.ensureDirectoryExists(filePath.getParent()); - EasyExcel.write(filePath.toFile(), modelClass).sheet().doWrite(data); - log.info("Excel文件写入成功: {}", filePath); } - /** - * 将数据写入Excel文件(指定Sheet名称) - * - * @param data 数据列表 - * @param outputPath 输出文件路径 - * @param sheetName Sheet名称 - * @param modelClass 数据模型类 - * @param 数据类型 - * @throws IOException 写入异常 - */ public void writeToExcel(List data, String outputPath, String sheetName, Class modelClass) throws IOException { Path filePath = properties.resolveExportPath(outputPath); fileOperationService.ensureDirectoryExists(filePath.getParent()); - EasyExcel.write(filePath.toFile(), modelClass).sheet(sheetName).doWrite(data); - log.info("Excel文件写入成功: {}", filePath); } - /** - * 将数据写入Excel文件(多Sheet) - * - * @param sheetsData Sheet数据列表 - * @param outputPath 输出文件路径 - * @param 数据类型 - * @throws IOException 写入异常 - */ public void writeToExcelWithMultipleSheets(List> sheetsData, String outputPath) throws IOException { Path filePath = properties.resolveExportPath(outputPath); fileOperationService.ensureDirectoryExists(filePath.getParent()); @@ -181,69 +111,26 @@ public class OutputService { } // ==================== 文件名生成相关方法 ==================== - - /** - * 生成输出文件名 - * - * @param prefix 文件名前缀 - * @param extension 文件扩展名 - * @return 生成的文件名 - */ + public String generateOutputFileName(String prefix, String extension) { return fileOperationService.generateFileName(prefix, extension); } // ==================== 内部数据类 ==================== - - /** - * Excel Sheet数据封装类 - * 用于封装单个Sheet的数据、名称和模型类信息 - * - * @param 数据类型 - */ - public static class ExcelSheetData { - private final List data; // 数据列表 - private final String sheetName; // Sheet名称 - private final Class modelClass; // 数据模型类 - /** - * 构造函数 - * - * @param data 数据列表 - * @param sheetName Sheet名称 - * @param modelClass 数据模型类 - */ + public static class ExcelSheetData { + private final List data; + private final String sheetName; + private final Class modelClass; + public ExcelSheetData(List data, String sheetName, Class modelClass) { this.data = data; this.sheetName = sheetName; this.modelClass = modelClass; } - /** - * 获取数据列表 - * - * @return 数据列表 - */ - public List getData() { - return data; - } - - /** - * 获取Sheet名称 - * - * @return Sheet名称 - */ - public String getSheetName() { - return sheetName; - } - - /** - * 获取数据模型类 - * - * @return 数据模型类 - */ - public Class getModelClass() { - return modelClass; - } + public List getData() { return data; } + public String getSheetName() { return sheetName; } + public Class getModelClass() { return modelClass; } } -} \ No newline at end of file +} diff --git a/src/main/java/com/example/workutils/system/tool/Tool.java b/src/main/java/com/example/workutils/system/tool/Tool.java index a1b7f8a..e89e8d9 100644 --- a/src/main/java/com/example/workutils/system/tool/Tool.java +++ b/src/main/java/com/example/workutils/system/tool/Tool.java @@ -1,6 +1,6 @@ package com.example.workutils.system.tool; -import com.example.workutils.system.ConsoleHelper; +import com.example.workutils.system.support.ConsoleHelper; import java.util.Scanner; @@ -29,10 +29,6 @@ public interface Tool { // ==================== 交互模式(模板方法) ==================== - /** - * 交互式菜单循环(默认实现,从 AbstractTool 合并而来) - * displayMenu → readChoice → if 0 return → handleChoice → repeat - */ default void execute(Scanner scanner) { while (true) { ConsoleHelper.printMenuTitle(getName()); @@ -59,9 +55,6 @@ public interface Tool { // ==================== 命令行模式 ==================== - /** - * 命令行模式执行(无交互),默认打印不支持提示 - */ default void executeCommand(String... args) throws Exception { System.out.println("工具 [" + getName() + "] 不支持命令行模式"); } diff --git a/src/main/java/com/example/workutils/system/tool/Registry.java b/src/main/java/com/example/workutils/system/tool/ToolRegistry.java similarity index 93% rename from src/main/java/com/example/workutils/system/tool/Registry.java rename to src/main/java/com/example/workutils/system/tool/ToolRegistry.java index 854e410..0ff74fc 100644 --- a/src/main/java/com/example/workutils/system/tool/Registry.java +++ b/src/main/java/com/example/workutils/system/tool/ToolRegistry.java @@ -1,6 +1,6 @@ package com.example.workutils.system.tool; -import com.example.workutils.system.ConsoleHelper; +import com.example.workutils.system.support.ConsoleHelper; import com.example.workutils.system.config.WorkUtilsProperties; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; @@ -12,11 +12,11 @@ import java.util.stream.Collectors; /** * 工具注册中心 * 自动发现 Spring 容器中的 Tool Bean,按配置过滤启用/禁用 - * 同时负责主菜单循环(从 MenuRunner 合并) + * 同时负责主菜单循环 */ @Slf4j @Component -public class Registry { +public class ToolRegistry { private static final int EXIT_CODE = 0; @@ -26,7 +26,7 @@ public class Registry { private Map toolByIdMap; private Map toolByCommandMap; - public Registry(List tools, WorkUtilsProperties properties) { + public ToolRegistry(List tools, WorkUtilsProperties properties) { this.allTools = tools; this.properties = properties; } @@ -59,7 +59,7 @@ public class Registry { log.info("工具注册完成: {}/{} 已启用", enabledTools.size(), allTools.size()); } - // ==================== 主菜单(从 MenuRunner 合并) ==================== + // ==================== 主菜单 ==================== public void runMainMenu(Scanner scanner) { log.info("启动主菜单");