From b76a2a451e6511eec8ff6190bd0aae201be50722 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=AE=B6=E8=AE=AD?= Date: Mon, 8 Jun 2026 17:40:57 +0800 Subject: [PATCH] =?UTF-8?q?=E8=8F=9C=E5=8D=95=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../workutils/WorkUtilsApplication.java | 68 +++++++++ .../workutils/WorkUtilsAppliction.java | 143 ------------------ .../biz/mybatis/menu/MyBatisMenu.java | 40 +++++ .../workutils/biz/output/menu/BatchMenu.java | 12 ++ .../workutils/biz/scanner/menu/ScanMenu.java | 35 +++++ .../workutils/menu/api/MenuRunner.java | 31 +--- .../example/workutils/tool/AbstractTool.java | 71 ++++----- .../example/workutils/tool/ConsoleHelper.java | 46 ++++++ .../java/com/example/workutils/tool/Tool.java | 18 +++ .../example/workutils/tool/ToolRegistry.java | 13 ++ 项目目录结构说明.md | 41 +++-- 11 files changed, 295 insertions(+), 223 deletions(-) create mode 100644 src/main/java/com/example/workutils/WorkUtilsApplication.java delete mode 100644 src/main/java/com/example/workutils/WorkUtilsAppliction.java create mode 100644 src/main/java/com/example/workutils/tool/ConsoleHelper.java diff --git a/src/main/java/com/example/workutils/WorkUtilsApplication.java b/src/main/java/com/example/workutils/WorkUtilsApplication.java new file mode 100644 index 0000000..b24bd0b --- /dev/null +++ b/src/main/java/com/example/workutils/WorkUtilsApplication.java @@ -0,0 +1,68 @@ +package com.example.workutils; + +import com.example.workutils.menu.api.MenuRunner; +import com.example.workutils.tool.Tool; +import com.example.workutils.tool.ToolRegistry; +import lombok.extern.slf4j.Slf4j; +import org.springframework.boot.CommandLineRunner; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; + +import java.util.Arrays; +import java.util.Optional; +import java.util.Scanner; + +@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) +@Slf4j +public class WorkUtilsApplication implements CommandLineRunner { + + private final MenuRunner menuRunner; + private final ToolRegistry toolRegistry; + + public WorkUtilsApplication(MenuRunner menuRunner, ToolRegistry toolRegistry) { + this.menuRunner = menuRunner; + this.toolRegistry = toolRegistry; + } + + public static void main(String[] args) { + SpringApplication.run(WorkUtilsApplication.class, args); + } + + @Override + public void run(String... args) throws Exception { + if (args.length == 0) { + showInteractiveMenu(); + } else { + processCommand(args); + } + } + + private void showInteractiveMenu() { + Scanner scanner = new Scanner(System.in); + menuRunner.run(scanner); + } + + /** + * 命令行模式:通过ToolRegistry按命令名分发到对应Tool + * 启动类不依赖任何具体业务Service + */ + private void processCommand(String... args) throws Exception { + String command = args[0].toLowerCase(); + + Optional toolOpt = toolRegistry.getToolByCommand(command); + if (toolOpt.isPresent()) { + Tool tool = toolOpt.get(); + try { + // args[0]是命令名,从args[1]开始传给tool + String[] toolArgs = args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0]; + tool.executeCommand(toolArgs); + } catch (Exception e) { + log.error("命令执行失败: {} [{}]", tool.getName(), tool.getId(), e); + System.err.println("命令执行失败: " + e.getMessage()); + } + } else { + System.err.println("未知命令: " + command); + } + } +} diff --git a/src/main/java/com/example/workutils/WorkUtilsAppliction.java b/src/main/java/com/example/workutils/WorkUtilsAppliction.java deleted file mode 100644 index aabd1b7..0000000 --- a/src/main/java/com/example/workutils/WorkUtilsAppliction.java +++ /dev/null @@ -1,143 +0,0 @@ -package com.example.workutils; - -import com.example.workutils.biz.mybatis.export.MyBatisExportService; -import com.example.workutils.biz.scanner.service.ScanService; -import com.example.workutils.menu.api.MenuRunner; -import com.example.workutils.tool.ToolRegistry; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.CommandLineRunner; -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; -import org.springframework.stereotype.Component; - -import java.util.ArrayList; -import java.util.List; -import java.util.Scanner; - -@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class}) -@Component -@Slf4j -public class WorkUtilsAppliction implements CommandLineRunner { - - @Autowired - private MenuRunner menuRunner; - - @Autowired - private ToolRegistry toolRegistry; - - @Autowired - private MyBatisExportService myBatisExportService; - - @Autowired - private ScanService scanService; - - public static void main(String[] args) { - SpringApplication.run(WorkUtilsAppliction.class, args); - } - - @Override - public void run(String... args) throws Exception { - if (args.length == 0) { - showInteractiveMenu(); - } else { - processCommand(args); - } - } - - private void showInteractiveMenu() { - Scanner scanner = new Scanner(System.in); - menuRunner.run(scanner); - } - - private void processCommand(String... args) throws Exception { - if (args.length < 1) { - showUsage(); - return; - } - - String command = args[0].toLowerCase(); - - switch (command) { - case "sql": - case "mysbql": - handleSqlCommand(args); - break; - case "scan": - handleScanCommand(args); - break; - default: - System.err.println("未知命令:" + command); - showUsage(); - } - } - - private void handleSqlCommand(String[] args) throws Exception { - if (args.length > 1) { - String subCommand = args[1].toLowerCase(); - if ("table".equals(subCommand) && args.length > 2) { - myBatisExportService.exportSQLByTable(args[2]); - System.out.println("表名SQL导出完成!"); - } else if ("tables".equals(subCommand) && args.length > 2) { - List tableNames = new ArrayList<>(); - for (int i = 2; i < args.length; i++) { - tableNames.add(args[i]); - } - myBatisExportService.exportSQLByTables(tableNames); - System.out.println("多表SQL导出完成!"); - } else { - myBatisExportService.exportSQL(); - System.out.println("所有SQL导出完成!"); - } - } else { - myBatisExportService.exportSQL(); - System.out.println("所有SQL导出完成!"); - } - } - - private void handleScanCommand(String[] args) throws Exception { - if (args.length < 3) { - System.err.println("扫描命令格式: scan [outputfile]"); - return; - } - - String type = args[1].toLowerCase(); - String pattern = args[2]; - String outputFile = args.length > 3 ? args[3] : "scan_result.csv"; - - switch (type) { - case "keyword": - scanService.scanKeywords(pattern, outputFile); - break; - case "redis": - scanService.scanRedisKeys(pattern, outputFile); - break; - case "dubbo": - scanService.scanDubboInterfaces(pattern, outputFile); - break; - default: - System.err.println("未知扫描类型:" + type); - return; - } - - System.out.println("扫描完成,结果已保存到:" + outputFile); - } - - private void showUsage() { - System.out.println("WorkUtils 使用说明:"); - System.out.println(""); - System.out.println("交互模式(无参数):"); - System.out.println(" java -jar workutils.jar"); - System.out.println(""); - System.out.println("命令行模式:"); - System.out.println(" sql [all|incremental] # SQL导出"); - System.out.println(" scan [output] # 扫描功能"); - System.out.println(" batch # 批量处理"); - System.out.println(""); - System.out.println("示例:"); - System.out.println(" java -jar workutils.jar sql # 导出所有SQL"); - System.out.println(" java -jar workutils.jar scan keyword \"TODO\" # 扫描TODO关键词"); - System.out.println(" java -jar workutils.jar batch # 执行所有检查"); - } -} diff --git a/src/main/java/com/example/workutils/biz/mybatis/menu/MyBatisMenu.java b/src/main/java/com/example/workutils/biz/mybatis/menu/MyBatisMenu.java index 15ffaa8..d4ecf3c 100644 --- a/src/main/java/com/example/workutils/biz/mybatis/menu/MyBatisMenu.java +++ b/src/main/java/com/example/workutils/biz/mybatis/menu/MyBatisMenu.java @@ -39,11 +39,51 @@ public class MyBatisMenu extends AbstractTool { return "MyBatis SQL导出"; } + @Override + public String getCommandName() { + return "sql"; + } + @Override public String[] getMenuOptions() { return MENU_OPTIONS; } + @Override + public void executeCommand(String... args) throws Exception { + if (args.length == 0) { + myBatisExportService.exportSQL(); + System.out.println("所有SQL导出完成!"); + } else { + String subCommand = args[0].toLowerCase(); + switch (subCommand) { + case "table": + if (args.length > 1) { + myBatisExportService.exportSQLByTable(args[1]); + System.out.println("表名SQL导出完成!"); + } else { + System.err.println("用法: sql table <表名>"); + } + break; + case "tables": + if (args.length > 1) { + java.util.List tableNames = new java.util.ArrayList<>(); + for (int i = 1; i < args.length; i++) { + tableNames.add(args[i]); + } + myBatisExportService.exportSQLByTables(tableNames); + System.out.println("多表SQL导出完成!"); + } else { + System.err.println("用法: sql tables <表名1> <表名2> ..."); + } + break; + default: + System.err.println("未知子命令: " + subCommand); + System.err.println("可用子命令: table <表名>, tables <表名1> <表名2> ..."); + } + } + } + @Override protected void handleChoice(int choice, Scanner scanner) throws Exception { switch (choice) { diff --git a/src/main/java/com/example/workutils/biz/output/menu/BatchMenu.java b/src/main/java/com/example/workutils/biz/output/menu/BatchMenu.java index 5cbc66d..78c93a5 100644 --- a/src/main/java/com/example/workutils/biz/output/menu/BatchMenu.java +++ b/src/main/java/com/example/workutils/biz/output/menu/BatchMenu.java @@ -51,11 +51,23 @@ public class BatchMenu extends AbstractTool { return "批量处理"; } + @Override + public String getCommandName() { + return "batch"; + } + @Override public String[] getMenuOptions() { return MENU_OPTIONS; } + @Override + public void executeCommand(String... args) throws Exception { + info("命令行模式: 开始执行所有检查"); + executeAllChecks(); + System.out.println("批量处理完成!"); + } + @Override protected void handleChoice(int choice, Scanner scanner) throws Exception { switch (choice) { diff --git a/src/main/java/com/example/workutils/biz/scanner/menu/ScanMenu.java b/src/main/java/com/example/workutils/biz/scanner/menu/ScanMenu.java index 3923999..08c3d4b 100644 --- a/src/main/java/com/example/workutils/biz/scanner/menu/ScanMenu.java +++ b/src/main/java/com/example/workutils/biz/scanner/menu/ScanMenu.java @@ -34,11 +34,46 @@ public class ScanMenu extends AbstractTool { return "扫描功能"; } + @Override + public String getCommandName() { + return "scan"; + } + @Override public String[] getMenuOptions() { return MENU_OPTIONS; } + @Override + public void executeCommand(String... args) throws Exception { + if (args.length < 2) { + System.err.println("扫描命令格式: scan [outputfile]"); + System.err.println("可用类型: keyword, redis, dubbo"); + return; + } + + String type = args[0].toLowerCase(); + String pattern = args[1]; + String outputFile = args.length > 2 ? args[2] : "scan_result.csv"; + + switch (type) { + case "keyword": + scanService.scanKeywords(pattern, outputFile); + break; + case "redis": + scanService.scanRedisKeys(pattern, outputFile); + break; + case "dubbo": + scanService.scanDubboInterfaces(pattern, outputFile); + break; + default: + System.err.println("未知扫描类型: " + type); + return; + } + + System.out.println("扫描完成,结果已保存到: " + outputFile); + } + @Override protected void handleChoice(int choice, Scanner scanner) throws Exception { switch (choice) { diff --git a/src/main/java/com/example/workutils/menu/api/MenuRunner.java b/src/main/java/com/example/workutils/menu/api/MenuRunner.java index d1727a4..d8b419a 100644 --- a/src/main/java/com/example/workutils/menu/api/MenuRunner.java +++ b/src/main/java/com/example/workutils/menu/api/MenuRunner.java @@ -1,5 +1,6 @@ package com.example.workutils.menu.api; +import com.example.workutils.tool.ConsoleHelper; import com.example.workutils.tool.Tool; import com.example.workutils.tool.ToolRegistry; import lombok.extern.slf4j.Slf4j; @@ -10,7 +11,7 @@ import java.util.Scanner; /** * 菜单运行器 - * 从ToolRegistry动态构建主菜单,无需硬编码Handler引用 + * 从ToolRegistry动态构建主菜单,无需硬编码Menu引用 */ @Slf4j @Component @@ -28,16 +29,16 @@ public class MenuRunner { * 运行主菜单循环 */ public void run(Scanner scanner) { - info("启动主菜单"); + log.info("启动主菜单"); while (true) { - printMenuTitle("主菜单"); + ConsoleHelper.printMenuTitle("主菜单"); displayMainMenu(); - int choice = readChoice(scanner); + int choice = ConsoleHelper.readChoice(scanner); scanner.nextLine(); if (choice == EXIT_CODE) { - info("用户退出系统"); + log.info("用户退出系统"); System.out.println("感谢使用,再见!"); break; } @@ -75,25 +76,7 @@ public class MenuRunner { System.out.println("工具执行失败: " + e.getMessage()); } } else { - System.out.println("无效选项"); + ConsoleHelper.printInvalidOption(); } } - - private int readChoice(Scanner scanner) { - try { - System.out.print("请输入选项编号: "); - return scanner.nextInt(); - } catch (Exception e) { - return -1; - } - } - - private void printMenuTitle(String title) { - System.out.println(); - System.out.println("========== " + title + " =========="); - } - - private void info(String message) { - log.info(message); - } } diff --git a/src/main/java/com/example/workutils/tool/AbstractTool.java b/src/main/java/com/example/workutils/tool/AbstractTool.java index cd8ef92..7f8d219 100644 --- a/src/main/java/com/example/workutils/tool/AbstractTool.java +++ b/src/main/java/com/example/workutils/tool/AbstractTool.java @@ -6,8 +6,10 @@ import java.util.Scanner; /** * 工具基类 - * 提供共享的菜单循环模板方法,消除各Handler中的重复代码 - * 子类只需实现handleChoice()定义具体业务逻辑 + * 提供共享的菜单循环模板方法,消除各Menu中的重复代码 + * 子类只需实现handleChoice()定义具体交互逻辑 + * + * 控制台I/O方法委托给ConsoleHelper,日志方法保留在此类 */ @Slf4j public abstract class AbstractTool implements Tool { @@ -19,10 +21,10 @@ public abstract class AbstractTool implements Tool { @Override public void execute(Scanner scanner) { while (true) { - printMenuTitle(getName()); - displayMenu(getMenuOptions()); + ConsoleHelper.printMenuTitle(getName()); + ConsoleHelper.displayMenu(getMenuOptions()); - int choice = readChoice(scanner); + int choice = ConsoleHelper.readChoice(scanner); scanner.nextLine(); if (choice == 0) { @@ -34,7 +36,7 @@ public abstract class AbstractTool implements Tool { handleChoice(choice, scanner); } catch (Exception e) { error(getName() + "执行失败", e); - printError(e.getMessage()); + ConsoleHelper.printError(e.getMessage()); } } } @@ -44,40 +46,7 @@ public abstract class AbstractTool implements Tool { */ protected abstract void handleChoice(int choice, Scanner scanner) throws Exception; - // ============ 共享菜单工具方法 ============ - - protected void displayMenu(String[] options) { - System.out.println("请选择操作:"); - for (String option : options) { - System.out.println(option); - } - } - - protected int readChoice(Scanner scanner) { - try { - System.out.print("请输入选项编号: "); - return scanner.nextInt(); - } catch (Exception e) { - return -1; - } - } - - protected void printMenuTitle(String title) { - System.out.println(); - System.out.println("========== " + title + " =========="); - } - - protected void printSuccess(String operation) { - System.out.println(operation + "完成!"); - } - - protected void printError(String message) { - System.out.println("操作失败: " + message); - } - - protected void printInvalidOption() { - System.out.println("无效选项"); - } + // ============ 日志方法 ============ protected void info(String message) { log.info(message); @@ -94,4 +63,26 @@ public abstract class AbstractTool implements Tool { protected void error(String message, Exception e) { log.error(message, e); } + + // ============ 控制台I/O方法(委托给ConsoleHelper,子类可直接调用) ============ + + protected void printMenuTitle(String title) { + ConsoleHelper.printMenuTitle(title); + } + + protected void displayMenu(String[] options) { + ConsoleHelper.displayMenu(options); + } + + protected void printSuccess(String operation) { + ConsoleHelper.printSuccess(operation); + } + + protected void printError(String message) { + ConsoleHelper.printError(message); + } + + protected void printInvalidOption() { + ConsoleHelper.printInvalidOption(); + } } diff --git a/src/main/java/com/example/workutils/tool/ConsoleHelper.java b/src/main/java/com/example/workutils/tool/ConsoleHelper.java new file mode 100644 index 0000000..fd8a902 --- /dev/null +++ b/src/main/java/com/example/workutils/tool/ConsoleHelper.java @@ -0,0 +1,46 @@ +package com.example.workutils.tool; + +import java.util.Scanner; + +/** + * 控制台交互工具类 + * 提取 MenuRunner 和 AbstractTool 中重复的输入/输出方法 + */ +public final class ConsoleHelper { + + private ConsoleHelper() { + } + + public static void printMenuTitle(String title) { + System.out.println(); + System.out.println("========== " + title + " =========="); + } + + public static void displayMenu(String[] options) { + System.out.println("请选择操作:"); + for (String option : options) { + System.out.println(option); + } + } + + public static int readChoice(Scanner scanner) { + try { + System.out.print("请输入选项编号: "); + return scanner.nextInt(); + } catch (Exception e) { + return -1; + } + } + + public static void printSuccess(String operation) { + System.out.println(operation + "完成!"); + } + + public static void printError(String message) { + System.out.println("操作失败: " + message); + } + + public static void printInvalidOption() { + System.out.println("无效选项"); + } +} diff --git a/src/main/java/com/example/workutils/tool/Tool.java b/src/main/java/com/example/workutils/tool/Tool.java index 3875c94..951bb9d 100644 --- a/src/main/java/com/example/workutils/tool/Tool.java +++ b/src/main/java/com/example/workutils/tool/Tool.java @@ -26,10 +26,28 @@ public interface Tool { */ String[] getMenuOptions(); + /** + * 命令行模式下的命令名,如 "sql", "scan", "batch" + * 返回空字符串表示不支持命令行模式 + */ + default String getCommandName() { + return ""; + } + /** * 执行工具的交互会话 * * @param scanner 控制台输入 */ void execute(Scanner scanner); + + /** + * 命令行模式执行(无交互) + * 默认实现打印不支持提示 + * + * @param args 命令行参数(不含命令名本身) + */ + default void executeCommand(String... args) throws Exception { + System.out.println("工具 [" + getName() + "] 不支持命令行模式"); + } } diff --git a/src/main/java/com/example/workutils/tool/ToolRegistry.java b/src/main/java/com/example/workutils/tool/ToolRegistry.java index 2f60fa7..5726b47 100644 --- a/src/main/java/com/example/workutils/tool/ToolRegistry.java +++ b/src/main/java/com/example/workutils/tool/ToolRegistry.java @@ -20,6 +20,7 @@ public class ToolRegistry { private final WorkUtilsProperties properties; private List enabledTools; private Map toolByIdMap; + private Map toolByCommandMap; /** * Spring自动注入所有Tool Bean @@ -35,12 +36,17 @@ public class ToolRegistry { Set enabledIds = resolveEnabledIds(); this.enabledTools = new ArrayList<>(); this.toolByIdMap = new LinkedHashMap<>(); + this.toolByCommandMap = new LinkedHashMap<>(); for (Tool tool : allTools) { try { if (enabledIds == null || enabledIds.contains(tool.getId())) { enabledTools.add(tool); toolByIdMap.put(tool.getId(), tool); + String cmd = tool.getCommandName(); + if (cmd != null && !cmd.isEmpty()) { + toolByCommandMap.put(cmd, tool); + } log.info("注册工具: {} [{}]", tool.getName(), tool.getId()); } else { log.info("跳过已禁用工具: {} [{}]", tool.getName(), tool.getId()); @@ -68,6 +74,13 @@ public class ToolRegistry { return Optional.ofNullable(toolByIdMap.get(id)); } + /** + * 按命令行命令名查找工具 + */ + public Optional getToolByCommand(String command) { + return Optional.ofNullable(toolByCommandMap.get(command)); + } + /** * 根据配置解析启用的工具ID集合 * 优先级: enabled白名单 > disabled黑名单 > 全部启用 diff --git a/项目目录结构说明.md b/项目目录结构说明.md index 7ebeb92..ee5c331 100644 --- a/项目目录结构说明.md +++ b/项目目录结构说明.md @@ -37,13 +37,14 @@ workutils/ ``` src/main/java/com/example/workutils/ -├── WorkUtilsAppliction.java # 应用启动入口 +├── WorkUtilsApplication.java # 应用启动入口 ├── config/ │ └── WorkUtilsProperties.java # 全局配置属性类 ├── tool/ │ ├── Tool.java # 工具策略接口 │ ├── AbstractTool.java # 工具基类(模板方法模式) -│ └── ToolRegistry.java # 工具注册中心 +│ ├── ToolRegistry.java # 工具注册中心 +│ └── ConsoleHelper.java # 控制台交互工具类(消除重复I/O代码) ├── menu/ │ └── api/ │ └── MenuRunner.java # 交互式菜单运行器 @@ -104,12 +105,12 @@ src/main/java/com/example/workutils/ ### 3.1 入口层 -#### `WorkUtilsAppliction.java` +#### `WorkUtilsApplication.java` - **作用**: Spring Boot 应用启动类,实现 `CommandLineRunner` - **功能**: - 支持交互式菜单模式(无参数启动) - - 支持命令行模式(`sql`、`scan` 命令) - - 通过 `MenuRunner` 动态构建主菜单 + - 支持命令行模式,通过 `ToolRegistry.getToolByCommand()` 按命令名分发到对应 Tool + - 不依赖任何具体业务 Service,完全解耦 #### `WorkUtilsProperties.java` - **作用**: 全局配置属性类,绑定 `application.yml` 中 `workutils` 前缀的配置 @@ -124,15 +125,20 @@ src/main/java/com/example/workutils/ #### `Tool.java` - **作用**: 工具策略接口,所有可插拔工具需实现此接口 -- **核心方法**: `getId()`、`getName()`、`getMenuOptions()`、`execute()` +- **核心方法**: `getId()`、`getName()`、`getMenuOptions()`、`execute()`、`getCommandName()`(命令行模式命令名)、`executeCommand()`(命令行模式执行) #### `AbstractTool.java` - **作用**: 工具基类,使用模板方法模式 - **功能**: 提供标准菜单交互循环(显示菜单→读取选择→处理选择→循环),子类只需实现 `handleChoice()` +- **控制台I/O**: 委托给 `ConsoleHelper`,子类通过 `printMenuTitle()`、`displayMenu()` 等 protected 方法间接调用 + +#### `ConsoleHelper.java` +- **作用**: 控制台交互工具类(静态方法) +- **功能**: 统一 `MenuRunner` 和 `AbstractTool` 中重复的控制台 I/O 方法,消除代码重复 #### `ToolRegistry.java` - **作用**: 工具注册中心,Spring 自动发现 `Tool` Bean -- **功能**: 根据配置过滤启用/禁用工具,支持白名单(enabled)和黑名单(disabled)模式 +- **功能**: 根据配置过滤启用/禁用工具,支持白名单(enabled)和黑名单(disabled)模式,同时维护按命令名(`getCommandName()`)的索引,支持命令行模式按命令名分发 --- @@ -224,7 +230,7 @@ src/main/resources/ | 模式/特点 | 应用位置 | 说明 | |-----------|---------|------| -| **策略模式** | `Tool` + `AbstractTool` + Handler 实现 | 各工具通过实现 `Tool` 接口注册,可插拔扩展 | +| **策略模式** | `Tool` + `AbstractTool` + Menu 实现 | 各工具通过实现 `Tool` 接口注册,可插拔扩展,支持交互和命令行双模式 | | **模板方法模式** | `AbstractTool` | 定义菜单交互骨架,子类实现 `handleChoice()` | | **策略模式** | `GitCommandStrategy` + 各 Strategy 实现 | Git 命令执行策略,`GitStrategyFactory` 管理注册 | | **工厂模式** | `ToolRegistry`、`GitStrategyFactory` | 自动发现和注册 Bean | @@ -233,24 +239,27 @@ src/main/resources/ | **并发处理** | `GitCommandExecutor`、`ScanService` | 线程池并发执行 Git 命令和多项目扫描 | | **过滤器模式** | `MyBatisExportService` 内部类 | `SqlFilter` 接口实现表名/关键字/标签ID 过滤 | | **模板回调** | `FileOperationService.scanFilesRecursively` | 使用 `SimpleFileVisitor` 实现文件遍历回调 | +| **工具类提取** | `ConsoleHelper` | 提取 `MenuRunner` 和 `AbstractTool` 中的重复控制台 I/O 代码 | --- ## 六、依赖关系图 ``` -WorkUtilsAppliction (入口) - ├── MenuRunner (菜单系统) - │ └── ToolRegistry (工具注册) - │ ├── BatchMenu ──────────────→ ScanService + MyBatisExportService +WorkUtilsApplication (入口) + ├── 交互模式 → MenuRunner + │ └── ToolRegistry.getEnabledTools() │ ├── GitMenu ────────────────→ GitService │ ├── MyBatisMenu ────────────→ MyBatisExportService │ ├── MyBatisGeneratorMenu ───→ MyBatisGeneratorService - │ └── ScanMenu ───────────────→ ScanService + │ ├── ScanMenu ───────────────→ ScanService + │ └── BatchMenu ──────────────→ ScanService + MyBatisExportService │ - └── 命令行模式 - ├── sql → MyBatisExportService - └── scan → ScanService + └── 命令行模式 → ToolRegistry.getToolByCommand(command) + ├── "sql" → MyBatisMenu.executeCommand(args) + ├── "scan" → ScanMenu.executeCommand(args) + └── "batch" → BatchMenu.executeCommand(args) + (启动类不依赖任何具体业务Service) 核心依赖链: MyBatisExportService → FileOperationService + OutputService