乐于分享
好东西不私藏

eGPS v2.1 模块/插件认知教程《二》

eGPS v2.1 模块/插件认知教程《二》

eGPS v2.1 模块/插件认知教程《二》

https://github.com/yudalang3/egps-shell/blob/main/manuals/00Readme_zh.md

这里 github 的地址是永久地址。

eGPS 插件开发教程(Plugin 模式)

📌 什么是 Plugin 模式

Plugin 模式是指将模块打包成 JAR 文件,放置到用户配置目录 ~/.egps2/config/plugin/ 中,作为外部扩展的方式。

适用场景

  • 🔌 第三方开发者发布的工具
  • 👥 需要分发给其他用户的功能
  • 🧪 实验性功能和原型
  • 🎨 自定义工具和扩展

🎯 两种开发方式选择

方式 1: 继承 FastBaseTemplate(推荐新手)

适合

  • 简单的工具型插件
  • 快速原型开发
  • 单一功能模块
  • UI 为主的插件

代码量: 约 50-100 行

方式 2: 实现 IModuleLoader(推荐进阶)

适合

  • 复杂的业务逻辑
  • 需要精确控制的功能
  • 团队协作开发
  • 大型插件项目

代码量: 约 150-300 行


📚 方式 1: 继承 FastBaseTemplate

第一步:创建项目结构

my-plugin/
├── src/
│   └── com/mycompany/myplugin/
│       └── MySimplePlugin.java
├── build/
└── dist/

第二步:编写插件类

创建 src/com/mycompany/myplugin/MySimplePlugin.java

package com.mycompany.myplugin;

import egps2.plugin.fastmodtem.FastBaseTemplate;
import javax.swing.*;
import java.awt.*;

/**
 * 我的第一个 eGPS 插件
 *
 * 这是一个简单的文本处理工具示例
 */

publicclassMySimplePluginextendsFastBaseTemplate{

private JTextArea inputArea;
private JTextArea outputArea;

publicMySimplePlugin(){
super();
        initUI();
    }

privatevoidinitUI(){
        setLayout(new BorderLayout(1010));
        setBorder(BorderFactory.createEmptyBorder(10101010));

// 顶部:标题
        JLabel titleLabel = new JLabel("文本处理工具", JLabel.CENTER);
        titleLabel.setFont(new Font("Arial", Font.BOLD, 16));
        add(titleLabel, BorderLayout.NORTH);

// 中间:输入输出区域
        JPanel centerPanel = new JPanel(new GridLayout(12100));

// 输入面板
        JPanel inputPanel = new JPanel(new BorderLayout());
        inputPanel.setBorder(BorderFactory.createTitledBorder("输入文本"));
        inputArea = new JTextArea();
        inputArea.setLineWrap(true);
        inputPanel.add(new JScrollPane(inputArea), BorderLayout.CENTER);

// 输出面板
        JPanel outputPanel = new JPanel(new BorderLayout());
        outputPanel.setBorder(BorderFactory.createTitledBorder("处理结果"));
        outputArea = new JTextArea();
        outputArea.setLineWrap(true);
        outputArea.setEditable(false);
        outputPanel.add(new JScrollPane(outputArea), BorderLayout.CENTER);

        centerPanel.add(inputPanel);
        centerPanel.add(outputPanel);
        add(centerPanel, BorderLayout.CENTER);

// 底部:操作按钮
        JPanel buttonPanel = new JPanel(new FlowLayout(FlowLayout.CENTER, 105));

        JButton upperCaseBtn = new JButton("转大写");
        upperCaseBtn.addActionListener(e -> processText(String::toUpperCase));

        JButton lowerCaseBtn = new JButton("转小写");
        lowerCaseBtn.addActionListener(e -> processText(String::toLowerCase));

        JButton reverseBtn = new JButton("反转");
        reverseBtn.addActionListener(e -> processText(this::reverseString));

        JButton clearBtn = new JButton("清空");
        clearBtn.addActionListener(e -> {
            inputArea.setText("");
            outputArea.setText("");
        });

        buttonPanel.add(upperCaseBtn);
        buttonPanel.add(lowerCaseBtn);
        buttonPanel.add(reverseBtn);
        buttonPanel.add(clearBtn);

        add(buttonPanel, BorderLayout.SOUTH);
    }

/**
     * 处理文本
     */

privatevoidprocessText(java.util.function.Function<String, String> processor){
        String input = inputArea.getText();
if (input.isEmpty()) {
            JOptionPane.showMessageDialog(this"请先输入文本!""提示", JOptionPane.WARNING_MESSAGE);
return;
        }
        String output = processor.apply(input);
        outputArea.setText(output);
    }

/**
     * 反转字符串
     */

private String reverseString(String str){
returnnew StringBuilder(str).reverse().toString();
    }

// ===== 必须实现的方法 =====

@Override
public String getTabName(){
return"文本工具";
    }

@Override
public String getShortDescription(){
return"简单的文本处理工具 - 支持大小写转换和反转";
    }

@Override
publicint[] getCategory() {
return ModuleClassification.getOneModuleClassification(
            ModuleClassification.BYFUNCTIONALITY_SIMPLE_TOOLS_INDEX,   // 功能类型
            ModuleClassification.BYAPPLICATION_COMMON_MODULE_INDEX,    // 应用领域
            ModuleClassification.BYCOMPLEXITY_LEVEL_1_INDEX,           // 复杂度
            ModuleClassification.BYDEPENDENCY_ONLY_EMPLOY_CONTAINER    // 依赖性
        );
    }
}

第三步:编译插件

# 设置变量
EGPS_HOME="/path/to/egps-main.gui"
SRC_DIR="src"
BUILD_DIR="build"

# 创建构建目录
mkdir -p $BUILD_DIR

# 编译
javac -d $BUILD_DIR \
      -cp "$EGPS_HOME/dependency-egps/*:$EGPS_HOME/out/production/egps-main.gui" \
      -encoding UTF-8 \
$SRC_DIR/com/mycompany/myplugin/MySimplePlugin.java

第四步:创建配置文件

在 build 目录下创建 eGPS2.plugin.properties

# 插件主类(必需)
launchClass=com.mycompany.myplugin.MySimplePlugin

# 插件名称(必需)
pluginName=My Simple Plugin

# 版本号(必需)
version=1.0.0

# 作者(可选)
author=Your Name

# 描述(可选)
description=A simple text processing tool

第五步:打包 JAR

# 进入 build 目录
cd build

# 创建 JAR 文件
jar cvf my-simple-plugin.jar .

# 返回上级目录
cd ..

# 移动到 dist 目录
mkdir -p dist
mv build/my-simple-plugin.jar dist/

第六步:安装插件

# 复制到插件目录
cp dist/my-simple-plugin.jar ~/.egps2/config/plugin/

第七步:测试插件

# 启动 eGPS
cd$EGPS_HOME
java -cp "./out/production/egps-main.gui:dependency-egps/*" egps2.Launcher

# 按 Ctrl+2 打开 Module Gallery
# 或者从菜单 Plugins → My Simple Plugin

📚 方式 2: 实现 IModuleLoader

第一步:创建项目结构

my-complex-plugin/
├── src/
│   └── com/mycompany/complexplugin/
│       ├── MyComplexPlugin.java        # 加载器
│       └── MyComplexPluginPanel.java   # 面板
├── build/
└── dist/

第二步:编写加载器类

创建 src/com/mycompany/complexplugin/MyComplexPlugin.java

package com.mycompany.complexplugin;

import egps2.modulei.IModuleLoader;
import egps2.modulei.IconBean;
import egps2.frame.ModuleFace;
import javax.swing.*;

/**
 * 插件加载器类
 *
 * 职责:
 * - 实现 IModuleLoader 接口
 * - 提供模块元信息(名称、描述、分类等)
 * - 创建并返回面板实例
 */

publicclassMyComplexPluginimplementsIModuleLoader{

private MyComplexPluginPanel panel;

publicMyComplexPlugin(){
// 在构造函数中创建面板
        panel = new MyComplexPluginPanel(this);
    }

@Override
public String getTabName(){
return"复杂数据分析工具";
    }

@Override
public String getShortDescription(){
return"演示复杂插件的开发 - 数据分析、可视化、导入导出";
    }

@Override
public ModuleFace getFace(){
// 返回面板实例
return panel;
    }

@Override
publicint[] getCategory() {
return ModuleClassification.getOneModuleClassification(
            ModuleClassification.BYFUNCTIONALITY_PROFESSIONAL_COMPUTATION_INDEX,  // 功能类型
            ModuleClassification.BYAPPLICATION_GENOMICS_INDEX,                    // 应用领域
            ModuleClassification.BYCOMPLEXITY_LEVEL_3_INDEX,                      // 复杂度
            ModuleClassification.BYDEPENDENCY_ONLY_EMPLOY_CONTAINER               // 依赖性
        );
    }

@Override
public IconBean getIcon(){
// 可以返回自定义图标
// 这里返回 null 使用默认图标
returnnull;
    }

@Override
public JPanel getEnglishDocument(){
// 可以提供英文帮助文档
return createDocumentPanel("English""This is a complex plugin example...");
    }

@Override
public JPanel getChineseDocument(){
// 可以提供中文帮助文档
return createDocumentPanel("中文""这是一个复杂插件示例...");
    }

/**
     * 创建帮助文档面板
     */

private JPanel createDocumentPanel(String title, String content){
        JPanel panel = new JPanel(new BorderLayout());
        panel.setBorder(BorderFactory.createTitledBorder(title));

        JTextArea textArea = new JTextArea(content);
        textArea.setEditable(false);
        textArea.setLineWrap(true);
        textArea.setWrapStyleWord(true);

        panel.add(new JScrollPane(textArea), BorderLayout.CENTER);
return panel;
    }
}

第三步:编写面板类

创建 src/com/mycompany/complexplugin/MyComplexPluginPanel.java

package com.mycompany.complexplugin;

import egps2.frame.ModuleFace;
import egps2.modulei.IModuleLoader;
import javax.swing.*;
import java.awt.*;
import java.io.*;

/**
 * 插件面板类
 *
 * 职责:
 * - 继承 ModuleFace
 * - 实现具体的 UI 界面
 * - 实现业务逻辑
 * - 处理数据导入导出
 */

publicclassMyComplexPluginPanelextendsModuleFace{

private JTextArea dataArea;
private JTextArea resultArea;
privateboolean hasData = false;

publicMyComplexPluginPanel(IModuleLoader loader){
super(loader);
        initUI();
    }

privatevoidinitUI(){
        setLayout(new BorderLayout(1010));
        setBorder(BorderFactory.createEmptyBorder(10101010));

// 顶部工具栏
        JToolBar toolBar = new JToolBar();
        toolBar.setFloatable(false);

        JButton importBtn = new JButton("导入数据");
        importBtn.addActionListener(e -> importData());

        JButton exportBtn = new JButton("导出结果");
        exportBtn.addActionListener(e -> exportData());

        JButton analyzeBtn = new JButton("开始分析");
        analyzeBtn.addActionListener(e -> analyzeData());

        toolBar.add(importBtn);
        toolBar.add(exportBtn);
        toolBar.addSeparator();
        toolBar.add(analyzeBtn);

        add(toolBar, BorderLayout.NORTH);

// 中间分割面板
        JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);

// 左侧:原始数据
        JPanel dataPanel = new JPanel(new BorderLayout());
        dataPanel.setBorder(BorderFactory.createTitledBorder("原始数据"));
        dataArea = new JTextArea();
        dataArea.setLineWrap(true);
        dataPanel.add(new JScrollPane(dataArea), BorderLayout.CENTER);

// 右侧:分析结果
        JPanel resultPanel = new JPanel(new BorderLayout());
        resultPanel.setBorder(BorderFactory.createTitledBorder("分析结果"));
        resultArea = new JTextArea();
        resultArea.setEditable(false);
        resultArea.setLineWrap(true);
        resultPanel.add(new JScrollPane(resultArea), BorderLayout.CENTER);

        splitPane.setLeftComponent(dataPanel);
        splitPane.setRightComponent(resultPanel);
        splitPane.setDividerLocation(0.5);

        add(splitPane, BorderLayout.CENTER);

// 底部状态栏
        JPanel statusPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        statusPanel.setBorder(BorderFactory.createEtchedBorder());
        JLabel statusLabel = new JLabel("就绪");
        statusPanel.add(statusLabel);

        add(statusPanel, BorderLayout.SOUTH);
    }

/**
     * 分析数据
     */

privatevoidanalyzeData(){
        String data = dataArea.getText();
if (data.isEmpty()) {
            JOptionPane.showMessageDialog(this"请先输入或导入数据!""警告", JOptionPane.WARNING_MESSAGE);
return;
        }

// 简单的数据分析示例
        String[] lines = data.split("\n");
int lineCount = lines.length;
int wordCount = 0;
int charCount = data.length();

for (String line : lines) {
            wordCount += line.split("\\s+").length;
        }

        StringBuilder result = new StringBuilder();
        result.append("=== 数据分析结果 ===\n\n");
        result.append("行数: ").append(lineCount).append("\n");
        result.append("词数: ").append(wordCount).append("\n");
        result.append("字符数: ").append(charCount).append("\n");
        result.append("\n=== 详细信息 ===\n\n");

for (int i = 0; i < lines.length; i++) {
            result.append("第 ").append(i + 1).append(" 行: ")
                  .append(lines[i].length()).append(" 字符\n");
        }

        resultArea.setText(result.toString());
        hasData = true;
    }

// ===== 实现 ModuleFace 的抽象方法 =====

@Override
publicbooleancanImport(){
// 表示这个模块支持导入数据
returntrue;
    }

@Override
publicvoidimportData(){
        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setDialogTitle("选择要导入的文本文件");

if (fileChooser.showOpenDialog(this) == JFileChooser.APPROVE_OPTION) {
            File file = fileChooser.getSelectedFile();
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
                StringBuilder content = new StringBuilder();
                String line;
while ((line = reader.readLine()) != null) {
                    content.append(line).append("\n");
                }
                dataArea.setText(content.toString());
                hasData = true;
                JOptionPane.showMessageDialog(this"数据导入成功!""成功", JOptionPane.INFORMATION_MESSAGE);
            } catch (IOException e) {
                JOptionPane.showMessageDialog(this"导入失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
            }
        }
    }

@Override
publicbooleancanExport(){
// 只有在有结果时才能导出
return hasData && !resultArea.getText().isEmpty();
    }

@Override
publicvoidexportData(){
if (!canExport()) {
            JOptionPane.showMessageDialog(this"没有可导出的数据!""警告", JOptionPane.WARNING_MESSAGE);
return;
        }

        JFileChooser fileChooser = new JFileChooser();
        fileChooser.setDialogTitle("选择导出位置");

if (fileChooser.showSaveDialog(this) == JFileChooser.APPROVE_OPTION) {
            File file = fileChooser.getSelectedFile();
try (PrintWriter writer = new PrintWriter(new FileWriter(file))) {
                writer.print(resultArea.getText());
                JOptionPane.showMessageDialog(this"结果导出成功!""成功", JOptionPane.INFORMATION_MESSAGE);
            } catch (IOException e) {
                JOptionPane.showMessageDialog(this"导出失败: " + e.getMessage(), "错误", JOptionPane.ERROR_MESSAGE);
            }
        }
    }

@Override
public String[] getFeatureNames() {
// 返回这个模块提供的功能列表
returnnew String[]{
"文本数据导入",
"数据统计分析",
"结果导出"
        };
    }

@Override
protectedvoidinitializeGraphics(){
// 如果有图形元素需要初始化,在这里实现
// 本示例不需要图形功能
    }

@Override
publicbooleanmoduleExisted(){
// 判断模块是否有未保存的数据
// 如果返回 true,关闭标签页时会弹出确认对话框
return hasData;
    }
}

第四步:编译、打包、安装

编译脚本同方式 1,只需要编译多个文件:

# 编译
javac -d $BUILD_DIR \
      -cp "$EGPS_HOME/dependency-egps/*:$EGPS_HOME/out/production/egps-main.gui" \
      -encoding UTF-8 \
$SRC_DIR/com/mycompany/complexplugin/*.java

配置文件和打包方式相同。


🎨 进阶技巧

1. 使用自定义图标

自定义图标需要在Loader接口类(即插件主类)中实现 getIcon() 接口,在这个接口中需要返回一个 IconBean 类型的对象。

为此,还需要在文件头引入 egps2.modulei.IconBean 。

import egps2.modulei.IconBean;

getIcon() 是一个抽象方法,在插件主类中实现时需要加上 @Override 修饰词。

@Override
public IconBean getIcon(){
// 从插件 JAR 内部读取图标
    InputStream is = getClass().getResourceAsStream("/icons/my-icon.png");
if (is == null) {
// 建议加空指针检查,避免后续 NPE
thrownew RuntimeException("Icon resource not found!");
    }
// 创建一个IconBean()对象。这一对象不支持在创建时传参,参数传递依赖于后续的方法调用
    IconBean icon = new IconBean();
    icon.setInputStream(is); // 传入InputStream对象,作为icon内容
    icon.setSVG(false); // 如果icon是一个SVG文件,此处设置为true;如果是PNG,则设置为false
return icon;
}

记得将图标文件放到 build/icons/my-icon.png

2. 添加第三方依赖

如果你的插件需要第三方库:

# 方法 1: 将依赖 JAR 解压后一起打包
cd build
jar xf /path/to/dependency.jar
rm -rf META-INF
cd ..

# 方法 2: 使用 Maven/Gradle 打包成 fat JAR

3. 多语言支持

private String getString(String key){
    Locale locale = Locale.getDefault();
if (locale.getLanguage().equals("zh")) {
return getChineseString(key);
    } else {
return getEnglishString(key);
    }
}

4. 保存用户配置

// 配置会保存到 ~/.egps2/config/
Properties props = new Properties();
props.setProperty("lastFile""/path/to/file");

String configDir = System.getProperty("user.home") + "/.egps2/config/";
File configFile = new File(configDir, "myplugin.properties");
props.store(new FileWriter(configFile), "My Plugin Config");

📦 完整构建脚本示例

创建 build.sh

#!/bin/bash
set -e

EGPS_HOME="/path/to/egps-main.gui"
PLUGIN_NAME="my-simple-plugin"
VERSION="1.0.0"

echo"🔨 构建 $PLUGIN_NAME v$VERSION"

# 清理
rm -rf build dist
mkdir -p build dist

# 编译
echo"⚙️  编译中..."
javac -d build \
      -cp "$EGPS_HOME/dependency-egps/*:$EGPS_HOME/out/production/egps-main.gui" \
      -encoding UTF-8 \
      src/com/mycompany/myplugin/*.java

# 创建配置
echo"📝 生成配置文件..."
cat > build/eGPS2.plugin.properties << EOF
launchClass=com.mycompany.myplugin.MySimplePlugin
pluginName=$PLUGIN_NAME
version=$VERSION
author=Your Name
EOF

# 打包
echo"📦 打包 JAR..."
cd build
jar cvf $PLUGIN_NAME-$VERSION.jar . > /dev/null 2>&1
cd ..

# 移动到 dist
mv build/$PLUGIN_NAME-$VERSION.jar dist/

echo"✅ 构建完成: dist/$PLUGIN_NAME-$VERSION.jar"

# 可选:自动安装
read -p "是否安装到插件目录? (y/n) " -n 1 -r
echo
if [[ $REPLY =~ ^[Yy]$ ]]; then
    cp dist/$PLUGIN_NAME-$VERSION.jar ~/.egps2/config/plugin/
echo"✅ 已安装到 ~/.egps2/config/plugin/"
fi

🐛 调试技巧

1. 查看控制台输出

System.out.println("调试信息: " + someValue);

启动 eGPS 时能在控制台看到输出。

2. 使用日志

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

publicclassMyPluginextendsFastBaseTemplate{
privatestaticfinal Logger logger = LoggerFactory.getLogger(MyPlugin.class);

publicMyPlugin(){
super();
        logger.info("MyPlugin initialized");
        logger.debug("Debug info: {}", someValue);
    }
}

3. 异常处理

try {
// 你的代码
catch (Exception e) {
    logger.error("Error occurred", e);
    JOptionPane.showMessageDialog(this,
"错误: " + e.getMessage(),
"错误",
        JOptionPane.ERROR_MESSAGE);
}

✅ 检查清单

在发布插件前,确保:

  • [ ] 插件类正确实现了 IModuleLoader 或继承了 FastBaseTemplate
  • [ ] eGPS2.plugin.properties 文件存在且配置正确
  • [ ] launchClass 指向的类存在且包名正确
  • [ ] 所有需要的类和资源文件都打包进 JAR
  • [ ] 插件在 eGPS 中能正常加载和运行
  • [ ] UI 界面正常显示
  • [ ] 所有功能按钮都能正常工作
  • [ ] 导入/导出功能正常(如果有)
  • [ ] 没有明显的 Bug 或异常
  • [ ] 编写了基本的使用说明

📚 下一步

  • eGPS2.plugin.properties_zh.md – 配置文件详细说明(推荐阅读)
  • 03_BUILTIN_DEVELOPMENT_zh.md – 学习如何开发内置模块
  • 04_ARCHITECTURE_zh.md – 深入理解 eGPS 模块系统架构
  • 参考内置模块源代码: src/egps2/builtin/modules/

版本: eGPS 2.1+最后更新: 2025-12-05作者: eGPS Dev Team

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » eGPS v2.1 模块/插件认知教程《二》

猜你喜欢

  • 暂无文章