代码优化

This commit is contained in:
张家训
2026-06-08 18:04:23 +08:00
parent f5292ffa19
commit 213436c989
10 changed files with 161 additions and 266 deletions
@@ -1,8 +1,7 @@
package com.example.workutils;
import com.example.workutils.system.menu.MenuRunner;
import com.example.workutils.system.tool.Tool;
import com.example.workutils.system.tool.ToolRegistry;
import com.example.workutils.system.tool.Registry;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
@@ -17,12 +16,10 @@ import java.util.Scanner;
@Slf4j
public class WorkUtilsApplication implements CommandLineRunner {
private final MenuRunner menuRunner;
private final ToolRegistry toolRegistry;
private final Registry registry;
public WorkUtilsApplication(MenuRunner menuRunner, ToolRegistry toolRegistry) {
this.menuRunner = menuRunner;
this.toolRegistry = toolRegistry;
public WorkUtilsApplication(Registry registry) {
this.registry = registry;
}
public static void main(String[] args) {
@@ -40,7 +37,7 @@ public class WorkUtilsApplication implements CommandLineRunner {
private void showInteractiveMenu() {
Scanner scanner = new Scanner(System.in);
menuRunner.run(scanner);
registry.runMainMenu(scanner);
}
/**
@@ -50,7 +47,7 @@ public class WorkUtilsApplication implements CommandLineRunner {
private void processCommand(String... args) throws Exception {
String command = args[0].toLowerCase();
Optional<Tool> toolOpt = toolRegistry.getToolByCommand(command);
Optional<Tool> toolOpt = registry.getToolByCommand(command);
if (toolOpt.isPresent()) {
Tool tool = toolOpt.get();
try {
@@ -2,7 +2,7 @@ package com.example.workutils.biz.git.menu;
import com.example.workutils.biz.git.service.GitService;
import com.example.workutils.biz.git.util.GitCommandExecutor;
import com.example.workutils.system.tool.AbstractTool;
import com.example.workutils.system.tool.Tool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -19,7 +19,7 @@ import java.util.stream.Collectors;
@Slf4j
@Component
@RequiredArgsConstructor
public class GitMenu extends AbstractTool {
public class GitMenu implements Tool {
private static final String[] MENU_OPTIONS = {
"1. 检测所有仓库健康状态",
@@ -45,7 +45,7 @@ public class GitMenu extends AbstractTool {
}
@Override
protected void handleChoice(int choice, Scanner scanner) throws Exception {
public void handleChoice(int choice, Scanner scanner) throws Exception {
switch (choice) {
case 1:
checkAllReposHealth(scanner);
@@ -61,13 +61,13 @@ public class GitMenu extends AbstractTool {
private void checkAllReposHealth(Scanner scanner) {
printMenuTitle("开始检测所有仓库");
info("开始检测所有仓库健康状态");
log.info("开始检测所有仓库健康状态");
List<GitCommandExecutor.RepoHealthResult> results = gitService.checkCorruptedRepos(scanner);
if (results.isEmpty()) {
System.out.println("所有仓库状态正常!");
info("所有仓库状态正常");
log.info("所有仓库状态正常");
} else {
System.out.println("\n发现 " + results.size() + " 个损坏的仓库:");
for (int i = 0; i < results.size(); i++) {
@@ -76,7 +76,7 @@ public class GitMenu extends AbstractTool {
extractProjectName(result.getRepoPath()),
result.getCorruptionReason());
}
info("检测完成,发现 {} 个损坏仓库", results.size());
log.info("检测完成,发现 {} 个损坏仓库", results.size());
}
}
@@ -108,11 +108,11 @@ public class GitMenu extends AbstractTool {
String confirm = scanner.nextLine().trim();
if (!"y".equalsIgnoreCase(confirm)) {
System.out.println("已取消修复操作。");
info("用户取消修复操作");
log.info("用户取消修复操作");
return;
}
info("开始修复 {} 个仓库", selectedIndices.size());
log.info("开始修复 {} 个仓库", selectedIndices.size());
int successCount = 0;
int failCount = 0;
for (Integer index : selectedIndices) {
@@ -132,7 +132,7 @@ public class GitMenu extends AbstractTool {
printMenuTitle("修复完成");
System.out.println("成功: " + successCount);
System.out.println("失败: " + failCount);
info("修复完成,成功: {},失败: {}", successCount, failCount);
log.info("修复完成,成功: {},失败: {}", successCount, failCount);
}
private List<Integer> parseSelectedIndices(String input, int repoCount) {
@@ -1,7 +1,7 @@
package com.example.workutils.biz.mybatis.menu;
import com.example.workutils.biz.mybatis.generator.MyBatisGeneratorService;
import com.example.workutils.system.tool.AbstractTool;
import com.example.workutils.system.tool.Tool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -17,7 +17,7 @@ import java.util.Scanner;
@Slf4j
@Component
@RequiredArgsConstructor
public class MyBatisGeneratorMenu extends AbstractTool {
public class MyBatisGeneratorMenu implements Tool {
private static final String[] MENU_OPTIONS = {
"1. 根据表名生成(单表)",
@@ -46,7 +46,7 @@ public class MyBatisGeneratorMenu extends AbstractTool {
}
@Override
protected void handleChoice(int choice, Scanner scanner) throws Exception {
public void handleChoice(int choice, Scanner scanner) throws Exception {
switch (choice) {
case 1:
generateBySingleTable(scanner);
@@ -78,7 +78,7 @@ public class MyBatisGeneratorMenu extends AbstractTool {
return;
}
info("开始生成表 [{}] 的Mapper文件", tableName);
log.info("开始生成表 [{}] 的Mapper文件", tableName);
String result = generatorService.generateByTableNames(Arrays.asList(tableName), true);
System.out.println(result);
printSuccess("生成完成");
@@ -98,7 +98,7 @@ public class MyBatisGeneratorMenu extends AbstractTool {
tableNames.set(i, tableNames.get(i).trim());
}
info("开始生成 {} 个表的Mapper文件", tableNames.size());
log.info("开始生成 {} 个表的Mapper文件", tableNames.size());
String result = generatorService.generateByTableNames(tableNames, true);
System.out.println(result);
printSuccess("生成完成");
@@ -131,7 +131,7 @@ public class MyBatisGeneratorMenu extends AbstractTool {
return;
}
info("开始根据通配符 [{}] 生成 {} 张表的Mapper文件", pattern, matchedTables.size());
log.info("开始根据通配符 [{}] 生成 {} 张表的Mapper文件", pattern, matchedTables.size());
String result = generatorService.generateByTableNames(matchedTables, true);
System.out.println(result);
printSuccess("生成完成");
@@ -1,7 +1,7 @@
package com.example.workutils.biz.mybatis.menu;
import com.example.workutils.biz.mybatis.export.MyBatisExportService;
import com.example.workutils.system.tool.AbstractTool;
import com.example.workutils.system.tool.Tool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -16,7 +16,7 @@ import java.util.Scanner;
@Slf4j
@Component
@RequiredArgsConstructor
public class MyBatisMenu extends AbstractTool {
public class MyBatisMenu implements Tool {
private static final String[] MENU_OPTIONS = {
"1. 导出所有SQL",
@@ -85,7 +85,7 @@ public class MyBatisMenu extends AbstractTool {
}
@Override
protected void handleChoice(int choice, Scanner scanner) throws Exception {
public void handleChoice(int choice, Scanner scanner) throws Exception {
switch (choice) {
case 1:
exportAllSql();
@@ -110,7 +110,7 @@ public class MyBatisMenu extends AbstractTool {
private void exportAllSql() throws Exception {
printMenuTitle("导出所有SQL");
info("开始导出所有SQL");
log.info("开始导出所有SQL");
myBatisExportService.exportSQL();
printSuccess("导出所有SQL");
}
@@ -122,7 +122,7 @@ public class MyBatisMenu extends AbstractTool {
System.out.println("表名不能为空");
return;
}
info("按表名导出SQL: {}", tableName);
log.info("按表名导出SQL: {}", tableName);
myBatisExportService.exportSQLByTable(tableName);
printSuccess("按表名导出SQL");
}
@@ -134,7 +134,7 @@ public class MyBatisMenu extends AbstractTool {
System.out.println("表名不能为空");
return;
}
info("按多个表名导出SQL: {}", tableNames);
log.info("按多个表名导出SQL: {}", tableNames);
myBatisExportService.exportSQLByTables(Arrays.asList(tableNames.split(",")));
printSuccess("按多个表名导出SQL");
}
@@ -146,7 +146,7 @@ public class MyBatisMenu extends AbstractTool {
System.out.println("关键字不能为空");
return;
}
info("按关键字导出SQL: {}", keyword);
log.info("按关键字导出SQL: {}", keyword);
myBatisExportService.exportSQLByKeyword(keyword);
printSuccess("按关键字导出SQL");
}
@@ -158,7 +158,7 @@ public class MyBatisMenu extends AbstractTool {
System.out.println("标签ID不能为空");
return;
}
info("按标签ID导出SQL: {}", tagId);
log.info("按标签ID导出SQL: {}", tagId);
myBatisExportService.exportSQLByTagId(tagId);
printSuccess("按标签ID导出SQL");
}
@@ -2,7 +2,7 @@ package com.example.workutils.biz.output.menu;
import com.example.workutils.biz.mybatis.export.MyBatisExportService;
import com.example.workutils.biz.scanner.service.ScanService;
import com.example.workutils.system.tool.AbstractTool;
import com.example.workutils.system.tool.Tool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -18,7 +18,7 @@ import java.util.Scanner;
@Slf4j
@Component
@RequiredArgsConstructor
public class BatchMenu extends AbstractTool {
public class BatchMenu implements Tool {
@FunctionalInterface
public interface ExceptionRunnable {
@@ -63,13 +63,13 @@ public class BatchMenu extends AbstractTool {
@Override
public void executeCommand(String... args) throws Exception {
info("命令行模式: 开始执行所有检查");
log.info("命令行模式: 开始执行所有检查");
executeAllChecks();
System.out.println("批量处理完成!");
}
@Override
protected void handleChoice(int choice, Scanner scanner) throws Exception {
public void handleChoice(int choice, Scanner scanner) throws Exception {
switch (choice) {
case 1:
executeAllChecks();
@@ -85,7 +85,7 @@ public class BatchMenu extends AbstractTool {
private void executeAllChecks() throws Exception {
printMenuTitle("执行所有检查");
info("开始执行所有检查");
log.info("开始执行所有检查");
executeStep("1. 导出SQL...", () -> myBatisExportService.exportSQL());
executeStep("2. 扫描Dubbo接口...",
@@ -94,7 +94,7 @@ public class BatchMenu extends AbstractTool {
() -> scanService.scanRedisKeysWithDefaultName("redis\\.opsFor"));
printSuccess("所有检查");
info("所有检查执行完成");
log.info("所有检查执行完成");
}
private void executeCustomChecks(Scanner scanner) throws Exception {
@@ -105,7 +105,7 @@ public class BatchMenu extends AbstractTool {
String choices = scanner.nextLine().trim();
List<String> choiceList = parseCommaSeparatedInput(choices);
info("用户选择执行检查项: {}", choiceList);
log.info("用户选择执行检查项: {}", choiceList);
for (String choice : choiceList) {
try {
@@ -132,7 +132,7 @@ public class BatchMenu extends AbstractTool {
break;
}
} catch (Exception e) {
error("执行检查项失败: " + choice, e);
log.error("执行检查项失败: " + choice, e);
System.out.println("检查项 " + choice + " 执行失败: " + e.getMessage());
}
}
@@ -1,7 +1,7 @@
package com.example.workutils.biz.scanner.menu;
import com.example.workutils.biz.scanner.service.ScanService;
import com.example.workutils.system.tool.AbstractTool;
import com.example.workutils.system.tool.Tool;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -15,7 +15,7 @@ import java.util.Scanner;
@Slf4j
@Component
@RequiredArgsConstructor
public class ScanMenu extends AbstractTool {
public class ScanMenu implements Tool {
private static final String[] MENU_OPTIONS = {
"1. 关键词扫描",
@@ -75,7 +75,7 @@ public class ScanMenu extends AbstractTool {
}
@Override
protected void handleChoice(int choice, Scanner scanner) throws Exception {
public void handleChoice(int choice, Scanner scanner) throws Exception {
switch (choice) {
case 1:
scanByKeyword(scanner);
@@ -88,7 +88,7 @@ public class ScanMenu extends AbstractTool {
private void scanByKeyword(Scanner scanner) throws Exception {
printMenuTitle("关键词扫描");
info("开始关键词扫描");
log.info("开始关键词扫描");
System.out.print("请输入关键词: ");
String keyword = scanner.nextLine().trim();
@@ -98,7 +98,7 @@ public class ScanMenu extends AbstractTool {
return;
}
info("扫描关键词: {}", keyword);
log.info("扫描关键词: {}", keyword);
scanService.scanKeywordsWithDefaultName(keyword);
printSuccess("关键词扫描");
@@ -1,82 +0,0 @@
package com.example.workutils.system.menu;
import com.example.workutils.system.ConsoleHelper;
import com.example.workutils.system.tool.Tool;
import com.example.workutils.system.tool.ToolRegistry;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.util.List;
import java.util.Scanner;
/**
* 菜单运行器
* 从ToolRegistry动态构建主菜单,无需硬编码Menu引用
*/
@Slf4j
@Component
public class MenuRunner {
private static final int EXIT_CODE = 0;
private final ToolRegistry toolRegistry;
public MenuRunner(ToolRegistry toolRegistry) {
this.toolRegistry = toolRegistry;
}
/**
* 运行主菜单循环
*/
public void run(Scanner scanner) {
log.info("启动主菜单");
while (true) {
ConsoleHelper.printMenuTitle("主菜单");
displayMainMenu();
int choice = ConsoleHelper.readChoice(scanner);
scanner.nextLine();
if (choice == EXIT_CODE) {
log.info("用户退出系统");
System.out.println("感谢使用,再见!");
break;
}
handleChoice(choice, scanner);
}
}
/**
* 动态构建主菜单
*/
private void displayMainMenu() {
System.out.println("请选择操作:");
List<Tool> tools = toolRegistry.getEnabledTools();
for (int i = 0; i < tools.size(); i++) {
System.out.println((i + 1) + ". " + tools.get(i).getName());
}
System.out.println(EXIT_CODE + ". 退出系统");
}
/**
* 处理用户选择,工具隔离
* 单个工具异常不影响平台和其他工具
*/
private void handleChoice(int choice, Scanner scanner) {
List<Tool> tools = toolRegistry.getEnabledTools();
int index = choice - 1;
if (index >= 0 && index < tools.size()) {
Tool tool = tools.get(index);
try {
tool.execute(scanner);
} catch (Exception e) {
log.error("工具执行失败: {} [{}]", tool.getName(), tool.getId(), e);
System.out.println("工具执行失败: " + e.getMessage());
}
} else {
ConsoleHelper.printInvalidOption();
}
}
}
@@ -1,89 +0,0 @@
package com.example.workutils.system.tool;
import com.example.workutils.system.ConsoleHelper;
import lombok.extern.slf4j.Slf4j;
import java.util.Scanner;
/**
* 工具基类
* 提供共享的菜单循环模板方法,消除各Menu中的重复代码
* 子类只需实现handleChoice()定义具体交互逻辑
*
* 控制台I/O方法委托给ConsoleHelper,日志方法保留在此类
*/
@Slf4j
public abstract class AbstractTool implements Tool {
/**
* 模板方法:标准菜单交互循环
* displayMenu → readChoice → if 0 return → handleChoice → repeat
*/
@Override
public void execute(Scanner scanner) {
while (true) {
ConsoleHelper.printMenuTitle(getName());
ConsoleHelper.displayMenu(getMenuOptions());
int choice = ConsoleHelper.readChoice(scanner);
scanner.nextLine();
if (choice == 0) {
info("返回上级菜单");
return;
}
try {
handleChoice(choice, scanner);
} catch (Exception e) {
error(getName() + "执行失败", e);
ConsoleHelper.printError(e.getMessage());
}
}
}
/**
* 处理子菜单选择,子类实现具体switch-case逻辑
*/
protected abstract void handleChoice(int choice, Scanner scanner) throws Exception;
// ============ 日志方法 ============
protected void info(String message) {
log.info(message);
}
protected void info(String message, Object... args) {
log.info(message, args);
}
protected void error(String message) {
log.error(message);
}
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();
}
}
@@ -1,5 +1,6 @@
package com.example.workutils.system.tool;
import com.example.workutils.system.ConsoleHelper;
import com.example.workutils.system.config.WorkUtilsProperties;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -10,11 +11,14 @@ import java.util.stream.Collectors;
/**
* 工具注册中心
* 自动发现Spring容器中的Tool Bean,按配置过滤启用/禁用
* 自动发现 Spring 容器中的 Tool Bean,按配置过滤启用/禁用
* 同时负责主菜单循环(从 MenuRunner 合并)
*/
@Slf4j
@Component
public class ToolRegistry {
public class Registry {
private static final int EXIT_CODE = 0;
private final List<Tool> allTools;
private final WorkUtilsProperties properties;
@@ -22,11 +26,7 @@ public class ToolRegistry {
private Map<String, Tool> toolByIdMap;
private Map<String, Tool> toolByCommandMap;
/**
* Spring自动注入所有Tool Bean
* 新增工具只需创建@Component类实现Tool接口,无需修改此类
*/
public ToolRegistry(List<Tool> tools, WorkUtilsProperties properties) {
public Registry(List<Tool> tools, WorkUtilsProperties properties) {
this.allTools = tools;
this.properties = properties;
}
@@ -52,7 +52,6 @@ public class ToolRegistry {
log.info("跳过已禁用工具: {} [{}]", tool.getName(), tool.getId());
}
} catch (Exception e) {
// 工具隔离:单个工具初始化异常不影响平台和其他工具
log.error("工具初始化失败: {} [{}]", tool.getName(), tool.getId(), e);
}
}
@@ -60,33 +59,66 @@ public class ToolRegistry {
log.info("工具注册完成: {}/{} 已启用", enabledTools.size(), allTools.size());
}
/**
* 获取所有已启用工具
*/
// ==================== 主菜单(从 MenuRunner 合并) ====================
public void runMainMenu(Scanner scanner) {
log.info("启动主菜单");
while (true) {
ConsoleHelper.printMenuTitle("主菜单");
displayMainMenu();
int choice = ConsoleHelper.readChoice(scanner);
scanner.nextLine();
if (choice == EXIT_CODE) {
log.info("用户退出系统");
System.out.println("感谢使用,再见!");
break;
}
dispatchTool(choice, scanner);
}
}
private void displayMainMenu() {
System.out.println("请选择操作:");
for (int i = 0; i < enabledTools.size(); i++) {
System.out.println((i + 1) + ". " + enabledTools.get(i).getName());
}
System.out.println(EXIT_CODE + ". 退出系统");
}
private void dispatchTool(int choice, Scanner scanner) {
int index = choice - 1;
if (index >= 0 && index < enabledTools.size()) {
Tool tool = enabledTools.get(index);
try {
tool.execute(scanner);
} catch (Exception e) {
log.error("工具执行失败: {} [{}]", tool.getName(), tool.getId(), e);
System.out.println("工具执行失败: " + e.getMessage());
}
} else {
ConsoleHelper.printInvalidOption();
}
}
// ==================== 查找方法 ====================
public List<Tool> getEnabledTools() {
return Collections.unmodifiableList(enabledTools);
}
/**
* 按ID查找工具
*/
public Optional<Tool> getToolById(String id) {
return Optional.ofNullable(toolByIdMap.get(id));
}
/**
* 按命令行命令名查找工具
*/
public Optional<Tool> getToolByCommand(String command) {
return Optional.ofNullable(toolByCommandMap.get(command));
}
/**
* 根据配置解析启用的工具ID集合
* 优先级: enabled白名单 > disabled黑名单 > 全部启用
*
* @return null表示全部启用,否则为启用的ID集合
*/
// ==================== 启用/禁用解析 ====================
private Set<String> resolveEnabledIds() {
WorkUtilsProperties.ToolsConfig toolsConfig = properties.getTools();
if (toolsConfig == null) {
@@ -1,53 +1,90 @@
package com.example.workutils.system.tool;
import com.example.workutils.system.ConsoleHelper;
import java.util.Scanner;
/**
* 工具策略接口
* 所有可插拔工具实现此接口,Spring自动发现Tool Bean
* 所有可插拔工具实现此接口,Spring 自动发现 Tool Bean
* <p>
* 提供交互式菜单(execute)和命令行(executeCommand)两种执行模式。
* 交互模式由默认 execute() 模板方法驱动,子类实现 handleChoice() 定义选择逻辑。
*/
public interface Tool {
/**
* 工具唯一标识(用于配置启用/禁用)
* 示例: "scan", "mybatis", "batch", "git-health", "mybatis-generator"
*/
/** 工具唯一标识(如 "scan", "mybatis") */
String getId();
/**
* 菜单显示名称
* 示例: "关键词扫描", "MyBatis导出"
*/
/** 菜单显示名称(如 "关键词扫描", "MyBatis SQL导出") */
String getName();
/**
* 子菜单选项
* 返回空数组表示无子菜单
*/
/** 子菜单选项,返回空数组表示无子菜单 */
String[] getMenuOptions();
/**
* 命令行模式下的命令名,如 "sql", "scan", "batch"
* 返回空字符串表示不支持命令行模式
*/
/** 命令行命令名(如 "sql", "scan"),返回空字符串表示不支持命令行模式 */
default String getCommandName() {
return "";
}
/**
* 执行工具的交互会话
*
* @param scanner 控制台输入
*/
void execute(Scanner scanner);
// ==================== 交互模式(模板方法) ====================
/**
* 命令行模式执行(无交互)
* 默认实现打印不支持提示
*
* @param args 命令行参数(不含命令名本身)
* 交互式菜单循环(默认实现,从 AbstractTool 合并而来)
* displayMenu → readChoice → if 0 return → handleChoice → repeat
*/
default void execute(Scanner scanner) {
while (true) {
ConsoleHelper.printMenuTitle(getName());
ConsoleHelper.displayMenu(getMenuOptions());
int choice = ConsoleHelper.readChoice(scanner);
scanner.nextLine();
if (choice == 0) {
return;
}
try {
handleChoice(choice, scanner);
} catch (Exception e) {
ConsoleHelper.printError(e.getMessage());
}
}
}
/** 处理子菜单选择,子类实现具体分发逻辑 */
default void handleChoice(int choice, Scanner scanner) throws Exception {
}
// ==================== 命令行模式 ====================
/**
* 命令行模式执行(无交互),默认打印不支持提示
*/
default void executeCommand(String... args) throws Exception {
System.out.println("工具 [" + getName() + "] 不支持命令行模式");
}
// ==================== 控制台输出辅助方法 ====================
default void printMenuTitle(String title) {
ConsoleHelper.printMenuTitle(title);
}
default void displayMenu(String[] options) {
ConsoleHelper.displayMenu(options);
}
default void printSuccess(String operation) {
ConsoleHelper.printSuccess(operation);
}
default void printError(String message) {
ConsoleHelper.printError(message);
}
default void printInvalidOption() {
ConsoleHelper.printInvalidOption();
}
}