菜单优化
This commit is contained in:
@@ -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<Tool> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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 <type> <pattern> [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 <keyword|redis|dubbo> <pattern> [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 # 执行所有检查");
|
||||
}
|
||||
}
|
||||
@@ -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<String> 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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 <type> <pattern> [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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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("无效选项");
|
||||
}
|
||||
}
|
||||
@@ -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() + "] 不支持命令行模式");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ public class ToolRegistry {
|
||||
private final WorkUtilsProperties properties;
|
||||
private List<Tool> enabledTools;
|
||||
private Map<String, Tool> toolByIdMap;
|
||||
private Map<String, Tool> toolByCommandMap;
|
||||
|
||||
/**
|
||||
* Spring自动注入所有Tool Bean
|
||||
@@ -35,12 +36,17 @@ public class ToolRegistry {
|
||||
Set<String> 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<Tool> getToolByCommand(String command) {
|
||||
return Optional.ofNullable(toolByCommandMap.get(command));
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据配置解析启用的工具ID集合
|
||||
* 优先级: enabled白名单 > disabled黑名单 > 全部启用
|
||||
|
||||
+25
-16
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user