代码优化提交

This commit is contained in:
张家训
2026-06-09 14:38:36 +08:00
parent b964fef4dd
commit c5773063d7
16 changed files with 159 additions and 182 deletions
@@ -41,19 +41,18 @@ public class WorkUtilsApplication implements CommandLineRunner {
}
/**
* 命令行模式:通过 PluginManager 按命令名分发到对应 Plugin
* 命令行模式:通过 PluginManager 按命令名分发到对应 Tool
* 启动类不依赖任何具体业务 Service
*/
private void processCommand(String... args) throws Exception {
String command = args[0].toLowerCase();
Optional<Plugin> pluginOpt = pluginManager.getPluginByCommand(command);
if (pluginOpt.isPresent()) {
Plugin plugin = pluginOpt.get();
Optional<Plugin> toolOpt = pluginManager.getPluginByCommand(command);
if (toolOpt.isPresent()) {
Plugin plugin = toolOpt.get();
try {
// args[0]是命令名,从args[1]开始传给plugin
String[] pluginArgs = args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0];
plugin.executeCommand(pluginArgs);
String[] toolArgs = args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0];
plugin.executeCommand(toolArgs);
} catch (Exception e) {
log.error("命令执行失败: {} [{}]", plugin.getName(), plugin.getId(), e);
System.err.println("命令执行失败: " + e.getMessage());
@@ -13,13 +13,14 @@ import java.util.List;
import java.util.Scanner;
/**
* 批量处理插件(实现 Plugin 接口)
* 批量处理插件(实现 Tool 接口)
* 仅负责菜单展示和用户交互,业务逻辑委托给各Service
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class BatchTool implements Plugin {
public class BatchPlugin implements Plugin {
@FunctionalInterface
public interface ExceptionRunnable {
@@ -2,8 +2,8 @@ package com.example.workutils.biz.git;
import com.example.workutils.biz.git.service.GitService;
import com.example.workutils.biz.git.service.internal.GitCommandExecutor;
import com.example.workutils.system.plugin.Plugin;
import com.example.workutils.system.plugin.PluginDescriptor;
import com.example.workutils.system.plugin.Plugin;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -14,12 +14,12 @@ import java.util.Scanner;
import java.util.stream.Collectors;
/**
* Git仓库健康检测插件(实现 Plugin 接口)
* Git仓库健康检测插件(实现 Tool 接口)
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class GitTool implements Plugin {
public class GitPlugin implements Plugin {
private static final PluginDescriptor DESCRIPTOR = PluginDescriptor.builder()
.id("git-health")
@@ -3,7 +3,7 @@ package com.example.workutils.biz.git.service;
import com.example.workutils.biz.git.service.internal.GitCommandExecutor;
import com.example.workutils.biz.git.service.internal.strategy.GitStrategyFactory;
import com.example.workutils.biz.git.service.model.GitCommand;
import com.example.workutils.system.support.FileOperationService;
import com.example.workutils.system.support.FileOperationHelper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -26,7 +26,7 @@ public class GitServiceImpl implements GitService {
private final GitStrategyFactory strategyFactory;
@Autowired
private FileOperationService fileOperationService;
private FileOperationHelper fileOperationHelper;
private static final String DEFAULT_BRANCH = "master";
@@ -52,10 +52,10 @@ public class GitServiceImpl implements GitService {
@Override
public List<GitCommandExecutor.RepoHealthResult> checkCorruptedRepos(Scanner scanner) {
List<GitCommandExecutor.RepoHealthResult> corruptedRepos = new ArrayList<>();
List<FileOperationService.ProjectPathInfo> projects;
List<FileOperationHelper.ProjectPathInfo> projects;
try {
projects = fileOperationService.getProjectPathsWithStatus();
projects = fileOperationHelper.getProjectPathsWithStatus();
} catch (IOException e) {
log.error("读取项目路径配置文件失败", e);
return corruptedRepos;
@@ -68,7 +68,7 @@ public class GitServiceImpl implements GitService {
int totalCount = projects.size();
boolean isAborted = false;
for (FileOperationService.ProjectPathInfo project : projects) {
for (FileOperationHelper.ProjectPathInfo project : projects) {
String repoPath = project.getPath();
String repoName = extractProjectName(repoPath);
@@ -80,7 +80,7 @@ public class GitServiceImpl implements GitService {
if (healthResult.isHealthy()) {
try {
fileOperationService.updateProjectStatus(repoPath, FileOperationService.ProjectStatus.NORMAL);
fileOperationHelper.updateProjectStatus(repoPath, FileOperationHelper.ProjectStatus.NORMAL);
System.out.printf("[%d/%d] %s - 正常%n", checkedCount, totalCount, repoName);
} catch (IOException e) {
log.warn("更新配置文件失败: {}", repoPath, e);
@@ -88,7 +88,7 @@ public class GitServiceImpl implements GitService {
} else {
corruptedRepos.add(healthResult);
try {
fileOperationService.updateProjectStatus(repoPath, FileOperationService.ProjectStatus.CORRUPTED);
fileOperationHelper.updateProjectStatus(repoPath, FileOperationHelper.ProjectStatus.CORRUPTED);
System.out.printf("[%d/%d] %s - 损坏: %s%n", checkedCount, totalCount, repoName, healthResult.getCorruptionReason());
} catch (IOException e) {
log.warn("更新配置文件失败: {}", repoPath, e);
@@ -143,7 +143,7 @@ public class GitServiceImpl implements GitService {
if (result.isSuccess()) {
log.info("仓库修复成功: {}", repoPath);
try {
fileOperationService.updateProjectStatus(repoPath, FileOperationService.ProjectStatus.NORMAL);
fileOperationHelper.updateProjectStatus(repoPath, FileOperationHelper.ProjectStatus.NORMAL);
} catch (IOException e) {
log.warn("更新配置文件失败: {}", repoPath, e);
}
@@ -157,9 +157,9 @@ public class GitServiceImpl implements GitService {
@Override
public List<String> listCorruptedRepos() {
try {
return fileOperationService.getProjectPathsWithStatus().stream()
.filter(p -> p.getStatus() == FileOperationService.ProjectStatus.CORRUPTED)
.map(FileOperationService.ProjectPathInfo::getPath)
return fileOperationHelper.getProjectPathsWithStatus().stream()
.filter(p -> p.getStatus() == FileOperationHelper.ProjectStatus.CORRUPTED)
.map(FileOperationHelper.ProjectPathInfo::getPath)
.collect(Collectors.toList());
} catch (IOException e) {
log.error("读取损坏仓库列表失败", e);
@@ -1,8 +1,8 @@
package com.example.workutils.biz.mybatisexport;
import com.example.workutils.biz.mybatisexport.service.MyBatisExportService;
import com.example.workutils.system.plugin.Plugin;
import com.example.workutils.system.plugin.PluginDescriptor;
import com.example.workutils.system.plugin.Plugin;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -11,12 +11,12 @@ import java.util.Arrays;
import java.util.Scanner;
/**
* MyBatis SQL导出插件(实现 Plugin 接口)
* MyBatis SQL导出插件(实现 Tool 接口)
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MyBatisExportTool implements Plugin {
public class MyBatisExportPlugin implements Plugin {
private static final PluginDescriptor DESCRIPTOR = PluginDescriptor.builder()
.id("mybatisexport")
@@ -2,7 +2,7 @@ package com.example.workutils.biz.mybatisexport.service;
import com.example.workutils.biz.mybatisexport.service.model.MybatisTag;
import com.example.workutils.biz.scanner.service.config.ScanConfig;
import com.example.workutils.system.support.FileOperationService;
import com.example.workutils.system.support.FileOperationHelper;
import com.example.workutils.system.support.OutputService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -25,7 +25,7 @@ import java.util.regex.Pattern;
public class MyBatisExportService {
@Autowired
private FileOperationService fileOperationService;
private FileOperationHelper fileOperationHelper;
@Autowired
private OutputService outputService;
@@ -78,7 +78,7 @@ public class MyBatisExportService {
private void exportWithFilter(String fileNamePrefix, SqlFilter filter,
ProcessStrategy processStrategy) throws Exception {
List<String> projectPaths = fileOperationService.getProjectPaths();
List<String> projectPaths = fileOperationHelper.getProjectPaths();
if (projectPaths.isEmpty()) {
log.warn("未找到项目路径");
return;
@@ -108,10 +108,10 @@ public class MyBatisExportService {
private List<String[]> processProject(String projectPath, SqlFilter filter,
ProcessStrategy processStrategy) throws Exception {
List<String[]> records = new ArrayList<>();
String projectName = fileOperationService.extractProjectName(projectPath);
String projectName = fileOperationHelper.extractProjectName(projectPath);
ScanConfig config = ScanConfig.forXmlFiles();
List<Path> xmlFiles = fileOperationService.scanFiles(projectPath, config);
List<Path> xmlFiles = fileOperationHelper.scanFiles(projectPath, config);
for (Path xmlFile : xmlFiles) {
try {
@@ -134,7 +134,7 @@ public class MyBatisExportService {
private List<MybatisTag> parseXmlFile(Path xmlFile) throws Exception {
List<MybatisTag> tags = new ArrayList<>();
String content = fileOperationService.readFileContent(xmlFile);
String content = fileOperationHelper.readFileContent(xmlFile);
for (String tagType : SQL_TAG_TYPES) {
tags.addAll(parseSqlTags(content, tagType));
@@ -12,12 +12,12 @@ import java.util.List;
import java.util.Scanner;
/**
* MyBatis逆向工程插件(实现 Plugin 接口)
* MyBatis逆向工程插件(实现 Tool 接口)
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class MyBatisGeneratorTool implements Plugin {
public class MyBatisGeneratorPlugin implements Plugin {
private static final PluginDescriptor DESCRIPTOR = PluginDescriptor.builder()
.id("mybatisgenerator")
@@ -1,8 +1,8 @@
package com.example.workutils.biz.scanner;
import com.example.workutils.biz.scanner.service.ScannerService;
import com.example.workutils.system.plugin.Plugin;
import com.example.workutils.system.plugin.PluginDescriptor;
import com.example.workutils.system.plugin.Plugin;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -10,12 +10,12 @@ import org.springframework.stereotype.Component;
import java.util.Scanner;
/**
* 扫描工具插件(实现 Plugin 接口)
* 扫描工具插件(实现 Tool 接口)
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class ScannerTool implements Plugin {
public class ScannerPlugin implements Plugin {
private static final PluginDescriptor DESCRIPTOR = PluginDescriptor.builder()
.id("scan")
@@ -2,7 +2,7 @@ package com.example.workutils.biz.scanner.service;
import com.example.workutils.biz.scanner.service.config.ScanConfig;
import com.example.workutils.system.config.WorkUtilsProperties;
import com.example.workutils.system.support.FileOperationService;
import com.example.workutils.system.support.FileOperationHelper;
import com.example.workutils.system.support.OutputService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
@@ -24,14 +24,14 @@ public class ScannerService {
private final ExecutorService executorService = Executors.newFixedThreadPool(10);
@Autowired
private FileOperationService fileOperationService;
private FileOperationHelper fileOperationHelper;
@Autowired
private OutputService outputService;
@Autowired
private WorkUtilsProperties properties;
public ScanResult scanAllProjects(ScanType scanType, String pattern) throws Exception {
List<String> projectPaths = fileOperationService.getProjectPaths();
List<String> projectPaths = fileOperationHelper.getProjectPaths();
if (projectPaths.isEmpty()) {
log.warn("No project paths found");
return new ScanResult();
@@ -63,10 +63,10 @@ public class ScannerService {
private ProjectScanResult scanProject(String projectPath, ScanType scanType, String pattern) throws Exception {
ProjectScanResult projectResult = new ProjectScanResult(projectPath, new ArrayList<>());
String projectName = fileOperationService.extractProjectName(projectPath);
String projectName = fileOperationHelper.extractProjectName(projectPath);
ScanConfig config = getConfigForScanType(scanType);
List<Path> files = fileOperationService.scanFiles(projectPath, config);
List<Path> files = fileOperationHelper.scanFiles(projectPath, config);
for (Path file : files) {
try {
@@ -90,7 +90,7 @@ public class ScannerService {
List<MatchResult> matches = new ArrayList<>();
List<String> lines;
try {
lines = fileOperationService.readLinesFromFile(file);
lines = fileOperationHelper.readLinesFromFile(file);
} catch (Exception e) {
log.warn("Failed to read file: {}, skipping this file", file, e);
return matches;
@@ -185,7 +185,7 @@ public class ScannerService {
}
}
fileOperationService.ensureDirectoryExists(properties.getTodayExportPath());
fileOperationHelper.ensureDirectoryExists(properties.getTodayExportPath());
outputService.writeRecordsToCsv(records, outputFileName);
log.info("Scan results exported to: {}", outputFileName);
}
@@ -1,24 +1,77 @@
package com.example.workutils.system.plugin;
import com.example.workutils.system.tool.Tool;
import com.example.workutils.system.support.ConsoleHelper;
import java.util.Scanner;
/**
* 插件接口 —— 所有 biz 模块的顶级契约
* 工具接口 —— 所有 biz 模块的顶级契约
* <p>
* 继承 {@link Tool},在其基础上增加插件生命周期管理:
* 提供:
* <ul>
* <li>{@link #getDescriptor()} — 提供插件元数据(id/name/version/description/dependencies)</li>
* <li>{@link #onLoad(PluginContext)} — 插件加载时回调,注入运行时上下文</li>
* <li>{@link #onUnload()} — 插件卸载时回调,清理资源</li>
* <li>插件元数据 — {@link #getDescriptor()} 声明 id/name/version/description</li>
* <li>生命周期 — {@link #onLoad(PluginContext)} / {@link #onUnload()}</li>
* <li>交互模式 — {@link #execute(Scanner)} 模板方法驱动,子类实现 {@link #handleChoice(int, Scanner)}</li>
* <li>命令行模式 — {@link #getCommandName()} + {@link #executeCommand(String...)}</li>
* </ul>
* <p>
* getId()/getName() 默认委托给 PluginDescriptor,实现类只需提供 getDescriptor()。
*/
public interface Plugin extends Tool {
public interface Plugin {
/** 插件描述符(元数据) */
PluginDescriptor getDescriptor();
/** 工具唯一标识,默认委托给 PluginDescriptor */
default String getId() {
return getDescriptor().getId();
}
/** 菜单显示名称,默认委托给 PluginDescriptor */
default String getName() {
return getDescriptor().getName();
}
/** 子菜单选项,返回空数组表示无子菜单 */
String[] getMenuOptions();
/** 命令行命令名(如 "sql", "scan"),返回空字符串表示不支持命令行模式 */
default String getCommandName() {
return "";
}
// ==================== 交互模式(模板方法) ====================
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() + "] 不支持命令行模式");
}
// ==================== 生命周期回调 ====================
/** 插件加载回调,PluginManager 在注册时调用 */
default void onLoad(PluginContext context) {
}
@@ -27,15 +80,25 @@ public interface Plugin extends Tool {
default void onUnload() {
}
// ==================== 默认委托 ====================
// ==================== 控制台输出辅助方法 ====================
@Override
default String getId() {
return getDescriptor().getId();
default void printMenuTitle(String title) {
ConsoleHelper.printMenuTitle(title);
}
@Override
default String getName() {
return getDescriptor().getName();
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();
}
}
@@ -6,7 +6,7 @@ import java.util.List;
/**
* 插件描述符(不可变元数据)
* <p>
* 每个 Plugin 通过 {@link Plugin#getDescriptor()} 返回此描述符,
* 每个插件通过 {@link Plugin#getDescriptor()} 返回此描述符,
* PluginManager 据此识别、管理、展示插件。
*/
public class PluginDescriptor {
@@ -11,7 +11,7 @@ import java.util.*;
/**
* 插件管理器 —— 插件生命周期管理 + 注册中心 + 主菜单
* <p>
* 替换旧版 {@code ToolRegistry},统一管理所有 {@link Plugin} 的:
* 统一管理所有 {@link Plugin} 实例的:
* <ul>
* <li>生命周期 — load / unload / enable / disable</li>
* <li>注册发现 — 按 id / command 查找</li>
@@ -21,7 +21,7 @@ import java.util.stream.Stream;
*/
@Service
@Slf4j
public class FileOperationService {
public class FileOperationHelper {
@Autowired
private WorkUtilsProperties properties;
@@ -22,10 +22,10 @@ import java.util.List;
*/
@Service
@Slf4j
public class OutputService {
public class OutputHelper {
@Autowired
private FileOperationService fileOperationService;
private FileOperationHelper fileOperationHelper;
@Autowired
private WorkUtilsProperties properties;
@@ -39,7 +39,7 @@ public class OutputService {
} else {
filePath = properties.resolveExportPath(outputPath);
}
fileOperationService.ensureDirectoryExists(filePath.getParent());
fileOperationHelper.ensureDirectoryExists(filePath.getParent());
try (CSVWriter writer = new CSVWriter(new FileWriter(filePath.toFile()))) {
if (headers.length > 0) {
@@ -62,9 +62,9 @@ public class OutputService {
public void appendToCsv(String[] record, String outputPath, String... headers) throws IOException {
Path filePath = properties.resolveExportPath(outputPath);
fileOperationService.ensureDirectoryExists(filePath.getParent());
fileOperationHelper.ensureDirectoryExists(filePath.getParent());
boolean fileExists = fileOperationService.fileExists(filePath);
boolean fileExists = fileOperationHelper.fileExists(filePath);
boolean shouldWriteHeaders = !fileExists && headers.length > 0;
try (CSVWriter writer = new CSVWriter(new FileWriter(filePath.toFile(), true))) {
@@ -82,21 +82,21 @@ public class OutputService {
public <T> void writeToExcel(List<T> data, String outputPath, Class<T> modelClass) throws IOException {
Path filePath = properties.resolveExportPath(outputPath);
fileOperationService.ensureDirectoryExists(filePath.getParent());
fileOperationHelper.ensureDirectoryExists(filePath.getParent());
EasyExcel.write(filePath.toFile(), modelClass).sheet().doWrite(data);
log.info("Excel文件写入成功: {}", filePath);
}
public <T> void writeToExcel(List<T> data, String outputPath, String sheetName, Class<T> modelClass) throws IOException {
Path filePath = properties.resolveExportPath(outputPath);
fileOperationService.ensureDirectoryExists(filePath.getParent());
fileOperationHelper.ensureDirectoryExists(filePath.getParent());
EasyExcel.write(filePath.toFile(), modelClass).sheet(sheetName).doWrite(data);
log.info("Excel文件写入成功: {}", filePath);
}
public <T> void writeToExcelWithMultipleSheets(List<ExcelSheetData<T>> sheetsData, String outputPath) throws IOException {
Path filePath = properties.resolveExportPath(outputPath);
fileOperationService.ensureDirectoryExists(filePath.getParent());
fileOperationHelper.ensureDirectoryExists(filePath.getParent());
try (ExcelWriter excelWriter = EasyExcel.write(filePath.toFile()).build()) {
for (ExcelSheetData<T> sheetData : sheetsData) {
@@ -113,7 +113,7 @@ public class OutputService {
// ==================== 文件名生成相关方法 ====================
public String generateOutputFileName(String prefix, String extension) {
return fileOperationService.generateFileName(prefix, extension);
return fileOperationHelper.generateFileName(prefix, extension);
}
// ==================== 内部数据类 ====================
@@ -1,83 +0,0 @@
package com.example.workutils.system.tool;
import com.example.workutils.system.support.ConsoleHelper;
import java.util.Scanner;
/**
* 工具策略接口
* 所有可插拔工具实现此接口,Spring 自动发现 Tool Bean
* <p>
* 提供交互式菜单(execute)和命令行(executeCommand)两种执行模式。
* 交互模式由默认 execute() 模板方法驱动,子类实现 handleChoice() 定义选择逻辑。
*/
public interface Tool {
/** 工具唯一标识(如 "scan", "mybatis") */
String getId();
/** 菜单显示名称(如 "关键词扫描", "MyBatis SQL导出") */
String getName();
/** 子菜单选项,返回空数组表示无子菜单 */
String[] getMenuOptions();
/** 命令行命令名(如 "sql", "scan"),返回空字符串表示不支持命令行模式 */
default String getCommandName() {
return "";
}
// ==================== 交互模式(模板方法) ====================
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();
}
}