资讯详情

从零开始开发一款IDEA Plugin插件:用TaoToken统一Key打通Gradle构建与AnAction调试

📅 2026/10/15 10:41:13 | 华诺云谱 👁 阅读
从零开始开发一款IDEA Plugin插件:用TaoToken统一Key打通Gradle构建与AnAction调试
1. 从零开发 IDEA PluginGradle 构建与 AnAction 调试全链路IDEA Plugin 插件开发这件事说难不难说简单也踩坑。它本质上是给 IntelliJ 平台写一个扩展模块通过plugin.xml声明扩展点用 Java 或 Kotlin 写逻辑最后打包成 zip 分发。适合谁适合已经会写 Java、想把自己重复的 IDE 操作自动化、或者想把内部工具集成进 IDE 的工程师。我这次要做的插件叫 Show in Explorer功能很朴素在编辑器里点一下就在系统文件管理器里定位当前文件。但麻雀虽小Gradle 依赖、AnAction 注册、沙箱调试、HTTP 请求 endpoint 配置这几条链路一个不少。真正让这个练手项目变得有实战价值的是插件里往往要发网络请求——比如调用大模型做代码补全、做注释生成、做 commit message 润色。这时候 Key 管理就成了麻烦事每个插件各存一份 Key换机器要重新配团队协作还要互相传。我的做法是把插件内的 HTTP endpoint 统一指向 TaoToken用一套 Key 打通多个模型调用插件本身只关心业务逻辑不关心 Key 从哪来。下面按完整链路走一遍每一步都给可复制的配置。先明确整体结构build.gradle.kts负责依赖和打包plugin.xml负责注册 AnAction 和扩展点Action 类负责响应点击Service 类负责具体业务HTTP 请求走统一的 endpoint 配置。这套结构你照着改就能跑起来。2. TaoToken 前置准备统一 Key 与 endpoint 配置在写插件代码之前先把 Key 和 endpoint 准备好否则后面调试 HTTP 请求时会卡在 401。TaoToken 在这里扮演的角色是统一的模型调用入口插件不需要为每个模型单独维护 Key只需要一个 Base URL 加一个 Key就能在插件里切换不同模型。对插件开发者来说这省掉了在设置页里做多 Key 管理的复杂度。第一步是拿到 Key。打开 https://taotoken.net/api-keys 登录后创建一个 API Key复制出来先存到本地临时文件里注意别提交到 Git。这个 Key 就是插件里 HTTP 请求要带的凭证。第二步是确认 Base URL。TaoToken 的 API 地址是 https://taotoken.net/api 注意这个地址不带任何查询参数直接作为请求前缀使用。插件里发请求时完整路径是https://taotoken.net/api/v1/chat/completions这种形式具体路径取决于你要调用的接口。第三步是选模型。TaoToken 支持多种模型插件里通过请求体里的model字段指定。你可以先在 https://taotoken.net/models 上确认可用模型列表把 Model ID 记下来比如claude-sonnet-4-5这类标识。插件配置里我会把它做成可选项方便切换。这里有个关键点插件里的 Key 不能硬编码。正确做法是放到 IDE 的设置项里或者读取环境变量。我在示例里用环境变量TAOTOKEN_API_KEY读取这样本地调试和打包分发都安全。如果你要做成团队内部插件可以在 Settings 里加一个配置页把 Key 存到PersistentStateComponent里。注意不要把 Key 写进plugin.xml或build.gradle.kts打包后的 jar 是可以被反编译的Key 会泄露。准备好这三样东西——Key、Base URL、Model ID——后面配置 HTTP 请求时直接填进去就行。如果你还没创建 Key先去 https://taotoken.net/api-keys 建一个整个过程不到一分钟。3. 可复制配置build.gradle.kts 与 plugin.xml 模板这一节是全文最核心的部分所有配置都可以直接复制。先看build.gradle.kts这是插件项目的构建文件决定了依赖版本、打包名称和 JVM 兼容性。plugins { id(java) id(org.jetbrains.intellij.platform) version 2.10.2 } group com.cxj.plugin version 1.0.0 repositories { mavenCentral() maven { url uri(https://maven.aliyun.com/repository/public) } intellijPlatform { defaultRepositories() } } dependencies { intellijPlatform { intellijIdea(2025.2.4) testFramework(org.jetbrains.intellij.platform.gradle.TestFrameworkType.Platform) } // HTTP 请求用 OkHttp比 HttpURLConnection 好用 implementation(com.squareup.okhttp3:okhttp:4.12.0) implementation(com.google.code.gson:gson:2.11.0) } intellijPlatform { pluginConfiguration { ideaVersion { sinceBuild 252.25557 } changeNotes h2b1.0.0/b/h2 ul li初始版本支持在资源管理器中定位当前文件/li li支持通过 TaoToken 统一 endpoint 调用模型/li /ul .trimIndent() } } tasks { withTypeJar { archiveBaseName.set(ShowInExplorer) archiveVersion.set(project.version.toString()) } withTypeJavaCompile { sourceCompatibility 21 targetCompatibility 21 } }这里有几个参数值得说明。intellijIdea(2025.2.4)指定了目标 IDE 版本sinceBuild 252.25557是最低兼容 build 号这两个要匹配否则插件在低版本 IDE 上装不上。okhttp和gson是我加的 HTTP 依赖如果你不做网络请求可以删掉。阿里云镜像仓库是为了国内构建加速首次构建大概 10 分钟之后增量构建很快。接下来是plugin.xml这是插件的注册文件AnAction 必须在这里声明才能被 IDE 识别。idea-plugin idcom.cxj.plugin.show-in-explorer/id nameShow in Explorer/name vendor emailxxxqq.comchenxinjin/vendor description![CDATA[ A small tool used to open the current file explorer location. After installation, there will be an icon on the right side of the menu bar. br 一个用于打开当前文件资源管理器位置的小工具安装后在菜单栏右侧会有一个图标。 ]]/description dependscom.intellij.modules.platform/depends extensions defaultExtensionNscom.intellij /extensions actions action idcom.cxj.plugin.showinexplorer.action.ShowInExplorerAction classcom.cxj.plugin.showinexplorer.action.ShowInExplorerAction text打开当前文件位置 description在资源管理器中定位当前文件 icon/META-INF/pluginIcon.svg add-to-group group-idMainToolbarRight anchorfirst/ keyboard-shortcut keymap$default first-keystrokectrl alt F1/ /action /actions /idea-pluginadd-to-group group-idMainToolbarRight把按钮放到主工具栏右侧keyboard-shortcut注册了 CtrlAltF1 快捷键。dependscom.intellij.modules.platform/depends是基础模块依赖必须保留。如果你要上传到插件市场description标签必须以英文开头这是官方审核要求。现在把 HTTP endpoint 配置加进来。我在 Action 类里读取环境变量构造请求时指向 TaoTokenprivate static final String BASE_URL https://taotoken.net/api; private static final String MODEL_ID claude-sonnet-4-5; private String getApiKey() { String key System.getenv(TAOTOKEN_API_KEY); if (key null || key.isEmpty()) { throw new IllegalStateException(请设置环境变量 TAOTOKEN_API_KEY); } return key; }这三件套——Base URL、Key、Model ID——就是插件调用模型的全部配置。Base URL 固定为https://taotoken.net/apiKey 从环境变量读Model ID 按需切换。如果你用 Cline MCP 或 Codex 的auth.json配置逻辑是一样的都是 Base URL 加 Key 加 Model ID 三件套。4. 验证请求AnAction 触发与日志输出配置写完了现在写 Action 类让它既能定位文件又能发一次 HTTP 请求验证 endpoint 通不通。先看完整的 Action 代码package com.cxj.plugin.showinexplorer.action; import com.cxj.plugin.showinexplorer.service.ShowInExplorerService; import com.cxj.plugin.showinexplorer.util.ActionDataUtil; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.ui.Messages; import com.intellij.openapi.vfs.VirtualFile; import okhttp3.*; import com.google.gson.JsonObject; import com.google.gson.JsonParser; import java.io.IOException; public class ShowInExplorerAction extends AnAction { private static final Logger LOG Logger.getInstance(ShowInExplorerAction.class); private static final String BASE_URL https://taotoken.net/api; private static final String MODEL_ID claude-sonnet-4-5; private static final OkHttpClient CLIENT new OkHttpClient(); Override public void actionPerformed(AnActionEvent event) { Project project event.getProject(); VirtualFile virtualFile ActionDataUtil.getCurrentVirtualFile(event); if (virtualFile null) { Messages.showMessageDialog(project, 请先打开一个本地文件, 无法打开资源管理器, Messages.getWarningIcon()); return; } try { getShowInExplorerService().reveal(virtualFile); LOG.info(Opened current file in explorer, path virtualFile.getPath()); // 触发一次模型请求验证 endpoint 配置 verifyEndpointAsync(virtualFile); } catch (IllegalArgumentException exception) { Messages.showMessageDialog(project, exception.getMessage(), 无法打开资源管理器, Messages.getWarningIcon()); LOG.warn(Failed to open current file in explorer, path virtualFile.getPath(), exception); } } private void verifyEndpointAsync(VirtualFile virtualFile) { ApplicationManager.getApplication().executeOnPooledThread(() - { try { String apiKey System.getenv(TAOTOKEN_API_KEY); if (apiKey null || apiKey.isEmpty()) { LOG.warn(TAOTOKEN_API_KEY 未设置跳过 endpoint 验证); return; } JsonObject body new JsonObject(); body.addProperty(model, MODEL_ID); body.addProperty(max_tokens, 32); JsonObject message new JsonObject(); message.addProperty(role, user); message.addProperty(content, 用一句话说明当前文件路径: virtualFile.getPath()); com.google.gson.JsonArray messages new com.google.gson.JsonArray(); messages.add(message); body.add(messages, messages); Request request new Request.Builder() .url(BASE_URL /v1/chat/completions) .addHeader(Authorization, Bearer apiKey) .addHeader(Content-Type, application/json) .post(RequestBody.create(body.toString(), MediaType.parse(application/json))) .build(); try (Response response CLIENT.newCall(request).execute()) { String respBody response.body() ! null ? response.body().string() : ; LOG.info(TaoToken response code response.code() , body respBody); if (response.isSuccessful()) { JsonObject json JsonParser.parseString(respBody).getAsJsonObject(); String content json.getAsJsonArray(choices) .get(0).getAsJsonObject() .getAsJsonObject(message) .get(content).getAsString(); LOG.info(Model reply: content); } } } catch (IOException e) { LOG.warn(TaoToken request failed, e); } }); } Override public void update(AnActionEvent event) { event.getPresentation().setEnabled(ActionDataUtil.getCurrentVirtualFile(event) ! null); } private ShowInExplorerService getShowInExplorerService() { return ApplicationManager.getApplication().getService(ShowInExplorerService.class); } }这段代码有几个设计点。第一HTTP 请求放在executeOnPooledThread里异步执行不阻塞 UI 线程否则点击按钮会卡住 IDE。第二请求体用 Gson 构造model字段填 Model IDmessages是标准对话格式。第三响应解析取choices[0].message.content这是 OpenAI 兼容格式TaoToken 的接口返回结构一致。Service 类和工具类保持原样即可Service(Service.Level.APP) public final class ShowInExplorerService { public void reveal(VirtualFile virtualFile) { File file new File(virtualFile.getPath()); if (!file.exists()) { throw new IllegalArgumentException(当前文件不存在或不是本地文件: virtualFile.getPath()); } RevealFileAction.openFile(file); } }工具类ActionDataUtil负责从事件里取当前文件逻辑是先从CommonDataKeys.VIRTUAL_FILE取取不到再从编辑器文档反查。这个类不用改。现在启动沙箱验证。点击 Gradle 面板里的Run IDE with Plugin会弹出一个新的 IDEA 窗口。在新窗口里随便打开一个项目按 CtrlAltF1你应该看到两件事文件管理器弹出来定位到当前文件同时原 IDEA 的日志窗口输出TaoToken response code200和模型返回的内容。如果日志里出现code401说明 Key 没读到检查环境变量是否在启动沙箱前设置好。提示沙箱 IDE 的环境变量继承自启动它的主 IDE所以要在启动前把TAOTOKEN_API_KEY配到系统环境变量里或者用Run Configuration手动加。验证通过后打包命令是Gradle - 项目名 - Tasks - intellijPlatform - buildPlugin产物在build/distributions/目录下。离线安装走Settings - Plugins - Install Plugin from Disk选中 zip 包即可。5. 本篇常见错排查401、local proxy failed 与 OAuth 报错插件开发过程中最容易卡住的不是业务逻辑而是环境配置。我把几个高频报错和排查路径列出来你对照日志逐条排。401 Unauthorized。日志里出现TaoToken response code401说明 Key 没传对。排查顺序先确认环境变量TAOTOKEN_API_KEY在当前 shell 里echo得出来再确认沙箱 IDE 是否继承了这个变量最稳的办法是在Run Configuration的 Environment variables 里显式加一条最后确认请求头格式是Authorization: Bearer keyBearer 后面有一个空格少空格也会 401。如果你用的是 Codex 的auth.json或 Cline MCP 配置检查字段名是否写成了apiKey而不是api_key不同工具字段名不一样。local proxy failed。这个报错通常出现在插件发请求时日志里会带Failed to connect to /127.0.0.1:xxxx。原因是 IDE 或系统设置了本地代理而插件用的 OkHttp 默认会读系统代理。解决办法是在 OkHttpClient 构造时显式禁用代理private static final OkHttpClient CLIENT new OkHttpClient.Builder() .proxy(Proxy.NO_PROXY) .build();如果你确实需要走代理那就把代理地址配对但注意不要在代码里硬编码代理凭证。reading choices 空指针。日志里出现NullPointerException在getAsJsonArray(choices)这一行说明响应体不是预期的 JSON 结构。常见原因是请求路径写错了比如写成了https://taotoken.net/api/chat/completions少了/v1或者请求体里model字段填了一个不存在的 Model ID服务端返回了错误对象而不是正常响应。排查办法是把respBody完整打出来看错误响应里通常有error.message字段说明原因。OAuth 相关报错。如果你在插件里集成了需要 OAuth 的第三方服务日志里可能出现OAuth token expired或invalid_grant。这类问题跟 TaoToken 无关是第三方服务的 token 刷新逻辑问题。排查思路是检查 refresh token 是否过期、回调地址是否跟注册时一致。插件里做 OAuth 建议用OAuthService而不是手写流程减少出错面。插件装了但按钮不显示。检查plugin.xml里add-to-group的group-id是否正确MainToolbarRight是主工具栏右侧如果你写成了MainToolbar可能被其他按钮挤掉。另外确认sinceBuild跟你的 IDE build 号匹配不匹配的话插件会被 IDE 标记为不兼容而不加载。Gradle 构建卡在 resolve dependencies。首次构建要下载 IntelliJ Platform SDK体积很大。如果卡住检查repositories里是否加了intellijPlatform { defaultRepositories() }这个块负责配置平台仓库。国内网络环境下加阿里云镜像能加速 Maven 依赖但平台 SDK 还是走 JetBrains 仓库耐心等或者配镜像。排查完这些插件基本就能稳定运行了。我踩过的坑主要集中在 401 和代理这两块前者是环境变量没继承后者是 OkHttp 默认读系统代理两个都改一行代码就能解决。6. 语义一致 CTA把 Key 管理交给 TaoToken插件跑通之后你会发现 Key 管理这件事在插件场景里特别烦每个用户要自己配 Key团队分发时要写文档教怎么配换模型要改代码重新打包。TaoToken 的价值就在这里——它把多模型调用收敛成一个 endpoint 加一个 Key插件只需要读一个环境变量模型切换在服务端完成插件代码不用动。如果你只是想让插件调通模型现在就可以去 https://taotoken.net/api-keys 创建一个 Key配到环境变量里按上面的代码跑一遍验证请求。接入文档在 https://taotoken.net/doc 里面有完整的接口说明和参数列表遇到请求格式问题可以对照查。如果你打算长期做插件开发或者插件里要集成 Agent 能力做代码生成、重构建议那 Coding Plan 更合适地址是 https://taotoken.net/coding-plan 它按编码场景做了额度优化比按量计费更划算。想先试试模型对话效果可以直接在 https://taotoken.net/chat 里发几条消息确认返回格式和延迟符合预期再写进插件。最后给一个实用建议插件里的 endpoint 配置做成可覆盖的默认指向 TaoToken但允许用户在 Settings 里改成自己的地址。这样既保证了开箱即用又保留了灵活性。配置读取用PersistentStateComponent存到 IDE 的配置目录里不要写死在代码里。这套结构你照着搭一遍后面做任何 IDE 插件都能复用。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑