插件版本更新
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
package com.example.workutils;
|
||||
|
||||
import com.example.workutils.system.tool.Tool;
|
||||
import com.example.workutils.system.tool.ToolRegistry;
|
||||
import com.example.workutils.system.plugin.Plugin;
|
||||
import com.example.workutils.system.plugin.PluginManager;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.boot.CommandLineRunner;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
@@ -16,10 +16,10 @@ import java.util.Scanner;
|
||||
@Slf4j
|
||||
public class WorkUtilsApplication implements CommandLineRunner {
|
||||
|
||||
private final ToolRegistry toolRegistry;
|
||||
private final PluginManager pluginManager;
|
||||
|
||||
public WorkUtilsApplication(ToolRegistry toolRegistry) {
|
||||
this.toolRegistry = toolRegistry;
|
||||
public WorkUtilsApplication(PluginManager pluginManager) {
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
@@ -37,25 +37,25 @@ public class WorkUtilsApplication implements CommandLineRunner {
|
||||
|
||||
private void showInteractiveMenu() {
|
||||
Scanner scanner = new Scanner(System.in);
|
||||
toolRegistry.runMainMenu(scanner);
|
||||
pluginManager.runMainMenu(scanner);
|
||||
}
|
||||
|
||||
/**
|
||||
* 命令行模式:通过ToolRegistry按命令名分发到对应Tool
|
||||
* 启动类不依赖任何具体业务Service
|
||||
* 命令行模式:通过 PluginManager 按命令名分发到对应 Plugin
|
||||
* 启动类不依赖任何具体业务 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();
|
||||
Optional<Plugin> pluginOpt = pluginManager.getPluginByCommand(command);
|
||||
if (pluginOpt.isPresent()) {
|
||||
Plugin plugin = pluginOpt.get();
|
||||
try {
|
||||
// args[0]是命令名,从args[1]开始传给tool
|
||||
String[] toolArgs = args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0];
|
||||
tool.executeCommand(toolArgs);
|
||||
// args[0]是命令名,从args[1]开始传给plugin
|
||||
String[] pluginArgs = args.length > 1 ? Arrays.copyOfRange(args, 1, args.length) : new String[0];
|
||||
plugin.executeCommand(pluginArgs);
|
||||
} catch (Exception e) {
|
||||
log.error("命令执行失败: {} [{}]", tool.getName(), tool.getId(), e);
|
||||
log.error("命令执行失败: {} [{}]", plugin.getName(), plugin.getId(), e);
|
||||
System.err.println("命令执行失败: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
|
||||
@@ -2,7 +2,8 @@ package com.example.workutils.biz.batch;
|
||||
|
||||
import com.example.workutils.biz.mybatisexport.service.MyBatisExportService;
|
||||
import com.example.workutils.biz.scanner.service.ScannerService;
|
||||
import com.example.workutils.system.tool.Tool;
|
||||
import com.example.workutils.system.plugin.Plugin;
|
||||
import com.example.workutils.system.plugin.PluginDescriptor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -12,19 +13,26 @@ import java.util.List;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* 批量处理工具
|
||||
* 批量处理插件(实现 Plugin 接口)
|
||||
* 仅负责菜单展示和用户交互,业务逻辑委托给各Service
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class BatchTool implements Tool {
|
||||
public class BatchTool implements Plugin {
|
||||
|
||||
@FunctionalInterface
|
||||
public interface ExceptionRunnable {
|
||||
void run() throws Exception;
|
||||
}
|
||||
|
||||
private static final PluginDescriptor DESCRIPTOR = PluginDescriptor.builder()
|
||||
.id("batch")
|
||||
.name("批量处理")
|
||||
.version("1.0.0")
|
||||
.description("一键执行多种检查任务")
|
||||
.build();
|
||||
|
||||
private static final String[] MENU_OPTIONS = {
|
||||
"1. 执行所有检查",
|
||||
"2. 自定义组合检查",
|
||||
@@ -42,13 +50,8 @@ public class BatchTool implements Tool {
|
||||
private final MyBatisExportService myBatisExportService;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "batch";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "批量处理";
|
||||
public PluginDescriptor getDescriptor() {
|
||||
return DESCRIPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -2,7 +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.tool.Tool;
|
||||
import com.example.workutils.system.plugin.Plugin;
|
||||
import com.example.workutils.system.plugin.PluginDescriptor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -13,12 +14,19 @@ import java.util.Scanner;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Git仓库健康检测工具入口(符合 Tool 规范)
|
||||
* Git仓库健康检测插件(实现 Plugin 接口)
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class GitTool implements Tool {
|
||||
public class GitTool implements Plugin {
|
||||
|
||||
private static final PluginDescriptor DESCRIPTOR = PluginDescriptor.builder()
|
||||
.id("git-health")
|
||||
.name("Git仓库健康检测")
|
||||
.version("1.0.0")
|
||||
.description("检测和修复Git仓库健康状态")
|
||||
.build();
|
||||
|
||||
private static final String[] MENU_OPTIONS = {
|
||||
"1. 检测所有仓库健康状态",
|
||||
@@ -29,13 +37,8 @@ public class GitTool implements Tool {
|
||||
private final GitService gitService;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "git-health";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "Git仓库健康检测";
|
||||
public PluginDescriptor getDescriptor() {
|
||||
return DESCRIPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package com.example.workutils.biz.mybatisexport;
|
||||
|
||||
import com.example.workutils.biz.mybatisexport.service.MyBatisExportService;
|
||||
import com.example.workutils.system.tool.Tool;
|
||||
import com.example.workutils.system.plugin.Plugin;
|
||||
import com.example.workutils.system.plugin.PluginDescriptor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -10,12 +11,19 @@ import java.util.Arrays;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* MyBatis SQL导出工具入口(符合 Tool 规范)
|
||||
* MyBatis SQL导出插件(实现 Plugin 接口)
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MyBatisExportTool implements Tool {
|
||||
public class MyBatisExportTool implements Plugin {
|
||||
|
||||
private static final PluginDescriptor DESCRIPTOR = PluginDescriptor.builder()
|
||||
.id("mybatisexport")
|
||||
.name("MyBatis SQL导出")
|
||||
.version("1.0.0")
|
||||
.description("从MyBatis XML中提取SQL并导出为CSV")
|
||||
.build();
|
||||
|
||||
private static final String[] MENU_OPTIONS = {
|
||||
"1. 导出所有SQL",
|
||||
@@ -29,13 +37,8 @@ public class MyBatisExportTool implements Tool {
|
||||
private final MyBatisExportService myBatisExportService;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "mybatisexport";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "MyBatis SQL导出";
|
||||
public PluginDescriptor getDescriptor() {
|
||||
return DESCRIPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
+13
-10
@@ -1,7 +1,8 @@
|
||||
package com.example.workutils.biz.mybatisgenerator;
|
||||
|
||||
import com.example.workutils.biz.mybatisgenerator.service.generator.MyBatisGeneratorService;
|
||||
import com.example.workutils.system.tool.Tool;
|
||||
import com.example.workutils.system.plugin.Plugin;
|
||||
import com.example.workutils.system.plugin.PluginDescriptor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -11,12 +12,19 @@ import java.util.List;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* MyBatis逆向工程工具入口(符合 Tool 规范)
|
||||
* MyBatis逆向工程插件(实现 Plugin 接口)
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class MyBatisGeneratorTool implements Tool {
|
||||
public class MyBatisGeneratorTool implements Plugin {
|
||||
|
||||
private static final PluginDescriptor DESCRIPTOR = PluginDescriptor.builder()
|
||||
.id("mybatisgenerator")
|
||||
.name("MyBatis逆向工程")
|
||||
.version("1.0.0")
|
||||
.description("根据数据库表结构生成PO/Mapper/XML")
|
||||
.build();
|
||||
|
||||
private static final String[] MENU_OPTIONS = {
|
||||
"1. 根据表名生成(单表)",
|
||||
@@ -30,13 +38,8 @@ public class MyBatisGeneratorTool implements Tool {
|
||||
private final MyBatisGeneratorService generatorService;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "mybatisgenerator";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "MyBatis逆向工程";
|
||||
public PluginDescriptor getDescriptor() {
|
||||
return DESCRIPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package com.example.workutils.biz.scanner;
|
||||
|
||||
import com.example.workutils.biz.scanner.service.ScannerService;
|
||||
import com.example.workutils.system.tool.Tool;
|
||||
import com.example.workutils.system.plugin.Plugin;
|
||||
import com.example.workutils.system.plugin.PluginDescriptor;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
@@ -9,12 +10,19 @@ import org.springframework.stereotype.Component;
|
||||
import java.util.Scanner;
|
||||
|
||||
/**
|
||||
* 扫描工具入口(符合 Tool 规范)
|
||||
* 扫描工具插件(实现 Plugin 接口)
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
public class ScannerTool implements Tool {
|
||||
public class ScannerTool implements Plugin {
|
||||
|
||||
private static final PluginDescriptor DESCRIPTOR = PluginDescriptor.builder()
|
||||
.id("scan")
|
||||
.name("扫描功能")
|
||||
.version("1.0.0")
|
||||
.description("关键词/Redis/Dubbo扫描工具")
|
||||
.build();
|
||||
|
||||
private static final String[] MENU_OPTIONS = {
|
||||
"1. 关键词扫描",
|
||||
@@ -24,13 +32,8 @@ public class ScannerTool implements Tool {
|
||||
private final ScannerService scannerService;
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return "scan";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "扫描功能";
|
||||
public PluginDescriptor getDescriptor() {
|
||||
return DESCRIPTOR;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.example.workutils.system.plugin;
|
||||
|
||||
import com.example.workutils.system.tool.Tool;
|
||||
|
||||
/**
|
||||
* 插件接口 —— 所有 biz 模块的顶级契约
|
||||
* <p>
|
||||
* 继承 {@link Tool},在其基础上增加插件生命周期管理:
|
||||
* <ul>
|
||||
* <li>{@link #getDescriptor()} — 提供插件元数据(id/name/version/description/dependencies)</li>
|
||||
* <li>{@link #onLoad(PluginContext)} — 插件加载时回调,注入运行时上下文</li>
|
||||
* <li>{@link #onUnload()} — 插件卸载时回调,清理资源</li>
|
||||
* </ul>
|
||||
* <p>
|
||||
* getId()/getName() 默认委托给 PluginDescriptor,实现类只需提供 getDescriptor()。
|
||||
*/
|
||||
public interface Plugin extends Tool {
|
||||
|
||||
/** 插件描述符(元数据) */
|
||||
PluginDescriptor getDescriptor();
|
||||
|
||||
/** 插件加载回调,PluginManager 在注册时调用 */
|
||||
default void onLoad(PluginContext context) {
|
||||
}
|
||||
|
||||
/** 插件卸载回调,清理资源 */
|
||||
default void onUnload() {
|
||||
}
|
||||
|
||||
// ==================== 默认委托 ====================
|
||||
|
||||
@Override
|
||||
default String getId() {
|
||||
return getDescriptor().getId();
|
||||
}
|
||||
|
||||
@Override
|
||||
default String getName() {
|
||||
return getDescriptor().getName();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.example.workutils.system.plugin;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 插件运行时上下文
|
||||
* <p>
|
||||
* 通过 {@link Plugin#onLoad(PluginContext)} 注入给每个插件,
|
||||
* 提供访问 PluginManager 和跨插件共享属性的能力。
|
||||
*/
|
||||
public class PluginContext {
|
||||
|
||||
private final PluginManager pluginManager;
|
||||
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
|
||||
|
||||
public PluginContext(PluginManager pluginManager) {
|
||||
this.pluginManager = pluginManager;
|
||||
}
|
||||
|
||||
/** 获取 PluginManager,用于插件间通信 */
|
||||
public PluginManager getPluginManager() {
|
||||
return pluginManager;
|
||||
}
|
||||
|
||||
/** 设置共享属性 */
|
||||
public void setAttribute(String key, Object value) {
|
||||
attributes.put(key, value);
|
||||
}
|
||||
|
||||
/** 获取共享属性 */
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getAttribute(String key) {
|
||||
return (T) attributes.get(key);
|
||||
}
|
||||
|
||||
/** 获取共享属性,带默认值 */
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T getAttribute(String key, T defaultValue) {
|
||||
return (T) attributes.getOrDefault(key, defaultValue);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,68 @@
|
||||
package com.example.workutils.system.plugin;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 插件描述符(不可变元数据)
|
||||
* <p>
|
||||
* 每个 Plugin 通过 {@link Plugin#getDescriptor()} 返回此描述符,
|
||||
* PluginManager 据此识别、管理、展示插件。
|
||||
*/
|
||||
public class PluginDescriptor {
|
||||
|
||||
private final String id;
|
||||
private final String name;
|
||||
private final String version;
|
||||
private final String description;
|
||||
private final List<String> dependencies;
|
||||
|
||||
private PluginDescriptor(Builder builder) {
|
||||
this.id = builder.id;
|
||||
this.name = builder.name;
|
||||
this.version = builder.version;
|
||||
this.description = builder.description;
|
||||
this.dependencies = Collections.unmodifiableList(builder.dependencies);
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder();
|
||||
}
|
||||
|
||||
// ==================== Getters ====================
|
||||
|
||||
public String getId() { return id; }
|
||||
public String getName() { return name; }
|
||||
public String getVersion() { return version; }
|
||||
public String getDescription() { return description; }
|
||||
|
||||
/** 依赖的插件 ID 列表(暂未强制校验,预留设计) */
|
||||
public List<String> getDependencies() { return dependencies; }
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return name + " [v" + version + "]";
|
||||
}
|
||||
|
||||
// ==================== Builder ====================
|
||||
|
||||
public static class Builder {
|
||||
private String id;
|
||||
private String name;
|
||||
private String version = "1.0.0";
|
||||
private String description = "";
|
||||
private List<String> dependencies = Collections.emptyList();
|
||||
|
||||
public Builder id(String id) { this.id = id; return this; }
|
||||
public Builder name(String name) { this.name = name; return this; }
|
||||
public Builder version(String version) { this.version = version;return this; }
|
||||
public Builder description(String desc) { this.description = desc; return this; }
|
||||
public Builder dependencies(List<String> d) { this.dependencies = d; return this; }
|
||||
|
||||
public PluginDescriptor build() {
|
||||
if (id == null || id.isEmpty()) throw new IllegalArgumentException("plugin id is required");
|
||||
if (name == null || name.isEmpty()) throw new IllegalArgumentException("plugin name is required");
|
||||
return new PluginDescriptor(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
package com.example.workutils.system.plugin;
|
||||
|
||||
import com.example.workutils.system.config.WorkUtilsProperties;
|
||||
import com.example.workutils.system.support.ConsoleHelper;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* 插件管理器 —— 插件生命周期管理 + 注册中心 + 主菜单
|
||||
* <p>
|
||||
* 替换旧版 {@code ToolRegistry},统一管理所有 {@link Plugin} 的:
|
||||
* <ul>
|
||||
* <li>生命周期 — load / unload / enable / disable</li>
|
||||
* <li>注册发现 — 按 id / command 查找</li>
|
||||
* <li>启用过滤 — 白名单 / 黑名单</li>
|
||||
* <li>主菜单 — 交互式菜单循环</li>
|
||||
* <li>命令行分发 — 按 command 名路由</li>
|
||||
* </ul>
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class PluginManager {
|
||||
|
||||
private static final int EXIT_CODE = 0;
|
||||
|
||||
private final List<Plugin> allPlugins;
|
||||
private final WorkUtilsProperties properties;
|
||||
private PluginContext pluginContext;
|
||||
|
||||
private List<Plugin> enabledPlugins;
|
||||
private Map<String, Plugin> pluginByIdMap;
|
||||
private Map<String, Plugin> pluginByCommandMap;
|
||||
private Map<String, PluginState> pluginStates;
|
||||
|
||||
public PluginManager(List<Plugin> plugins, WorkUtilsProperties properties) {
|
||||
this.allPlugins = plugins;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
this.pluginContext = new PluginContext(this);
|
||||
this.enabledPlugins = new ArrayList<>();
|
||||
this.pluginByIdMap = new LinkedHashMap<>();
|
||||
this.pluginByCommandMap = new LinkedHashMap<>();
|
||||
this.pluginStates = new LinkedHashMap<>();
|
||||
|
||||
Set<String> enabledIds = resolveEnabledIds();
|
||||
|
||||
for (Plugin plugin : allPlugins) {
|
||||
try {
|
||||
// 生命周期:加载
|
||||
plugin.onLoad(pluginContext);
|
||||
PluginDescriptor desc = plugin.getDescriptor();
|
||||
pluginStates.put(desc.getId(), PluginState.LOADED);
|
||||
|
||||
if (enabledIds == null || enabledIds.contains(desc.getId())) {
|
||||
enabledPlugins.add(plugin);
|
||||
pluginByIdMap.put(desc.getId(), plugin);
|
||||
pluginStates.put(desc.getId(), PluginState.ACTIVE);
|
||||
|
||||
String cmd = plugin.getCommandName();
|
||||
if (cmd != null && !cmd.isEmpty()) {
|
||||
pluginByCommandMap.put(cmd, plugin);
|
||||
}
|
||||
log.info("加载插件: {}", desc);
|
||||
} else {
|
||||
pluginStates.put(desc.getId(), PluginState.DISABLED);
|
||||
log.info("跳过已禁用插件: {}", desc);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
String pluginId = safeGetId(plugin);
|
||||
pluginStates.put(pluginId, PluginState.ERROR);
|
||||
log.error("插件加载失败: " + pluginId, e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("插件加载完成: {}/{} 已激活", enabledPlugins.size(), allPlugins.size());
|
||||
}
|
||||
|
||||
// ==================== 主菜单 ====================
|
||||
|
||||
/** 启动主菜单交互循环 */
|
||||
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("感谢使用,再见!");
|
||||
shutdown();
|
||||
break;
|
||||
}
|
||||
|
||||
dispatchPlugin(choice, scanner);
|
||||
}
|
||||
}
|
||||
|
||||
private void displayMainMenu() {
|
||||
System.out.println("请选择操作:");
|
||||
for (int i = 0; i < enabledPlugins.size(); i++) {
|
||||
PluginDescriptor desc = enabledPlugins.get(i).getDescriptor();
|
||||
System.out.println((i + 1) + ". " + desc.getName() + " [v" + desc.getVersion() + "]");
|
||||
}
|
||||
System.out.println(EXIT_CODE + ". 退出系统");
|
||||
}
|
||||
|
||||
private void dispatchPlugin(int choice, Scanner scanner) {
|
||||
int index = choice - 1;
|
||||
if (index >= 0 && index < enabledPlugins.size()) {
|
||||
Plugin plugin = enabledPlugins.get(index);
|
||||
PluginDescriptor desc = plugin.getDescriptor();
|
||||
try {
|
||||
plugin.execute(scanner);
|
||||
} catch (Exception e) {
|
||||
log.error("插件执行失败: " + desc, e);
|
||||
System.out.println("插件执行失败: " + e.getMessage());
|
||||
}
|
||||
} else {
|
||||
ConsoleHelper.printInvalidOption();
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 查找方法 ====================
|
||||
|
||||
/** 获取所有已启用插件(只读) */
|
||||
public List<Plugin> getEnabledPlugins() {
|
||||
return Collections.unmodifiableList(enabledPlugins);
|
||||
}
|
||||
|
||||
/** 按 ID 查找插件 */
|
||||
public Optional<Plugin> getPluginById(String id) {
|
||||
return Optional.ofNullable(pluginByIdMap.get(id));
|
||||
}
|
||||
|
||||
/** 按命令行命令名查找插件 */
|
||||
public Optional<Plugin> getPluginByCommand(String command) {
|
||||
return Optional.ofNullable(pluginByCommandMap.get(command));
|
||||
}
|
||||
|
||||
/** 获取插件状态 */
|
||||
public PluginState getPluginState(String pluginId) {
|
||||
return pluginStates.getOrDefault(pluginId, PluginState.UNLOADED);
|
||||
}
|
||||
|
||||
/** 列出所有插件(含已禁用) */
|
||||
public List<PluginDescriptor> listAllPluginDescriptors() {
|
||||
List<PluginDescriptor> result = new ArrayList<>();
|
||||
for (Plugin plugin : allPlugins) {
|
||||
result.add(plugin.getDescriptor());
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ==================== 生命周期管理(预留) ====================
|
||||
|
||||
/** 手动启用插件 */
|
||||
public boolean enablePlugin(String pluginId) {
|
||||
Plugin plugin = pluginByIdMap.get(pluginId);
|
||||
if (plugin == null) {
|
||||
Plugin p = findPluginById(pluginId);
|
||||
if (p == null) {
|
||||
log.warn("未找到插件: {}", pluginId);
|
||||
return false;
|
||||
}
|
||||
enabledPlugins.add(p);
|
||||
pluginByIdMap.put(pluginId, p);
|
||||
String cmd = p.getCommandName();
|
||||
if (cmd != null && !cmd.isEmpty()) {
|
||||
pluginByCommandMap.put(cmd, p);
|
||||
}
|
||||
pluginStates.put(pluginId, PluginState.ACTIVE);
|
||||
log.info("启用插件: {}", p.getDescriptor());
|
||||
} else {
|
||||
pluginStates.put(pluginId, PluginState.ACTIVE);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/** 手动禁用插件 */
|
||||
public boolean disablePlugin(String pluginId) {
|
||||
Plugin plugin = pluginByIdMap.remove(pluginId);
|
||||
if (plugin != null) {
|
||||
enabledPlugins.remove(plugin);
|
||||
String cmd = plugin.getCommandName();
|
||||
if (cmd != null && !cmd.isEmpty()) {
|
||||
pluginByCommandMap.remove(cmd, plugin);
|
||||
}
|
||||
pluginStates.put(pluginId, PluginState.DISABLED);
|
||||
log.info("禁用插件: {}", plugin.getDescriptor());
|
||||
}
|
||||
return plugin != null;
|
||||
}
|
||||
|
||||
// ==================== 内部方法 ====================
|
||||
|
||||
private void shutdown() {
|
||||
for (Plugin plugin : allPlugins) {
|
||||
try {
|
||||
plugin.onUnload();
|
||||
} catch (Exception e) {
|
||||
log.warn("插件卸载异常: " + plugin.getDescriptor(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Plugin findPluginById(String id) {
|
||||
for (Plugin p : allPlugins) {
|
||||
if (p.getId().equals(id)) {
|
||||
return p;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private String safeGetId(Plugin plugin) {
|
||||
try {
|
||||
return plugin.getId();
|
||||
} catch (Exception e) {
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
// ==================== 启用/禁用解析 ====================
|
||||
|
||||
private Set<String> resolveEnabledIds() {
|
||||
WorkUtilsProperties.ToolsConfig toolsConfig = properties.getTools();
|
||||
if (toolsConfig == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (toolsConfig.getEnabled() != null && !toolsConfig.getEnabled().isEmpty()) {
|
||||
return new HashSet<>(toolsConfig.getEnabled());
|
||||
}
|
||||
|
||||
if (toolsConfig.getDisabled() != null && !toolsConfig.getDisabled().isEmpty()) {
|
||||
Set<String> allIds = new HashSet<>();
|
||||
for (Plugin plugin : allPlugins) {
|
||||
allIds.add(plugin.getId());
|
||||
}
|
||||
allIds.removeAll(toolsConfig.getDisabled());
|
||||
return allIds;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package com.example.workutils.system.plugin;
|
||||
|
||||
/**
|
||||
* 插件生命周期状态
|
||||
*/
|
||||
public enum PluginState {
|
||||
|
||||
/** 未加载 */
|
||||
UNLOADED,
|
||||
|
||||
/** 已加载(onLoad 完成) */
|
||||
LOADED,
|
||||
|
||||
/** 已激活(已启用,可交互) */
|
||||
ACTIVE,
|
||||
|
||||
/** 已禁用 */
|
||||
DISABLED,
|
||||
|
||||
/** 加载/执行异常 */
|
||||
ERROR
|
||||
}
|
||||
@@ -1,142 +0,0 @@
|
||||
package com.example.workutils.system.tool;
|
||||
|
||||
import com.example.workutils.system.support.ConsoleHelper;
|
||||
import com.example.workutils.system.config.WorkUtilsProperties;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 工具注册中心
|
||||
* 自动发现 Spring 容器中的 Tool Bean,按配置过滤启用/禁用
|
||||
* 同时负责主菜单循环
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class ToolRegistry {
|
||||
|
||||
private static final int EXIT_CODE = 0;
|
||||
|
||||
private final List<Tool> allTools;
|
||||
private final WorkUtilsProperties properties;
|
||||
private List<Tool> enabledTools;
|
||||
private Map<String, Tool> toolByIdMap;
|
||||
private Map<String, Tool> toolByCommandMap;
|
||||
|
||||
public ToolRegistry(List<Tool> tools, WorkUtilsProperties properties) {
|
||||
this.allTools = tools;
|
||||
this.properties = properties;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void init() {
|
||||
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());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.error("工具初始化失败: {} [{}]", tool.getName(), tool.getId(), e);
|
||||
}
|
||||
}
|
||||
|
||||
log.info("工具注册完成: {}/{} 已启用", enabledTools.size(), allTools.size());
|
||||
}
|
||||
|
||||
// ==================== 主菜单 ====================
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
public Optional<Tool> getToolById(String id) {
|
||||
return Optional.ofNullable(toolByIdMap.get(id));
|
||||
}
|
||||
|
||||
public Optional<Tool> getToolByCommand(String command) {
|
||||
return Optional.ofNullable(toolByCommandMap.get(command));
|
||||
}
|
||||
|
||||
// ==================== 启用/禁用解析 ====================
|
||||
|
||||
private Set<String> resolveEnabledIds() {
|
||||
WorkUtilsProperties.ToolsConfig toolsConfig = properties.getTools();
|
||||
if (toolsConfig == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (toolsConfig.getEnabled() != null && !toolsConfig.getEnabled().isEmpty()) {
|
||||
return new HashSet<>(toolsConfig.getEnabled());
|
||||
}
|
||||
|
||||
if (toolsConfig.getDisabled() != null && !toolsConfig.getDisabled().isEmpty()) {
|
||||
Set<String> allIds = allTools.stream()
|
||||
.map(Tool::getId)
|
||||
.collect(Collectors.toSet());
|
||||
allIds.removeAll(toolsConfig.getDisabled());
|
||||
return allIds;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
+153
-69
@@ -45,14 +45,19 @@ src/main/java/com/example/workutils/
|
||||
│ │ ├── ConsoleHelper.java # 控制台交互工具类
|
||||
│ │ ├── FileOperationService.java# 文件操作服务(读取/扫描/写入/路径管理)
|
||||
│ │ └── OutputService.java # 输出服务(CSV/Excel)
|
||||
│ └── tool/ # Tool 契约与注册中心
|
||||
│ ├── Tool.java # 工具契约接口(所有 biz 模块必须实现)
|
||||
│ └── ToolRegistry.java # 工具注册中心(主菜单循环 + 按命令分发)
|
||||
└── biz/ # 业务工具层(各模块是独立 Tool)
|
||||
├── batch/ # 批量处理工具
|
||||
│ └── BatchTool.java # 批量检查入口(实现 Tool)
|
||||
├── git/ # Git 操作工具
|
||||
│ ├── GitTool.java # Git 工具入口(实现 Tool)
|
||||
│ ├── tool/ # Tool 基础契约
|
||||
│ │ └── Tool.java # 工具基础接口(Plugin 继承此接口)
|
||||
│ └── plugin/ # 插件化框架
|
||||
│ ├── Plugin.java # 插件接口(扩展 Tool,增加生命周期)
|
||||
│ ├── PluginDescriptor.java # 插件描述符(id/name/version/description/dependencies)
|
||||
│ ├── PluginContext.java # 插件运行时上下文
|
||||
│ ├── PluginManager.java # 插件管理器(生命周期 + 注册 + 主菜单 + 命令分发)
|
||||
│ └── PluginState.java # 插件状态枚举(UNLOADED/LOADED/ACTIVE/DISABLED/ERROR)
|
||||
└── biz/ # 业务插件层(各模块是独立 Plugin)
|
||||
├── batch/ # 批量处理插件
|
||||
│ └── BatchTool.java # 批量检查入口(实现 Plugin)
|
||||
├── git/ # Git 操作插件
|
||||
│ ├── GitTool.java # Git 插件入口(实现 Plugin)
|
||||
│ └── service/ # 服务实现层
|
||||
│ ├── GitService.java # Git 服务接口
|
||||
│ ├── GitServiceImpl.java # Git 服务实现
|
||||
@@ -70,21 +75,21 @@ src/main/java/com/example/workutils/
|
||||
│ ├── GitStrategyFactory.java # 策略工厂
|
||||
│ ├── CheckBranchChangesStrategy.java
|
||||
│ └── CheckoutAndPullStrategy.java
|
||||
├── mybatisexport/ # MyBatis SQL导出工具(独立插件)
|
||||
│ ├── MyBatisExportTool.java # SQL 导出入口(实现 Tool)
|
||||
├── mybatisexport/ # MyBatis SQL导出插件
|
||||
│ ├── MyBatisExportTool.java # SQL 导出入口(实现 Plugin)
|
||||
│ └── service/
|
||||
│ ├── MyBatisExportService.java # SQL 导出服务
|
||||
│ └── model/
|
||||
│ └── MybatisTag.java # MyBatis 标签数据对象
|
||||
├── mybatisgenerator/ # MyBatis逆向工程工具(独立插件)
|
||||
│ ├── MyBatisGeneratorTool.java # 逆向工程入口(实现 Tool)
|
||||
├── mybatisgenerator/ # MyBatis逆向工程插件
|
||||
│ ├── MyBatisGeneratorTool.java # 逆向工程入口(实现 Plugin)
|
||||
│ └── service/generator/
|
||||
│ ├── MyBatisGeneratorService.java # 逆向工程服务
|
||||
│ ├── CustomCommentGenerator.java # 自定义注释生成器
|
||||
│ ├── LombokPlugin.java # Lombok 插件(@Data)
|
||||
│ └── SelectiveOnlyPlugin.java # 只保留 Selective 方法插件
|
||||
└── scanner/ # 扫描工具
|
||||
├── ScannerTool.java # 扫描工具入口(实现 Tool)
|
||||
└── scanner/ # 扫描插件
|
||||
├── ScannerTool.java # 扫描工具入口(实现 Plugin)
|
||||
└── service/ # 服务实现层
|
||||
├── ScannerService.java # 扫描服务
|
||||
└── config/
|
||||
@@ -100,92 +105,96 @@ src/main/java/com/example/workutils/
|
||||
#### `WorkUtilsApplication.java`
|
||||
- **作用**: Spring Boot 应用启动类,实现 `CommandLineRunner`
|
||||
- **功能**:
|
||||
- 支持交互式菜单模式(无参数启动),委托 `ToolRegistry.runMainMenu()`
|
||||
- 支持命令行模式,通过 `ToolRegistry.getToolByCommand()` 按命令名分发
|
||||
- 支持交互式菜单模式(无参数启动),委托 `PluginManager.runMainMenu()`
|
||||
- 支持命令行模式,通过 `PluginManager.getPluginByCommand()` 按命令名分发
|
||||
- 不依赖任何具体业务 Service,完全解耦
|
||||
|
||||
---
|
||||
|
||||
### 3.2 系统框架层(`system/`)
|
||||
|
||||
#### `system/tool/Tool.java`
|
||||
- **作用**: 工具契约接口,所有 biz 工具模块必须实现此接口
|
||||
- **核心方法**: `getId()`、`getName()`、`getMenuOptions()`、`execute()`(模板方法,含默认实现)、`getCommandName()`(命令行模式)、`executeCommand()`(命令行模式执行)
|
||||
#### 3.2.1 插件化框架(`system/plugin/`)
|
||||
|
||||
#### `system/tool/ToolRegistry.java`
|
||||
- **作用**: 工具注册中心,Spring 自动发现 `Tool` Bean,提供主菜单循环
|
||||
- **功能**: 根据配置过滤启用/禁用工具,支持白名单(enabled)和黑名单(disabled)模式,维护按命令名的索引,支持命令行模式按命令名分发
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `Plugin.java` | **插件接口**,继承 `Tool`,增加生命周期方法 `onLoad(PluginContext)` / `onUnload()`,`getId()`/`getName()` 默认委托给 `PluginDescriptor` |
|
||||
| `PluginDescriptor.java` | 插件描述符(不可变),包含 `id`、`name`、`version`、`description`、`dependencies`,通过 Builder 模式构建 |
|
||||
| `PluginContext.java` | 插件运行时上下文,提供 `PluginManager` 引用和跨插件共享属性 |
|
||||
| `PluginManager.java` | **插件管理器**(`@Component`),负责:生命周期管理(load/enable/disable)、注册发现(按 id/command 查找)、启用过滤(白名单/黑名单)、主菜单交互循环、命令行分发 |
|
||||
| `PluginState.java` | 插件状态枚举:`UNLOADED` → `LOADED` → `ACTIVE` / `DISABLED` / `ERROR` |
|
||||
|
||||
#### `system/support/ConsoleHelper.java`
|
||||
- **作用**: 控制台交互工具类(静态方法),统一控制台 I/O
|
||||
#### 3.2.2 Tool 基础契约(`system/tool/`)
|
||||
|
||||
#### `system/support/FileOperationService.java`
|
||||
- **作用**: 文件操作服务,提供文件读取、扫描、写入、项目路径管理等基础能力
|
||||
#### `Tool.java`
|
||||
- **作用**: 工具基础接口,定义 `getId()`、`getName()`、`getMenuOptions()`、`execute()`、`handleChoice()`、`getCommandName()`、`executeCommand()` 等方法
|
||||
- **关系**: `Plugin` 继承 `Tool`,biz 模块直接实现 `Plugin`
|
||||
|
||||
#### `system/support/OutputService.java`
|
||||
- **作用**: 输出服务,负责将数据输出为 CSV(OpenCSV)或 Excel(EasyExcel)格式,支持单Sheet/多Sheet、追加写入等
|
||||
#### 3.2.3 共享基础设施(`system/support/`)
|
||||
|
||||
#### `system/config/WorkUtilsProperties.java`
|
||||
- **作用**: 全局配置属性类,绑定 `application.yml` 中 `workutils` 前缀的配置
|
||||
- **功能**: 管理基础目录、配置目录、导出目录路径,提供带日期时间戳的导出文件路径生成,管理工具启用/禁用白名单和黑名单
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `ConsoleHelper.java` | 控制台交互工具类(静态方法),统一控制台 I/O |
|
||||
| `FileOperationService.java` | 文件操作服务,提供文件读取、扫描、写入、项目路径管理等基础能力 |
|
||||
| `OutputService.java` | 输出服务,负责将数据输出为 CSV(OpenCSV)或 Excel(EasyExcel)格式 |
|
||||
|
||||
#### 3.2.4 全局配置(`system/config/`)
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `WorkUtilsProperties.java` | 全局配置属性类(`@ConfigurationProperties`),管理路径、工具启用/禁用(`ToolsConfig`) |
|
||||
|
||||
---
|
||||
|
||||
### 3.3 业务工具层(`biz/`)
|
||||
### 3.3 业务插件层(`biz/`)
|
||||
|
||||
> **架构约定**: 每个 biz 模块的 Tool 类(如 `GitTool.java`)放在模块根目录作为入口,所有服务实现及附属文件(config/model/internal/generator)收在 `service/` 子包下,内部细节用 `internal/` 隔离。
|
||||
> **架构约定**: 每个 biz 模块的 Tool 类放在模块根目录,实现 `Plugin` 接口(继承自 `Tool`)。通过静态 `PluginDescriptor` 声明插件元数据(id/name/version/description)。所有服务实现及附属文件收在 `service/` 子包下,内部细节用 `internal/` 隔离。
|
||||
|
||||
#### 3.3.1 扫描工具(`biz/scanner/`)
|
||||
#### 3.3.1 扫描插件(`biz/scanner/`)
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `ScannerTool.java` | 扫描工具入口(实现 Tool),负责菜单交互,委托 ScannerService 执行业务逻辑 |
|
||||
| `ScannerTool.java` | 扫描插件入口(实现 Plugin),版本 `1.0.0`,负责菜单交互,委托 ScannerService 执行业务逻辑 |
|
||||
| `service/ScannerService.java` | 扫描服务,支持关键词扫描、Redis Key 扫描、Dubbo 接口扫描三种类型,支持并发多项目扫描 |
|
||||
| `service/config/ScanConfig.java` | 扫描配置,定义文件扩展名、排除目录、最大文件大小等参数 |
|
||||
|
||||
#### 3.3.2a MyBatis SQL导出工具(`biz/mybatisexport/`)
|
||||
#### 3.3.2a MyBatis SQL导出插件(`biz/mybatisexport/`)
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `MyBatisExportTool.java` | SQL 导出工具入口(实现 Tool),负责菜单交互,委托 MyBatisExportService 执行 |
|
||||
| `service/MyBatisExportService.java` | SQL 导出核心服务,从 XML 文件中提取 select/insert/update/delete 标签,支持按表名/关键字/标签ID 过滤导出为 CSV |
|
||||
| `service/model/MybatisTag.java` | MyBatis 标签数据对象,封装 tagId、tagName、parameterType、resultMap、namespace |
|
||||
| `MyBatisExportTool.java` | SQL 导出插件入口(实现 Plugin),版本 `1.0.0`,负责菜单交互 |
|
||||
| `service/MyBatisExportService.java` | SQL 导出核心服务,从 XML 文件中提取 select/insert/update/delete 标签 |
|
||||
| `service/model/MybatisTag.java` | MyBatis 标签数据对象 |
|
||||
|
||||
#### 3.3.2b MyBatis逆向工程工具(`biz/mybatisgenerator/`)
|
||||
#### 3.3.2b MyBatis逆向工程插件(`biz/mybatisgenerator/`)
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `MyBatisGeneratorTool.java` | 逆向工程工具入口(实现 Tool),负责菜单交互,委托 MyBatisGeneratorService 执行 |
|
||||
| `service/generator/MyBatisGeneratorService.java` | MyBatis 逆向工程服务,根据数据库表生成 PO、Mapper 接口、XML 映射文件 |
|
||||
| `service/generator/CustomCommentGenerator.java` | 自定义注释生成器,生成简洁中文注释 |
|
||||
| `service/generator/LombokPlugin.java` | Lombok 插件,为 PO 类添加 `@Data` 注解 |
|
||||
| `service/generator/SelectiveOnlyPlugin.java` | 自定义插件,只保留 `insertSelective` 和 `updateByPrimaryKeySelective` |
|
||||
| `MyBatisGeneratorTool.java` | 逆向工程插件入口(实现 Plugin),版本 `1.0.0` |
|
||||
| `service/generator/MyBatisGeneratorService.java` | MyBatis 逆向工程服务 |
|
||||
| `service/generator/CustomCommentGenerator.java` | 自定义注释生成器 |
|
||||
| `service/generator/LombokPlugin.java` | Lombok 插件(@Data) |
|
||||
| `service/generator/SelectiveOnlyPlugin.java` | 只保留 Selective 方法插件 |
|
||||
|
||||
> **设计说明**: 原 `biz/mybatis/` 已按工具维度拆分为 `mybatisexport` 和 `mybatisgenerator` 两个独立包,为后续**插件化设计**做准备——每个工具将独立打包、独立加载。
|
||||
|
||||
#### 3.3.3 Git 工具(`biz/git/`)
|
||||
#### 3.3.3 Git 插件(`biz/git/`)
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `GitTool.java` | Git 工具入口(实现 Tool),负责菜单交互,委托 GitService 执行 |
|
||||
| `service/GitService.java` | Git 服务接口,定义分支变更检查、切换拉取、健康检测、修复仓库等方法 |
|
||||
| `service/GitServiceImpl.java` | Git 服务实现,协调各策略执行仓库健康检测和修复逻辑 |
|
||||
| `service/config/GitOptimizationConfig.java` | Git 性能优化配置,管理超时、线程池、缓存、批处理、Fetch、网络等参数 |
|
||||
| `service/model/GitCommand.java` | Git 命令传输对象,定义命令类型枚举(CHECK_BRANCH_CHANGES、CHECKOUT_AND_PULL 等6种) |
|
||||
| `service/internal/GitCommandExecutor.java` | **核心执行器**,封装原生 git 命令执行(跨平台),提供超时控制、并发线程池、命令批处理 |
|
||||
| `service/internal/GitCommandController.java` | Git 命令控制器,管理命令的异步执行、跳过、中断 |
|
||||
| `service/internal/GitRepositoryManager.java` | Git 仓库管理器,提供仓库发现、分支操作、批量操作等高级封装 |
|
||||
| `service/internal/GitResultCache.java` | Git 结果缓存管理器,缓存仓库信息/分支信息/提交ID,减少重复 git 命令 |
|
||||
| `service/internal/strategy/GitCommandStrategy.java` | Git 命令策略接口 |
|
||||
| `service/internal/strategy/GitStrategyFactory.java` | Git 策略工厂,注册和管理所有策略实现 |
|
||||
| `service/internal/strategy/CheckBranchChangesStrategy.java` | 分支变更检查策略,比较两个分支的差异 |
|
||||
| `service/internal/strategy/CheckoutAndPullStrategy.java` | 切换并拉取策略,批量切换分支并拉取最新代码 |
|
||||
| `GitTool.java` | Git 插件入口(实现 Plugin),版本 `1.0.0` |
|
||||
| `service/GitService.java` | Git 服务接口 |
|
||||
| `service/GitServiceImpl.java` | Git 服务实现 |
|
||||
| `service/config/GitOptimizationConfig.java` | Git 性能优化配置 |
|
||||
| `service/model/GitCommand.java` | Git 命令传输对象 |
|
||||
| `service/internal/GitCommandExecutor.java` | **核心执行器**,跨平台 git 命令执行 |
|
||||
| `service/internal/GitCommandController.java` | Git 命令控制器(异步/中断) |
|
||||
| `service/internal/GitRepositoryManager.java` | Git 仓库管理器 |
|
||||
| `service/internal/GitResultCache.java` | Git 结果缓存管理器 |
|
||||
| `service/internal/strategy/*` | 策略模式实现 |
|
||||
|
||||
#### 3.3.4 批量工具(`biz/batch/`)
|
||||
#### 3.3.4 批量插件(`biz/batch/`)
|
||||
|
||||
| 文件 | 作用 |
|
||||
|------|------|
|
||||
| `BatchTool.java` | 批量处理工具入口(实现 Tool),显示批量检查选项,委托各 Service 执行 |
|
||||
| `BatchTool.java` | 批量处理插件入口(实现 Plugin),版本 `1.0.0`,一键执行多种检查 |
|
||||
|
||||
---
|
||||
|
||||
@@ -215,16 +224,18 @@ src/main/resources/
|
||||
|
||||
| 模式/特点 | 应用位置 | 说明 |
|
||||
|-----------|---------|------|
|
||||
| **策略模式** | `Tool` 接口 + 各 biz Tool 实现 | 各工具通过实现 `Tool` 接口注册到 `ToolRegistry`,可插拔扩展,支持交互和命令行双模式 |
|
||||
| **插件架构** | `Plugin` + `PluginManager` + `PluginDescriptor` | 每个 biz 模块作为独立 Plugin,声明版本、描述,PluginManager 统一管理生命周期(load/enable/disable),可插拔扩展 |
|
||||
| **策略模式** | `Tool` / `Plugin` 接口 + 各 biz 实现 | 各插件通过实现 Plugin 接口注册到 PluginManager,支持交互和命令行双模式 |
|
||||
| **策略模式** | `GitCommandStrategy` + 各 Strategy 实现 | Git 命令执行策略,`GitStrategyFactory` 管理注册 |
|
||||
| **工厂模式** | `ToolRegistry`、`GitStrategyFactory` | Spring 自动发现和注入 Bean |
|
||||
| **工厂模式** | `PluginManager`、`GitStrategyFactory` | Spring 自动发现和注入 Bean |
|
||||
| **命令模式** | `GitCommand` + `GitCommandController` | 封装 Git 命令的异步执行、中断、跳过 |
|
||||
| **缓存模式** | `GitResultCache` | 缓存 Git 查询结果,减少重复命令执行 |
|
||||
| **并发处理** | `GitCommandExecutor`、`ScannerService` | 线程池并发执行 Git 命令和多项目扫描 |
|
||||
| **过滤器模式** | `MyBatisExportService` 内部类 | `SqlFilter` 接口实现表名/关键字/标签ID 过滤 |
|
||||
| **模板回调** | `FileOperationService.scanFilesRecursively` | 使用 `SimpleFileVisitor` 实现文件遍历回调 |
|
||||
| **分层架构** | `system/` vs `biz/` | system 定义平台契约与共享基础设施,biz 实现具体工具 |
|
||||
| **分层架构** | `system/` vs `biz/` | system 定义平台契约与共享基础设施,biz 实现具体插件 |
|
||||
| **内部隔离** | `biz/*/service/internal/` | 内部实现类用 `internal/` 隔离,不对外暴露 |
|
||||
| **生命周期管理** | `Plugin.onLoad()` / `Plugin.onUnload()` | 插件加载/卸载回调,PluginContext 传递运行时上下文 |
|
||||
|
||||
---
|
||||
|
||||
@@ -232,21 +243,28 @@ src/main/resources/
|
||||
|
||||
```
|
||||
WorkUtilsApplication (入口)
|
||||
├── 交互模式 → ToolRegistry.runMainMenu()
|
||||
│ └── ToolRegistry.getEnabledTools()
|
||||
├── 交互模式 → PluginManager.runMainMenu()
|
||||
│ └── PluginManager.getEnabledPlugins()
|
||||
│ ├── ScannerTool ────────────→ ScannerService
|
||||
│ ├── MyBatisExportTool ──────→ MyBatisExportService
|
||||
│ ├── MyBatisGeneratorTool ───→ MyBatisGeneratorService
|
||||
│ ├── GitTool ────────────────→ GitService → GitStrategyFactory
|
||||
│ └── BatchTool ──────────────→ ScannerService + MyBatisExportService
|
||||
│
|
||||
└── 命令行模式 → ToolRegistry.getToolByCommand(command)
|
||||
└── 命令行模式 → PluginManager.getPluginByCommand(command)
|
||||
├── "scan" → ScannerTool.executeCommand(args)
|
||||
├── "sql" → MyBatisExportTool.executeCommand(args)
|
||||
├── "batch" → BatchTool.executeCommand(args)
|
||||
└── "git" → GitTool.executeCommand(args)
|
||||
(启动类不依赖任何具体业务 Service)
|
||||
|
||||
插件加载链路:
|
||||
PluginManager.init()
|
||||
├── 遍历 Spring 中所有 Plugin Bean
|
||||
├── 依次调用 plugin.onLoad(PluginContext)
|
||||
├── 按 ToolsConfig 过滤启用/禁用
|
||||
└── 构建 pluginByIdMap / pluginByCommandMap / pluginStates
|
||||
|
||||
核心依赖链:
|
||||
ScannerService → FileOperationService + OutputService
|
||||
MyBatisExportService → FileOperationService + OutputService
|
||||
@@ -273,3 +291,69 @@ WorkUtilsApplication (入口)
|
||||
2. **将数据库密码移到外部配置文件**或使用环境变量
|
||||
3. **更新 `项目说明.md`** 使其与实际技术栈一致
|
||||
4. **增加单元测试**,目前无测试代码
|
||||
|
||||
---
|
||||
|
||||
## 八、插件化架构设计
|
||||
|
||||
### 8.1 核心概念
|
||||
|
||||
```
|
||||
┌──────────────────────────────────────────────────────┐
|
||||
│ PluginManager │
|
||||
│ ┌─────────────┐ ┌──────────────┐ ┌────────────┐ │
|
||||
│ │ 生命周期管理 │ │ 注册与发现 │ │ 菜单/命令 │ │
|
||||
│ │ load/unload │ │ id/command │ │ 分发 │ │
|
||||
│ └─────────────┘ └──────────────┘ └────────────┘ │
|
||||
│ │
|
||||
│ pluginByIdMap pluginByCommandMap pluginStates │
|
||||
└──────────────────────┬───────────────────────────────┘
|
||||
│ onLoad(PluginContext)
|
||||
┌──────────────┼──────────────┬──────────────┐
|
||||
▼ ▼ ▼ ▼
|
||||
ScannerTool MyBatisExport MyBatisGen GitTool BatchTool
|
||||
[v1.0.0] [v1.0.0] [v1.0.0] [v1.0.0] [v1.0.0]
|
||||
```
|
||||
|
||||
### 8.2 接口层次
|
||||
|
||||
```
|
||||
Tool (system/tool/) ← 基础契约:getId/getName/getMenuOptions/execute/...
|
||||
↑
|
||||
Plugin (system/plugin/) ← 扩展契约:+getDescriptor/onLoad/onUnload
|
||||
↑
|
||||
各 biz 模块 ← 具体实现:@Component + PluginDescriptor
|
||||
```
|
||||
|
||||
### 8.3 插件描述符(PluginDescriptor)
|
||||
|
||||
每个插件通过静态 `PluginDescriptor` 声明自身元数据:
|
||||
|
||||
| 字段 | 说明 | 示例 |
|
||||
|------|------|------|
|
||||
| `id` | 全局唯一标识 | `"scan"`, `"mybatisexport"` |
|
||||
| `name` | 菜单显示名称 | `"扫描功能"` |
|
||||
| `version` | 语义化版本 | `"1.0.0"` |
|
||||
| `description` | 功能简述 | `"关键词/Redis/Dubbo扫描工具"` |
|
||||
| `dependencies` | 依赖的插件 ID(预留) | `[]` |
|
||||
|
||||
### 8.4 插件状态机
|
||||
|
||||
```
|
||||
UNLOADED ──load──▶ LOADED ──enable──▶ ACTIVE ──disable──▶ DISABLED
|
||||
▲ │ │ │
|
||||
│ │ error │ error │
|
||||
│ ▼ ▼ │
|
||||
└──────────────── ERROR ◀──────────── ERROR ◀─────────────┘
|
||||
unload ↻ UNLOADED
|
||||
```
|
||||
|
||||
### 8.5 后续插件化方向
|
||||
|
||||
当前已完成插件化基础框架搭建,后续可进一步实现:
|
||||
|
||||
1. **SPI 自动发现** — 通过 `META-INF/services` 或 Spring Factories 自动发现外部 jar 中的 Plugin 实现
|
||||
2. **独立打包** — 每个 biz 模块打成独立 jar,`PluginManager` 动态加载
|
||||
3. **热加载** — 基于自定义 ClassLoader 实现插件的动态加载/卸载/更新
|
||||
4. **依赖注入隔离** — 每个插件使用独立 Spring 子容器(`ApplicationContext`),避免 Bean 冲突
|
||||
5. **插件市场** — 插件仓库 + 版本管理 + 远程下载
|
||||
|
||||
Reference in New Issue
Block a user