菜单优化

This commit is contained in:
张家训
2026-06-08 17:40:57 +08:00
parent ecdfb1f1a1
commit b76a2a451e
11 changed files with 295 additions and 223 deletions
@@ -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黑名单 > 全部启用