# MyPlugin **Repository Path**: gaoruishan/my-plugin ## Basic Information - **Project Name**: MyPlugin - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-04-17 - **Last Updated**: 2026-10-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # GaoRuiShan Tools — IntelliJ IDEA 翻译插件 > 第一个自研 IDEA 插件,傻瓜式使用。在编辑器中选中英文,一键翻译为中文,结果弹窗展示。 --- ## 功能特性 - 选中编辑器中的任意文本,触发翻译 - 支持三种触发方式:工具栏按钮 / 编辑器右键菜单 / 快捷键 `Ctrl+G` - 调用网易有道智云翻译 API(V3 鉴权,SHA-256 签名) - API 密钥采用 XOR 混淆存储,避免明文泄露 --- ## 环境要求 | 依赖 | 版本 | |------|------| | IntelliJ IDEA | 2021.3.x(SDK: IU-213.6461.79) | | JDK | 11(Language Level 11) | | 插件最低兼容 | IDEA 2017.3+(since-build 173.0) | --- ## 快速开始 ### 1. 克隆仓库 ```bash git clone https://gitee.com/gaoruishan/my-plugin.git cd my-plugin ``` ### 2. 配置项目(Project Structure) 打开 **File → Project Structure**: | 设置项 | 值 | |--------|----| | Name | `MyPlugin` | | SDK | `IntelliJ IDEA IU-213.6461.79`(java 11) | | Language level | `11 - Local variable syntax for lambda parameters` | | Compiler output | `D:\IdeaProjects\MyPlugin\out` | > 在 **Libraries** 选项卡中确认 `libs/` 下三个 jar 已加入:`fastjson-1.2.83.jar`、`okhttp-3.14.9.jar`、`okio-1.17.2.jar`。 > 另外确认 **Modules → MyPlugin** 的模块类型为 **IntelliJ Platform Plugin**(`.iml` 中 `type="PLUGIN_MODULE"`),且右侧 **Plugin Deployment** 面板的 **Descriptor** 指向 `resources/META-INF/plugin.xml`。若模块类型不对,打包动作不会出现,详见文末「常见问题排查」。 ### 3. 配置运行(Run/Debug Configurations) 打开 **Run → Edit Configurations**,添加一个 **Plugin** 类型: | 配置项 | 值 | |--------|----| | VM Options | `-Xmx512m -Xms256m -ea` | | Use classpath of module | `MyPlugin` | | JRE | `IntelliJ IDEA IU-213.6461.79` | ### 4. 运行插件 点击 **Run**,IDEA 会启动一个沙箱 IDE 实例,插件已自动安装。 在沙箱中打开任意文件,选中英文文本,按 `Ctrl+G` 或点击工具栏图标即可看到翻译弹窗。 --- ## 项目结构 ``` MyPlugin/ ├── src/com/grs/myplugin/action/ │ ├── MyTranslate.java # 主 Action(插件触发入口) │ ├── TranslateDemo.java # 独立测试 Demo(含 main 方法) │ ├── ObfuscatedApiKey.java # API 密钥安全存储(XOR 混淆) │ ├── Xml2UdlPreviewAction.java # 加载 IRIS XML 到工具窗口的 Action │ ├── ExportIrisXmlAction.java # 右键 XML,批量导出全部组件为 UDL 的 Action │ └── utils/ │ ├── AuthV3Util.java # 有道鉴权 V3 工具 │ ├── AuthV4Util.java # 有道鉴权 V4 工具(媒体接口备用) │ ├── HttpUtil.java # OkHttp GET/POST 封装 │ ├── FileUtil.java # 文件 Base64 读写工具 │ └── WebSocketUtil.java # WebSocket 工具(已注释,预留扩展) ├── src/com/grs/myplugin/xml2udl/ │ ├── XmlToUdlEngine.java # 核心引擎:XML 解析 + UDL 生成 │ ├── UdlMetaDefinitions.java # 字段 → UDL 方括号属性序列化规则表 │ ├── XmlTreeNode.java # 组件树节点(叶子持有 DOM) │ ├── ParsedXmlFile.java # 解析结果(文件名 + 根节点) │ ├── UdlResult.java # 生成的 UDL 文本 + 建议扩展名 │ └── ui/ │ ├── Xml2UdlToolWindowFactory.java # 注册工具窗口 │ ├── Xml2UdlToolWindowPanel.java # 组件树面板(双击预览 / 右键导出) │ ├── Xml2UdlTreeNode.java # Swing 树节点适配器 │ ├── UdlPreviewService.java # 内存预览(LightVirtualFile) │ └── UdlExportService.java # 导出 UDL 到磁盘 ├── resources/ │ ├── META-INF/plugin.xml # 插件元数据与 Action 注册配置 │ └── icons/ │ ├── picon.png # 工具栏图标(当前使用) │ └── picon2.png # 备用图标 └── libs/ ├── fastjson-1.2.83.jar ├── okhttp-3.14.9.jar └── okio-1.17.2.jar ``` --- ## 模块配置文件(MyPlugin.iml) `MyPlugin.iml` 是 IDEA 的模块描述文件,本项目的关键字段说明如下: | 字段 | 值 | 作用 | |------|----|------| | `module type` | `PLUGIN_MODULE` | 声明为 IntelliJ 平台插件模块;只有该类型才会出现 **Prepare Plugin Module...For Deployment** 打包动作 | | `DevKit.ModuleBuildProperties` | `resources/META-INF/plugin.xml` | 指定插件描述符(plugin.xml)路径,即 **Plugin Deployment** 面板中的 Descriptor | | `sourceFolder src` | `src` | Java 源码根 | | `sourceFolder resources` | `type="java-resource"` | **资源根**,使 `icon="/icons/picon.png"` 等以 `/` 开头的路径可被解析 | | `orderEntry inheritedJdk` | — | 继承模块/项目 SDK(IntelliJ IDEA 平台 SDK) | | `orderEntry library libs` | 项目级 | 引入 `libs/` 下的 fastjson / okhttp / okio | > ⚠️ 若缺少 ``,会报 `Cannot resolve icon '/icons/picon.png'`——因为 `icon` 的 `/` 路径需要在某个**资源根**下查找。 > 该文件由 IDE 自动读写;手工修改后请执行 **File → Reload All from Disk** 使其生效。 --- ## 核心调用流程 ``` 用户触发(Ctrl+G / 工具栏 / 右键菜单) ↓ MyTranslate#actionPerformed() ↓ 获取编辑器选中文本 ↓ translate(text) ├─ ObfuscatedApiKey → 解密 AppKey / AppSecret ├─ createRequestParams() → { q, from=en, to=zh-CHS } ├─ AuthV3Util.addAuthParams() │ └─ sign = sha256(appKey + input(q) + salt + curtime + appSecret) ├─ HttpUtil.doPost("https://openapi.youdao.com/api") └─ fastjson 解析 JSON → translation[0] ↓ Messages.showMessageDialog() 弹窗展示译文 ``` --- ## IRIS XML → UDL 预览 插件还提供 InterSystems IRIS XML Export 的只读 UDL 预览能力: 1. 在 Project 视图或编辑器中右键 `.xml` 文件。 2. 选择 **预览IRIS XML**。 3. 右侧会打开 **IRIS XML to UDL** 工具窗口,按 Class、CSP、Routine、Project 等顶层类型分组展示组件。 4. 双击具体组件,在 IDEA 编辑器中打开内存中的只读预览内容。 5. 右键具体组件选择 **导出cls**,将该组件生成的文件写入**源 XML 所在目录**(自动按组件类型使用扩展名:Class→`.cls`、CSP→`.csp`、JavaScript→`.js`、Routine→`.cls`/`.int` 等;同名文件将被覆盖)。 若需一次性导出文件里的**全部组件**,可在 Project 视图或编辑器中右键 `.xml` 文件,选择 **导出IRIS XML**:插件会解析该 XML 并把其中每个组件生成为独立 UDL 文件,全部写入**源 XML 所在目录**,完成后弹窗提示导出数量。此动作不依赖工具窗口,无需先执行「预览IRIS XML」。 转换器使用 JDK 标准 DOM API,不额外引入 XML 依赖。预览在内存中进行,不落地临时文件;**导出cls**(单个组件)与 **导出IRIS XML**(批量全部组件)是仅有的写磁盘操作。它支持 Class、Property、Parameter、Method、Query、Index、ForeignKey、Projection、Storage、Trigger、XData、Routine、CSP、Project 和 UDLText。 当前限制:首期仅支持 XML → UDL;不包含 `.cls` → XML 反向导出,且未注册自定义 UDL 语法高亮。若已安装能识别 `.cls` 的语言插件,编辑器会按其文件关联提供高亮。 ### XML → UDL 调用链 ```text 预览IRIS XML Action ↓ 后台读取与 DOM 解析 XmlToUdlEngine ↓ 按顶层 XML 标签构造组件树 IRIS XML to UDL Tool Window ↓ 双击叶子节点 LightVirtualFile(内存 UDL 预览) ↓ 右键叶子节点 → 导出cls 写入源 XML 所在目录(磁盘文件) ``` ### 模块职责(xml2udl) | 层 | 文件 | 职责 | |----|------|------| | 核心转换 | `XmlToUdlEngine` | 解析 XML、构建组件树,按节点类型(Class/Property/Method 等)生成 UDL | | 核心转换 | `UdlMetaDefinitions` | 定义字段 → 方括号属性的序列化规则(前缀 `D:`/`B:`/`I:`,标志 `M`/`Q`/`N:`) | | 核心转换 | `XmlTreeNode` | 组件树节点,叶子节点持有原始 DOM、分组节点为空 | | 核心转换 | `ParsedXmlFile` / `UdlResult` | 解析结果与生成结果的数据载体 | | UI | `Xml2UdlToolWindowFactory` / `Xml2UdlToolWindowPanel` | 注册工具窗口,驱动组件树展示与交互 | | UI | `Xml2UdlTreeNode` | Swing 树节点适配器,委托底层 `XmlTreeNode` | | UI | `UdlPreviewService` / `UdlExportService` | 内存预览(LightVirtualFile)与磁盘导出 | | Action | `Xml2UdlPreviewAction` | 读取选中的 XML、后台解析、回到 EDT 刷新工具窗口 | | Action | `ExportIrisXmlAction` | 右键 XML、后台解析、回到 EDT 批量导出全部组件到源 XML 目录 | --- ## API 密钥配置 本项目使用**网易有道智云**翻译服务。 1. 前往 [https://ai.youdao.com](https://ai.youdao.com) 注册账号并创建应用。 2. 获取 **应用ID(APP_KEY)** 和 **应用密钥(APP_SECRET)**。 3. 打开 `ObfuscatedApiKey.java`,取消注释底部 `main` 方法。 4. 将 `ORIGINAL_key` 和注释中的原始密钥替换为你自己的值。 5. 运行 `GenerateObfuscatedBytes()` 生成新的混淆字节数组。 6. 将输出的字节数组替换 `OBFUSCATED_BYTES_KEY` / `OBFUSCATED_BYTES_SECRET`。 7. 重新注释 `main` 方法,提交代码。 > API 文档:[有道文本翻译服务](https://ai.youdao.com/DOCSIRMA/html/%E8%87%AA%E7%84%B6%E8%AF%AD%E8%A8%80%E7%BF%BB%E8%AF%91/API%E6%96%87%E6%A1%A3/%E6%96%87%E6%9C%AC%E7%BF%BB%E8%AF%91%E6%9C%8D%E5%8A%A1/%E6%96%87%E6%9C%AC%E7%BF%BB%E8%AF%91%E6%9C%8D%E5%8A%A1-API%E6%96%87%E6%A1%A3.html) --- ## 构建发布 本项目是 **DevKit 模式**(无 Gradle/Maven),依赖手工放在 `libs/`。有两种打包方式,产物都是标准插件 zip。 ### 方式一:IDE 一键打包(推荐) 1. 菜单 **Build → Prepare Plugin Module 'MyPlugin' For Deployment**(Project 视图右键模块 → 同名项亦可)。 2. 完成后右下角提示 `Zip for module 'MyPlugin' was saved to ...`。 3. 产物位于**项目根目录**:`D:\IdeaProjects\MyPlugin\MyPlugin.zip`。 > ⚠️ 务必区分这两个动作: > - **Build → Build Artifacts → MyPlugin:jar → Build**:只在 `out/artifacts/MyPlugin_jar/` 生成 `MyPlugin.jar` + 依赖 jar,**不会产生 zip**。 > - **Prepare Plugin Module 'MyPlugin' For Deployment**:才会打包出 `MyPlugin.zip`。二者共用同一个 Artifact 定义。 ### 方式二:命令行打包(等价,无需配置 Artifact) ```powershell $proj = 'D:\IdeaProjects\MyPlugin' $ideaHome = 'D:\Program Files\IntelliJ IDEA 2021.3.1' # 提供 com.intellij.* 平台类 $jdk = 'C:\Users\grs05\.jdks\ms-11.0.32.1' # JDK 11 $work = "$env:TEMP\myplugin_pkg" $classes = "$work\classes" $stage = "$work\stage\MyPlugin\lib" New-Item -ItemType Directory -Force -Path $classes, $stage | Out-Null # 1) 编译(-encoding UTF-8 防止中文乱码;用 @argfile 传参,避免 classpath 里的空格被 PowerShell 拆断、反斜杠被 javac 转义) $cp = @() $cp += (Get-ChildItem "$ideaHome\lib" -Filter *.jar | % { $_.FullName }) $cp += (Get-ChildItem "$ideaHome\plugins\java\lib" -Filter *.jar | % { $_.FullName }) $cp += (Get-ChildItem "$proj\libs" -Filter *.jar | % { $_.FullName }) $cp = ($cp | % { $_.Replace('\','/') }) -join ';' $src = Get-ChildItem -Recurse -Filter *.java "$proj\src" | % { $_.FullName } $argFile = "$work\javac.args" Set-Content -Path $argFile -Encoding Ascii -Value @('-encoding','UTF-8','-d',($classes.Replace('\','/')),'--release=11','-classpath',('"' + $cp + '"')) Add-Content -Path $argFile -Encoding Ascii -Value ($src | % { $_.Replace('\','/') }) & "$jdk\bin\javac.exe" "@$argFile" # 2) 资源并入(让 plugin.xml / icons 落到 jar 根) Copy-Item -Recurse -Force "$proj\resources\*" $classes # 3) 插件本体 jar & "$jdk\bin\jar.exe" --create --file "$stage\MyPlugin.jar" -C $classes . # 4) 依赖 jar Copy-Item -Force "$proj\libs\*.jar" $stage # 5) 压缩为 zip(必须用正斜杠 /) $zip = "$proj\MyPlugin.zip" Remove-Item $zip -Force -ErrorAction SilentlyContinue Add-Type -AssemblyName System.IO.Compression Add-Type -AssemblyName System.IO.Compression.FileSystem $root = (Resolve-Path "$work\stage").Path $fs = [System.IO.File]::Open($zip, [System.IO.FileMode]::CreateNew) $a = New-Object System.IO.Compression.ZipArchive($fs, [System.IO.Compression.ZipArchiveMode]::Create) Get-ChildItem -Recurse -File $root | % { $rel = $_.FullName.Substring($root.Length + 1).Replace('\','/') $es = $a.CreateEntry($rel, [System.IO.Compression.CompressionLevel]::Optimal).Open() $in = [System.IO.File]::OpenRead($_.FullName); $in.CopyTo($es); $in.Dispose(); $es.Dispose() } $a.Dispose(); $fs.Dispose() ``` > PowerShell 5.1 的 `Compress-Archive` 会把条目分隔符写成反斜杠 `\`,不符合 ZIP 规范,可能导致 IDE 无法识别其中的 jar;因此上例用 .NET `ZipArchive` 强制写入 `/`。 ### 产物结构 ```text MyPlugin.zip └── MyPlugin/ └── lib/ ├── MyPlugin.jar # classes + META-INF/plugin.xml + icons ├── fastjson-1.2.83.jar ├── okhttp-3.14.9.jar └── okio-1.17.2.jar ``` ### 安装 **Settings → Plugins → ⚙ → Install Plugin from Disk…** 选择 `MyPlugin.zip` → 重启 IDE。 --- ## 常见问题排查 ### 1. 运行报错 `Cannot find VM executable` **现象**:点击 Run 后无法启动沙箱 IDE,提示 `Cannot find VM executable`。 **根因**:运行配置为 `IntelliJ Platform Plugin` 类型,其 JVM 取自项目 SDK `IntelliJ IDEA IU-213.6461.79`(类型 `IDEA JDK`)。该 SDK 的**内部 JDK** 指向一个已被删除的目录(例如 `D:\dev\jdk-11.0.15.1`),导致 DevKit 解析不到 `java.exe`。 **解决**:**File → Project Structure → Platform Settings → SDKs** 中新增一个可用的 JDK 11(如 `C:\Users\grs05\.jdks\ms-11.0.32.1`),`Apply → OK`。DevKit 会自动把它识别为该 IDEA SDK 的内部 JDK,VM 路径随即恢复。 ### 2. 编译报错 `Exception无法转换为Throwable` **现象**:`Xml2UdlPreviewAction.java` 中 `catch (Exception e)` 报“不兼容的类型”。 **根因**:文件顶部误引入了 Qodana 的同名类,屏蔽了 `java.lang.Exception`: ```java import com.jetbrains.qodana.sarif.model.Exception; // ← 错误,应删除 ``` **解决**:删除该 import 即可。 ### 3. Build 菜单没有 “Prepare Plugin Module ... For Deployment” **根因**:DevKit 仅在模块类型为 **`PLUGIN_MODULE`** 时才显示该动作(以及 Project Structure 里的 **Plugin Deployment** 面板)。若 `.iml` 里是 `JAVA_MODULE`,则不显示。 **解决**:将 `MyPlugin.iml` 第 2 行改为: ```xml ``` 然后 `File → Reload All from Disk`。之后: - Build 菜单出现 **Prepare Plugin Module 'MyPlugin' For Deployment**; - Project Structure → Modules → MyPlugin 出现 **Plugin Deployment** 面板,其中 **Descriptor** = `resources/META-INF/plugin.xml`(这就是“插件描述符”的指定位置)。 ### 4. 运行时中文字符串乱码(如 `锛38涓口粍浠讹紝...`) **现象**:源码里中文正常,编辑器也显示正常,但打包安装后弹窗/面板里的中文变成 `锛`、`涓`、`缁` 之类的乱码。 **根因**:源码文件是 **UTF-8**,但编译器按 **系统默认编码(中文 Windows 为 GBK)** 读取,导致编译期就把字符串常量破坏了(UTF-8 字节被当作 GBK 解读)。IDEA 编辑器会做字符集自动探测所以显示正常,但编译器不探测,于是出现“编辑器正常、运行时乱码”。这通常是因为项目未显式设置编码,`.idea/encodings.xml` 缺失,IDEA 退回系统默认。 **解决**:新建 `.idea/encodings.xml`,把项目编码固定为 UTF-8: ```xml ``` 保存后 `File → Reload All from Disk`,再 **Build → Rebuild Project** 全量重编(增量编译可能不会重编未改动文件)。 > 若改用命令行打包(见上文脚本),确保 `javac` 带上 `-encoding UTF-8` 即可避免此问题。 --- ## 扩展开发建议 | 扩展方向 | 实现要点 | |----------|----------| | 支持中译英 | 修改 `createRequestParams()` 中 `from`/`to` 参数,或增加语言方向检测 | | 图片/音频翻译 | 使用 `FileUtil.loadMediaAsBase64()` + `AuthV4Util` + 对应有道 API | | WebSocket 实时翻译 | 取消注释 `WebSocketUtil.java` 并补充包声明 | | 翻译结果插入编辑器 | 通过 `WriteCommandAction` + `Document.insertString()` 实现 | | 新增 Action | 在 `plugin.xml` 的 `` 块注册,`group-id` 参考现有配置 | --- ## 许可证 个人学习项目,仅供参考。