# 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` 参考现有配置 |
---
## 许可证
个人学习项目,仅供参考。