菜单优化
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导出";
|
return "MyBatis SQL导出";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCommandName() {
|
||||||
|
return "sql";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getMenuOptions() {
|
public String[] getMenuOptions() {
|
||||||
return MENU_OPTIONS;
|
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
|
@Override
|
||||||
protected void handleChoice(int choice, Scanner scanner) throws Exception {
|
protected void handleChoice(int choice, Scanner scanner) throws Exception {
|
||||||
switch (choice) {
|
switch (choice) {
|
||||||
|
|||||||
@@ -51,11 +51,23 @@ public class BatchMenu extends AbstractTool {
|
|||||||
return "批量处理";
|
return "批量处理";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCommandName() {
|
||||||
|
return "batch";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getMenuOptions() {
|
public String[] getMenuOptions() {
|
||||||
return MENU_OPTIONS;
|
return MENU_OPTIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void executeCommand(String... args) throws Exception {
|
||||||
|
info("命令行模式: 开始执行所有检查");
|
||||||
|
executeAllChecks();
|
||||||
|
System.out.println("批量处理完成!");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void handleChoice(int choice, Scanner scanner) throws Exception {
|
protected void handleChoice(int choice, Scanner scanner) throws Exception {
|
||||||
switch (choice) {
|
switch (choice) {
|
||||||
|
|||||||
@@ -34,11 +34,46 @@ public class ScanMenu extends AbstractTool {
|
|||||||
return "扫描功能";
|
return "扫描功能";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCommandName() {
|
||||||
|
return "scan";
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String[] getMenuOptions() {
|
public String[] getMenuOptions() {
|
||||||
return MENU_OPTIONS;
|
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
|
@Override
|
||||||
protected void handleChoice(int choice, Scanner scanner) throws Exception {
|
protected void handleChoice(int choice, Scanner scanner) throws Exception {
|
||||||
switch (choice) {
|
switch (choice) {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package com.example.workutils.menu.api;
|
package com.example.workutils.menu.api;
|
||||||
|
|
||||||
|
import com.example.workutils.tool.ConsoleHelper;
|
||||||
import com.example.workutils.tool.Tool;
|
import com.example.workutils.tool.Tool;
|
||||||
import com.example.workutils.tool.ToolRegistry;
|
import com.example.workutils.tool.ToolRegistry;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -10,7 +11,7 @@ import java.util.Scanner;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 菜单运行器
|
* 菜单运行器
|
||||||
* 从ToolRegistry动态构建主菜单,无需硬编码Handler引用
|
* 从ToolRegistry动态构建主菜单,无需硬编码Menu引用
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@Component
|
@Component
|
||||||
@@ -28,16 +29,16 @@ public class MenuRunner {
|
|||||||
* 运行主菜单循环
|
* 运行主菜单循环
|
||||||
*/
|
*/
|
||||||
public void run(Scanner scanner) {
|
public void run(Scanner scanner) {
|
||||||
info("启动主菜单");
|
log.info("启动主菜单");
|
||||||
while (true) {
|
while (true) {
|
||||||
printMenuTitle("主菜单");
|
ConsoleHelper.printMenuTitle("主菜单");
|
||||||
displayMainMenu();
|
displayMainMenu();
|
||||||
|
|
||||||
int choice = readChoice(scanner);
|
int choice = ConsoleHelper.readChoice(scanner);
|
||||||
scanner.nextLine();
|
scanner.nextLine();
|
||||||
|
|
||||||
if (choice == EXIT_CODE) {
|
if (choice == EXIT_CODE) {
|
||||||
info("用户退出系统");
|
log.info("用户退出系统");
|
||||||
System.out.println("感谢使用,再见!");
|
System.out.println("感谢使用,再见!");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -75,25 +76,7 @@ public class MenuRunner {
|
|||||||
System.out.println("工具执行失败: " + e.getMessage());
|
System.out.println("工具执行失败: " + e.getMessage());
|
||||||
}
|
}
|
||||||
} else {
|
} 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中的重复代码
|
* 提供共享的菜单循环模板方法,消除各Menu中的重复代码
|
||||||
* 子类只需实现handleChoice()定义具体业务逻辑
|
* 子类只需实现handleChoice()定义具体交互逻辑
|
||||||
|
*
|
||||||
|
* 控制台I/O方法委托给ConsoleHelper,日志方法保留在此类
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public abstract class AbstractTool implements Tool {
|
public abstract class AbstractTool implements Tool {
|
||||||
@@ -19,10 +21,10 @@ public abstract class AbstractTool implements Tool {
|
|||||||
@Override
|
@Override
|
||||||
public void execute(Scanner scanner) {
|
public void execute(Scanner scanner) {
|
||||||
while (true) {
|
while (true) {
|
||||||
printMenuTitle(getName());
|
ConsoleHelper.printMenuTitle(getName());
|
||||||
displayMenu(getMenuOptions());
|
ConsoleHelper.displayMenu(getMenuOptions());
|
||||||
|
|
||||||
int choice = readChoice(scanner);
|
int choice = ConsoleHelper.readChoice(scanner);
|
||||||
scanner.nextLine();
|
scanner.nextLine();
|
||||||
|
|
||||||
if (choice == 0) {
|
if (choice == 0) {
|
||||||
@@ -34,7 +36,7 @@ public abstract class AbstractTool implements Tool {
|
|||||||
handleChoice(choice, scanner);
|
handleChoice(choice, scanner);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
error(getName() + "执行失败", 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 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) {
|
protected void info(String message) {
|
||||||
log.info(message);
|
log.info(message);
|
||||||
@@ -94,4 +63,26 @@ public abstract class AbstractTool implements Tool {
|
|||||||
protected void error(String message, Exception e) {
|
protected void error(String message, Exception e) {
|
||||||
log.error(message, 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();
|
String[] getMenuOptions();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 命令行模式下的命令名,如 "sql", "scan", "batch"
|
||||||
|
* 返回空字符串表示不支持命令行模式
|
||||||
|
*/
|
||||||
|
default String getCommandName() {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 执行工具的交互会话
|
* 执行工具的交互会话
|
||||||
*
|
*
|
||||||
* @param scanner 控制台输入
|
* @param scanner 控制台输入
|
||||||
*/
|
*/
|
||||||
void execute(Scanner 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 final WorkUtilsProperties properties;
|
||||||
private List<Tool> enabledTools;
|
private List<Tool> enabledTools;
|
||||||
private Map<String, Tool> toolByIdMap;
|
private Map<String, Tool> toolByIdMap;
|
||||||
|
private Map<String, Tool> toolByCommandMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spring自动注入所有Tool Bean
|
* Spring自动注入所有Tool Bean
|
||||||
@@ -35,12 +36,17 @@ public class ToolRegistry {
|
|||||||
Set<String> enabledIds = resolveEnabledIds();
|
Set<String> enabledIds = resolveEnabledIds();
|
||||||
this.enabledTools = new ArrayList<>();
|
this.enabledTools = new ArrayList<>();
|
||||||
this.toolByIdMap = new LinkedHashMap<>();
|
this.toolByIdMap = new LinkedHashMap<>();
|
||||||
|
this.toolByCommandMap = new LinkedHashMap<>();
|
||||||
|
|
||||||
for (Tool tool : allTools) {
|
for (Tool tool : allTools) {
|
||||||
try {
|
try {
|
||||||
if (enabledIds == null || enabledIds.contains(tool.getId())) {
|
if (enabledIds == null || enabledIds.contains(tool.getId())) {
|
||||||
enabledTools.add(tool);
|
enabledTools.add(tool);
|
||||||
toolByIdMap.put(tool.getId(), 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());
|
log.info("注册工具: {} [{}]", tool.getName(), tool.getId());
|
||||||
} else {
|
} else {
|
||||||
log.info("跳过已禁用工具: {} [{}]", tool.getName(), tool.getId());
|
log.info("跳过已禁用工具: {} [{}]", tool.getName(), tool.getId());
|
||||||
@@ -68,6 +74,13 @@ public class ToolRegistry {
|
|||||||
return Optional.ofNullable(toolByIdMap.get(id));
|
return Optional.ofNullable(toolByIdMap.get(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 按命令行命令名查找工具
|
||||||
|
*/
|
||||||
|
public Optional<Tool> getToolByCommand(String command) {
|
||||||
|
return Optional.ofNullable(toolByCommandMap.get(command));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 根据配置解析启用的工具ID集合
|
* 根据配置解析启用的工具ID集合
|
||||||
* 优先级: enabled白名单 > disabled黑名单 > 全部启用
|
* 优先级: enabled白名单 > disabled黑名单 > 全部启用
|
||||||
|
|||||||
+25
-16
@@ -37,13 +37,14 @@ workutils/
|
|||||||
|
|
||||||
```
|
```
|
||||||
src/main/java/com/example/workutils/
|
src/main/java/com/example/workutils/
|
||||||
├── WorkUtilsAppliction.java # 应用启动入口
|
├── WorkUtilsApplication.java # 应用启动入口
|
||||||
├── config/
|
├── config/
|
||||||
│ └── WorkUtilsProperties.java # 全局配置属性类
|
│ └── WorkUtilsProperties.java # 全局配置属性类
|
||||||
├── tool/
|
├── tool/
|
||||||
│ ├── Tool.java # 工具策略接口
|
│ ├── Tool.java # 工具策略接口
|
||||||
│ ├── AbstractTool.java # 工具基类(模板方法模式)
|
│ ├── AbstractTool.java # 工具基类(模板方法模式)
|
||||||
│ └── ToolRegistry.java # 工具注册中心
|
│ ├── ToolRegistry.java # 工具注册中心
|
||||||
|
│ └── ConsoleHelper.java # 控制台交互工具类(消除重复I/O代码)
|
||||||
├── menu/
|
├── menu/
|
||||||
│ └── api/
|
│ └── api/
|
||||||
│ └── MenuRunner.java # 交互式菜单运行器
|
│ └── MenuRunner.java # 交互式菜单运行器
|
||||||
@@ -104,12 +105,12 @@ src/main/java/com/example/workutils/
|
|||||||
|
|
||||||
### 3.1 入口层
|
### 3.1 入口层
|
||||||
|
|
||||||
#### `WorkUtilsAppliction.java`
|
#### `WorkUtilsApplication.java`
|
||||||
- **作用**: Spring Boot 应用启动类,实现 `CommandLineRunner`
|
- **作用**: Spring Boot 应用启动类,实现 `CommandLineRunner`
|
||||||
- **功能**:
|
- **功能**:
|
||||||
- 支持交互式菜单模式(无参数启动)
|
- 支持交互式菜单模式(无参数启动)
|
||||||
- 支持命令行模式(`sql`、`scan` 命令)
|
- 支持命令行模式,通过 `ToolRegistry.getToolByCommand()` 按命令名分发到对应 Tool
|
||||||
- 通过 `MenuRunner` 动态构建主菜单
|
- 不依赖任何具体业务 Service,完全解耦
|
||||||
|
|
||||||
#### `WorkUtilsProperties.java`
|
#### `WorkUtilsProperties.java`
|
||||||
- **作用**: 全局配置属性类,绑定 `application.yml` 中 `workutils` 前缀的配置
|
- **作用**: 全局配置属性类,绑定 `application.yml` 中 `workutils` 前缀的配置
|
||||||
@@ -124,15 +125,20 @@ src/main/java/com/example/workutils/
|
|||||||
|
|
||||||
#### `Tool.java`
|
#### `Tool.java`
|
||||||
- **作用**: 工具策略接口,所有可插拔工具需实现此接口
|
- **作用**: 工具策略接口,所有可插拔工具需实现此接口
|
||||||
- **核心方法**: `getId()`、`getName()`、`getMenuOptions()`、`execute()`
|
- **核心方法**: `getId()`、`getName()`、`getMenuOptions()`、`execute()`、`getCommandName()`(命令行模式命令名)、`executeCommand()`(命令行模式执行)
|
||||||
|
|
||||||
#### `AbstractTool.java`
|
#### `AbstractTool.java`
|
||||||
- **作用**: 工具基类,使用模板方法模式
|
- **作用**: 工具基类,使用模板方法模式
|
||||||
- **功能**: 提供标准菜单交互循环(显示菜单→读取选择→处理选择→循环),子类只需实现 `handleChoice()`
|
- **功能**: 提供标准菜单交互循环(显示菜单→读取选择→处理选择→循环),子类只需实现 `handleChoice()`
|
||||||
|
- **控制台I/O**: 委托给 `ConsoleHelper`,子类通过 `printMenuTitle()`、`displayMenu()` 等 protected 方法间接调用
|
||||||
|
|
||||||
|
#### `ConsoleHelper.java`
|
||||||
|
- **作用**: 控制台交互工具类(静态方法)
|
||||||
|
- **功能**: 统一 `MenuRunner` 和 `AbstractTool` 中重复的控制台 I/O 方法,消除代码重复
|
||||||
|
|
||||||
#### `ToolRegistry.java`
|
#### `ToolRegistry.java`
|
||||||
- **作用**: 工具注册中心,Spring 自动发现 `Tool` Bean
|
- **作用**: 工具注册中心,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()` |
|
| **模板方法模式** | `AbstractTool` | 定义菜单交互骨架,子类实现 `handleChoice()` |
|
||||||
| **策略模式** | `GitCommandStrategy` + 各 Strategy 实现 | Git 命令执行策略,`GitStrategyFactory` 管理注册 |
|
| **策略模式** | `GitCommandStrategy` + 各 Strategy 实现 | Git 命令执行策略,`GitStrategyFactory` 管理注册 |
|
||||||
| **工厂模式** | `ToolRegistry`、`GitStrategyFactory` | 自动发现和注册 Bean |
|
| **工厂模式** | `ToolRegistry`、`GitStrategyFactory` | 自动发现和注册 Bean |
|
||||||
@@ -233,24 +239,27 @@ src/main/resources/
|
|||||||
| **并发处理** | `GitCommandExecutor`、`ScanService` | 线程池并发执行 Git 命令和多项目扫描 |
|
| **并发处理** | `GitCommandExecutor`、`ScanService` | 线程池并发执行 Git 命令和多项目扫描 |
|
||||||
| **过滤器模式** | `MyBatisExportService` 内部类 | `SqlFilter` 接口实现表名/关键字/标签ID 过滤 |
|
| **过滤器模式** | `MyBatisExportService` 内部类 | `SqlFilter` 接口实现表名/关键字/标签ID 过滤 |
|
||||||
| **模板回调** | `FileOperationService.scanFilesRecursively` | 使用 `SimpleFileVisitor` 实现文件遍历回调 |
|
| **模板回调** | `FileOperationService.scanFilesRecursively` | 使用 `SimpleFileVisitor` 实现文件遍历回调 |
|
||||||
|
| **工具类提取** | `ConsoleHelper` | 提取 `MenuRunner` 和 `AbstractTool` 中的重复控制台 I/O 代码 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 六、依赖关系图
|
## 六、依赖关系图
|
||||||
|
|
||||||
```
|
```
|
||||||
WorkUtilsAppliction (入口)
|
WorkUtilsApplication (入口)
|
||||||
├── MenuRunner (菜单系统)
|
├── 交互模式 → MenuRunner
|
||||||
│ └── ToolRegistry (工具注册)
|
│ └── ToolRegistry.getEnabledTools()
|
||||||
│ ├── BatchMenu ──────────────→ ScanService + MyBatisExportService
|
|
||||||
│ ├── GitMenu ────────────────→ GitService
|
│ ├── GitMenu ────────────────→ GitService
|
||||||
│ ├── MyBatisMenu ────────────→ MyBatisExportService
|
│ ├── MyBatisMenu ────────────→ MyBatisExportService
|
||||||
│ ├── MyBatisGeneratorMenu ───→ MyBatisGeneratorService
|
│ ├── MyBatisGeneratorMenu ───→ MyBatisGeneratorService
|
||||||
│ └── ScanMenu ───────────────→ ScanService
|
│ ├── ScanMenu ───────────────→ ScanService
|
||||||
|
│ └── BatchMenu ──────────────→ ScanService + MyBatisExportService
|
||||||
│
|
│
|
||||||
└── 命令行模式
|
└── 命令行模式 → ToolRegistry.getToolByCommand(command)
|
||||||
├── sql → MyBatisExportService
|
├── "sql" → MyBatisMenu.executeCommand(args)
|
||||||
└── scan → ScanService
|
├── "scan" → ScanMenu.executeCommand(args)
|
||||||
|
└── "batch" → BatchMenu.executeCommand(args)
|
||||||
|
(启动类不依赖任何具体业务Service)
|
||||||
|
|
||||||
核心依赖链:
|
核心依赖链:
|
||||||
MyBatisExportService → FileOperationService + OutputService
|
MyBatisExportService → FileOperationService + OutputService
|
||||||
|
|||||||
Reference in New Issue
Block a user