☰
Cursor + GitOps 实战:自动部署 Chrome 插件许可证服务到 Cloudflare Workers | TaoToken 统一 Key 接入
2026/10/7 14:33:07 网站建设 项目流程

1. 为什么 Chrome 插件许可证服务必须独立部署

做 Chrome 插件的人迟早会碰到同一个问题:Pro 版本怎么校验。我见过太多插件把支付平台的 API Key 直接写进background.js或者content.js,然后打包上传到商店。这种做法在技术上能跑通,但安全模型是崩的。扩展代码最终会落到用户浏览器里,压缩和混淆只是提高阅读成本,不是加密。任何人打开 DevTools 的 Sources 面板,或者把 crx 解包,都能看到里面的接口地址、请求参数,甚至明文密钥。

所以正确的结构是三层:Chrome 插件只负责收集用户输入的 License Key,然后请求你自己的服务端;服务端持有真正的支付平台密钥,去和许可证提供商校验;校验结果再返回给插件。插件永远不接触支付平台的 Key,它只知道一个 Worker 地址。

这个服务端用 Cloudflare Workers 非常合适。它免费额度够个人产品用,冷启动几乎无感,全球边缘节点,而且天然支持环境变量注入密钥。你不需要维护一台服务器,也不需要担心证书和扩容。

但光有 Worker 还不够。许可证服务涉及 API Key、环境变量、部署版本、回滚。如果每次改代码都手动wrangler deploy,时间一长必然出现「本地代码和线上版本不一致」的问题。这时候就需要 GitOps:Git 仓库是部署状态的唯一来源,代码合并到主分支就自动部署,每次线上变更都能回溯到某个 commit。

这篇内容要解决的就是这条链路:用 Cursor 辅助写 Worker 代码和配置文件,用 GitHub Actions 做自动部署,用 TaoToken 统一管理模型调用凭据,最后用 curl 验证许可证接口真的返回了正确结果。适合正在做 Chrome 插件商业化、或者想把小型服务纳入自动化部署的独立开发者。

2. TaoToken 统一 Key 接入:把模型调用凭据从代码里拿出来

在讲 Worker 部署之前,先解决一个容易被忽略的问题:许可证服务本身可能也要调用大模型。比如你想在激活时做风控判断、或者给用户生成一段个性化的欢迎文案、或者用模型解析支付平台的异常返回。这些调用都需要 API Key。

如果每个服务各自维护一套 Key,很快就会乱:有的写在.env,有的塞进 GitHub Secrets,有的直接硬编码。更麻烦的是,当你同时用多个模型供应商时,Key 的格式、Base URL、模型 ID 都不一样,切换成本很高。

TaoToken 在这里的作用是提供一个统一的 API 通道。你只需要在 TaoToken 控制台创建一个 Key,然后所有模型调用都走同一个 Base URL 和同一个 Key,模型 ID 在请求体里指定。这样 Worker 的环境变量里只需要放一个TAOTOKEN_API_KEY,而不是五六个不同平台的密钥。

具体操作路径是这样的:先到官网 https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 注册账号,然后进入控制台 https://taotoken.net/console?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 创建 API Key。创建完成后,在 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 可以看到完整的 Key 字符串,复制保存好,它只会完整显示一次。

如果你只是想先验证模型能不能通,可以直接用模型对话页面 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 发一条消息测试。这个页面不需要写代码,适合确认 Key 有效、模型可用。

对于长期做编码和 Agent 的场景,Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 会更划算,它针对高频调用做了额度优化。接入文档在 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= ,里面有各语言的调用示例。

API 的基础地址是https://taotoken.net/api,注意这个地址不带 UTM 参数,是给代码里用的。在 Worker 里调用时,请求结构大致是这样:

const resp = await fetch("https://taotoken.net/api/v1/chat/completions", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${env.TAOTOKEN_API_KEY}` }, body: JSON.stringify({ model: "claude-sonnet-4-20250514", messages: [{ role: "user", content: "ping" }] }) });

这里的关键点是:TAOTOKEN_API_KEY只存在于 Worker 的环境变量里,不出现在代码仓库,也不出现在 Chrome 插件里。插件端永远只请求你自己的 Worker 地址。

如果你用的是 Claude Code 这类工具,TaoToken 也提供了对应的接入方式,参考 https://taotoken.net/claude-code-anthropic?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 。核心还是三件套:Base URL 填https://taotoken.net/api,Key 填你创建的 Key,Model ID 按文档里的可用列表填。

3. 可复制配置:wrangler.toml、GitHub Actions 与 Worker 代码

这一节给出可以直接复制粘贴的配置。整个项目结构建议这样组织:

scraper-ai-license-server/ ├── src/ │ └── index.js ├── wrangler.toml ├── package.json └── .github/ └── workflows/ └── deploy.yml

先看wrangler.toml。这个文件定义 Worker 的名称、入口、兼容日期,以及非敏感的环境变量。敏感密钥不写在这里,通过 Cloudflare 后台或wrangler secret设置。

name = "scraper-ai-license-server" main = "src/index.js" compatibility_date = "2026-06-01" [vars] LICENSE_PROVIDER = "creem" TAOTOKEN_BASE_URL = "https://taotoken.net/api" # 敏感变量不写在这里,用以下命令设置: # npx wrangler secret put CREEM_API_KEY # npx wrangler secret put TAOTOKEN_API_KEY

注意compatibility_date要填一个已经发布的日期,不要填未来日期,否则 wrangler 会报错。[vars]里只放非敏感配置,比如供应商名称和 Base URL。

接下来是 Worker 主代码src/index.js。它处理/activate和/verify两个接口,统一 CORS,统一 JSON 响应,密钥全部从env读取。

function corsHeaders() { return { "Access-Control-Allow-Origin": "*", "Access-Control-Allow-Methods": "POST, OPTIONS", "Access-Control-Allow-Headers": "Content-Type" }; } function jsonResponse(data, status = 200) { return new Response(JSON.stringify(data), { status, headers: { "Content-Type": "application/json", ...corsHeaders() } }); } async function activateLicense(request, env) { const { licenseKey, instanceId } = await request.json(); if (!licenseKey || !instanceId) { return jsonResponse({ valid: false, message: "missing licenseKey or instanceId" }, 400); } const providerResp = await fetch("https://api.creem.io/v1/licenses/activate", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": env.CREEM_API_KEY }, body: JSON.stringify({ license_key: licenseKey, instance_id: instanceId }) }); const providerData = await providerResp.json(); if (!providerResp.ok) { return jsonResponse({ valid: false, message: providerData.message || "provider error" }, 502); } return jsonResponse({ valid: true, activatedAt: Date.now(), plan: providerData.plan || "pro" }); } async function verifyLicense(request, env) { const { licenseKey, instanceId } = await request.json(); const providerResp = await fetch("https://api.creem.io/v1/licenses/validate", { method: "POST", headers: { "Content-Type": "application/json", "x-api-key": env.CREEM_API_KEY }, body: JSON.stringify({ license_key: licenseKey, instance_id: instanceId }) }); const providerData = await providerResp.json(); return jsonResponse({ valid: providerResp.ok && providerData.valid === true }); } export default { async fetch(request, env) { const url = new URL(request.url); if (request.method === "OPTIONS") { return new Response(null, { headers: corsHeaders() }); } if (url.pathname === "/activate" && request.method === "POST") { return activateLicense(request, env); } if (url.pathname === "/verify" && request.method === "POST") { return verifyLicense(request, env); } return jsonResponse({ error: "Not found" }, 404); } };

这段代码里,env.CREEM_API_KEY和env.TAOTOKEN_API_KEY都来自 Cloudflare 的加密环境变量,不会出现在 Git 仓库里。如果你要在激活流程里加模型调用,就在activateLicense里用env.TAOTOKEN_API_KEY去请求env.TAOTOKEN_BASE_URL。

然后是 GitHub Actions 工作流.github/workflows/deploy.yml。它监听 main 分支的 push,安装依赖后执行wrangler deploy。

name: Deploy License Worker on: push: branches: - main jobs: deploy: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup Node uses: actions/setup-node@v4 with: node-version: "20" - name: Install dependencies run: npm install - name: Deploy to Cloudflare Workers run: npx wrangler deploy env: CLOUDFLARE_API_TOKEN: ${{ secrets.CLOUDFLARE_API_TOKEN }} CLOUDFLARE_ACCOUNT_ID: ${{ secrets.CLOUDFLARE_ACCOUNT_ID }}

CLOUDFLARE_API_TOKEN和CLOUDFLARE_ACCOUNT_ID配置在 GitHub 仓库的 Settings → Secrets and variables → Actions 里。API Token 在 Cloudflare 后台的 My Profile → API Tokens 创建,权限选「Edit Cloudflare Workers」即可。

package.json里只需要声明 wrangler 依赖:

{ "name": "scraper-ai-license-server", "version": "1.0.0", "private": true, "devDependencies": { "wrangler": "^3.80.0" } }

这套配置的核心逻辑是:代码里没有任何明文密钥,所有敏感信息通过 Cloudflare Secrets 和 GitHub Secrets 注入。Git 仓库可以公开,也不会泄露。

4. 验证请求:push 触发部署与 curl 测试许可证接口

配置写完之后,验证分两步:先确认 push 能触发自动部署,再确认接口真的返回正确结果。

第一步,把代码提交到 main 分支:

git add . git commit -m "feat: add license worker with gitops deploy" git push origin main

push 之后打开 GitHub 仓库的 Actions 标签页,应该能看到一个名为「Deploy License Worker」的工作流正在运行。点进去可以看到每一步的日志。如果wrangler deploy成功,最后会输出类似这样的内容:

Uploaded scraper-ai-license-server (1.23 sec) Deployed scraper-ai-license-server triggers (0.45 sec) https://scraper-ai-license-server.<your-subdomain>.workers.dev

这个 URL 就是你的许可证服务地址。把它填到 Chrome 插件的LICENSE_API_BASE里。

第二步,用 curl 验证/activate接口。先测一个明显无效的 Key,确认错误处理正常:

curl -X POST https://scraper-ai-license-server.<your-subdomain>.workers.dev/activate \ -H "Content-Type: application/json" \ -d '{"licenseKey":"invalid-key-123","instanceId":"test-instance-001"}'

预期返回:

{"valid":false,"message":"provider error"}

状态码应该是 502 或 400,说明 Worker 正确地把错误透传出来了,而不是崩溃或者返回 200。

再用一个真实的测试 License Key 验证成功路径:

curl -X POST https://scraper-ai-license-server.<your-subdomain>.workers.dev/activate \ -H "Content-Type: application/json" \ -d '{"licenseKey":"YOUR_REAL_TEST_KEY","instanceId":"test-instance-001"}'

预期返回:

{"valid":true,"activatedAt":1735689600000,"plan":"pro"}

如果看到valid: true,说明整条链路是通的:curl → Cloudflare Worker → 支付平台许可证接口 → 返回结果。这时候再去 Chrome 插件里调用同一个接口,应该也能拿到相同结果。

再验证一下 CORS 预检请求:

curl -X OPTIONS https://scraper-ai-license-server.<your-subdomain>.workers.dev/activate \ -H "Origin: chrome-extension://abcdefg" \ -H "Access-Control-Request-Method: POST" \ -i

响应头里应该包含Access-Control-Allow-Origin: *和Access-Control-Allow-Methods: POST, OPTIONS。如果没有这两个头,Chrome 插件里的 fetch 会被浏览器拦截。

最后验证/verify接口:

curl -X POST https://scraper-ai-license-server.<your-subdomain>.workers.dev/verify \ -H "Content-Type: application/json" \ -d '{"licenseKey":"YOUR_REAL_TEST_KEY","instanceId":"test-instance-001"}'

返回{"valid":true}就说明校验接口也正常。到这里,一次完整的 push 触发部署 + curl 验证就完成了。

5. 本篇常见错排查:401、CORS、wrangler 报错与 OAuth 问题

这一节列出实际部署中最容易撞到的几个错误,以及对应的排查方向。

错误一:401 Unauthorized来自支付平台

现象是 curl 返回{"valid":false,"message":"provider error"},但你去 Cloudflare 后台看日志,发现请求确实发出去了,只是支付平台返回了 401。这通常说明CREEM_API_KEY没有正确设置,或者设置后没有重新部署。

排查步骤:先在 Cloudflare 后台进入 Workers & Pages → 你的 Worker → Settings → Variables and Secrets,确认CREEM_API_KEY存在且类型是 Secret。如果刚添加,需要重新触发一次部署才能生效。可以用npx wrangler secret list查看当前已设置的 secret 名称。

注意,wrangler secret put设置的变量不会出现在wrangler.toml里,这是正常的。如果你在本地用wrangler dev调试,需要在项目根目录建一个.dev.vars文件,里面写CREEM_API_KEY=xxx,但这个文件必须加入.gitignore。

错误二:local proxy failed或reading 'choices'报错

如果你在 Worker 里调用了 TaoToken 的模型接口,可能会遇到Cannot read properties of undefined (reading 'choices')。这通常是因为响应结构和你预期的不一样。先打印原始响应:

const raw = await resp.text(); console.log("raw response:", raw);

常见原因是 Base URL 写错了。TaoToken 的 API 地址是https://taotoken.net/api,完整的 chat completions 路径是https://taotoken.net/api/v1/chat/completions。如果你只写了https://taotoken.net,就会 404,返回的就不是 JSON 结构,解析choices自然报错。

另一个原因是Authorization头格式不对。必须是Bearer <你的Key>,中间有一个空格。少了空格或者用了Token前缀都会 401。

错误三:CORS 报错No 'Access-Control-Allow-Origin' header

Chrome 插件里 fetch 报这个错,说明 Worker 没有正确处理 OPTIONS 预检。检查你的 Worker 代码里是否有这一段:

if (request.method === "OPTIONS") { return new Response(null, { headers: corsHeaders() }); }

这段必须放在路由判断的最前面。如果你把它放在/activate判断之后,OPTIONS 请求会先命中 404 分支,就不会返回 CORS 头。

另外,Access-Control-Allow-Headers要包含Content-Type,因为插件发的是 JSON 请求。如果你还传了自定义头,比如X-Instance-Id,也要加进去。

错误四:wrangler deploy报OAuth error或Authentication error

在 GitHub Actions 里跑wrangler deploy时,如果报 OAuth 相关错误,通常是因为CLOUDFLARE_API_TOKEN没有设置,或者 Token 权限不够。wrangler 在 CI 环境里不会走 OAuth 浏览器登录,它只认CLOUDFLARE_API_TOKEN环境变量。

排查:确认 GitHub Secrets 里有CLOUDFLARE_API_TOKEN和CLOUDFLARE_ACCOUNT_ID。API Token 的权限要包含Workers Scripts:Edit。如果 Token 创建时只给了读权限,部署会失败。

还有一个容易忽略的点:CLOUDFLARE_ACCOUNT_ID不是你的登录邮箱,而是 Cloudflare 后台右侧栏显示的 Account ID,一串十六进制字符。填错了会报account not found。

错误五:部署成功但接口 404

Actions 显示部署成功,但 curl 访问返回 404。先确认你访问的 URL 和部署日志里输出的 URL 完全一致。Workers 的默认域名是<worker-name>.<subdomain>.workers.dev,subdomain是你在 Cloudflare 注册时设置的,不是账号 ID。

如果 URL 没错,检查wrangler.toml里的main字段是否指向了正确的入口文件。如果main = "src/index.js"但实际文件在src/worker/index.js,wrangler 会部署一个空 Worker,所有请求都 404。

6. 语义一致 CTA:把 Key 管理和部署流程固定下来

整套流程跑通之后,你会发现真正花时间的不是写 Worker 代码,而是把密钥管理和部署流程固定成可重复的动作。Cursor 在这里的价值是帮你快速生成wrangler.toml、GitHub Actions YAML 和 Worker 路由代码的初稿,但密钥放哪里、怎么注入、怎么验证,这些决策还是得自己做。

我的建议是把三件事固定下来:第一,所有模型调用统一走 TaoToken 的 Base URL 和 Key,Worker 环境变量里只保留一个TAOTOKEN_API_KEY;第二,所有敏感信息通过 Cloudflare Secrets 和 GitHub Secrets 注入,代码仓库里不出现任何明文密钥;第三,每次部署后用 curl 跑一遍/activate和/verify,确认返回结构没变。

如果你还没创建 TaoToken 的 Key,可以从 API Keys 页面 https://taotoken.net/api-keys?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 开始。想先确认模型调用能不能通,用模型对话 https://taotoken.net/chat?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 发一条消息最快。接入细节和参数说明在文档 https://taotoken.net/doc?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 里。长期做编码和 Agent 的话,Coding Plan https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_medium=csdn&utm_campaign=rewrite&utm_content= 的额度更适合高频场景。

最后一步,把 Chrome 插件里的LICENSE_API_BASE改成你的 Worker 地址,然后在插件里调用/activate,确认chrome.storage.local里写入了licenseStatus: "pro"。到这一步,从 Cursor 写代码到 GitOps 自动部署再到插件端激活,整条链路就闭环了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询