菜单优化
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黑名单 > 全部启用
|
||||
|
||||
Reference in New Issue
Block a user