夜雨聆风学习资料网

ARTICLE · 1308777

你的插件能收费了,可每次发版还得在群里发文件?

你的插件能收费了,可每次发版还得在群里发文件?

千猫扩展授权系统 v1.5.5 —— Go 单文件后端 + Vue3 管理后台,后台点一下,客户那边就是新版。

上个月有个做电商工具的读者跟我聊,说他插件卖出去两百多份,收入还行。但他最烦的不是推广,是发版。

改完一个 bug,流程是这样的:打包 → 传网盘 → 群里发链接 → 有人说"哪个是新的" → 有人装错目录 → 有人装了但没重载 → 第二天还有人报同一个已经修好的 bug。

他说:修 bug 十分钟,发版两个小时。

这个问题我们上一篇文章《你做的 Chrome 插件,为什么卖不出去?》里提过一句,当时的答案是"定时检测 + 引导下载"。这一年真在客户那边跑下来,我们发现——那还不够。

引导下载解决的是"看得见新版",没解决"装得上新版"。而对一个几十上百客户的产品来说,装不上就等于没发。

所以这一版我们干脆把整套后台用Go + Vue 重写了,并且把"装得上"这件事做绝。


一、先说结论:这套系统现在由三层组成

以前
现在(v1.5.5)
后端
PHP + MySQL + 前端页面,三套各自配
一个 ~20MB 的 Go 单文件(Gin 框架),扔上去就能跑,自带 14 张表自动建
管理后台
混在 PHP 里的页面
独立的 Vue3 + Element Plus 工程,构建后丢进 admin/ 即可,前后端分离
插件端
同样
Chrome MV3 授权套件(在线授权 + 在线更新),Service Worker 里发请求
怎么发版
后台传包 → 客户看到提示 → 自己下载 → 自己装 → 自己重启
后台传包 → 客户点一个按钮 → 结束
怎么接插件
去代码仓库里翻 examples 目录
后台页面右上角点一下,套件 zip 直接下

后端和前端彻底分开:Go 二进制只管 /api/ 接口和它"自己"的升级;Vue 后台是纯静态产物,由 Nginx 直接托管。改前端不用动后端,改后端也不用碰前端。


二、Go + Vue 这套架构到底怎么跑

一张图说清(文字版):

  • 一个 Go 二进制,身兼两职。它既是你运营的「母站」,也能被客户装成「子站」——区别只在 config.yaml 里有没有一段 license。母站自用是"通用版",卖给客户用"客户版"(见第四节),靠编译期参数区分,不是两套代码。
  • Vue 后台是独立工程(ext-license-web,Vite 构建,Vue3 + Element Plus + Pinia),产物解压到网站根的 admin/。Nginx 把 /api/ 反代给 Go 二进制,把 /admin/ 当静态文件给浏览器。前后端用 HMAC + 时间戳 + nonce 对接。
  • Chrome 扩展端是另一套:授权逻辑跑在 Service Worker 里(避开业务站点 CSP),用 chrome.alarms 定时(SW 闲置会被回收),不碰 eval。更新检测走「应用管理 → 版本」,和后台程序的「系统升级」是两条独立的线——这是收到最多误会的地方。

为什么选 Go 单文件?部署时你不用装 Go 工具链、不用装 PHP,只要有 MySQL。二进制 + 一份 config.yaml + 一个 Vue 静态目录,齐了。


三、"一键无感升级"这次补的硬骨头

客户侧操作只有一步:

打开后台 →「系统管理 → 系统升级」→ 看到新版本和更新说明 → 点红色按钮「一键无感升级」。

进度条自己走:下载、校验、替换、重启。走完页面自动刷新,显示的就是新版本。

背后其实是四件事(写给懂的人看,不懂的跳到下一节):

① 下载 + 校验。版本包在传输过程中做 sha256 比对,包不完整或者被改过,直接拒绝,一个字节都不会往磁盘上落。

② 先备份再替换。替换前把当前二进制和前端目录各备一份带时间戳的。中途任何一步失败,回滚到原样——最坏结果是他还是老版本,不是变成一堆打不开的文件。

③ 重启不是「杀进程再拉起来」。用的是 syscall.Exec,把当前进程原地替换成新程序,PID 不变。这意味着不管你用 systemd、宝塔的进程守护,还是 nohup 手跑的,都能无缝换过去——不需要你为不同的守护方式写不同的重启命令。

④ 老版本自动清。每次发布成功后,其它版本的目录和多余备份会被自动删掉,只留当前版本加最近两份备份。不然跑半年,磁盘就被历史包吃满了。

这次(1.5.2 起)额外补了三块健壮性

  • 启动即清扫历史残留。早期有个版本升级失败时,临时名会无限叠成 xxx.new.new.new…,每个 ~20MB,跑半年磁盘被吃满。现在每次启动自动清掉这些残留,且绝不删正在运行的那个文件(Linux 允许 unlink 运行中可执行文件,删了进程一退出守护就拉不起来)。
  • 规范二进制名 + 三级替换。先按规范名(剥掉 .new/.old 后缀)定位目标;替换分三级:直接 rename → 删了再 rename → 挪成 .old 再 rename。最后一级专门应对禁止原地覆盖的挂载(某些 overlay / 只读文件系统)。
  • 前端自愈。前端放错目录,升级时自动搬到配置指定的 asset_dir。以前"升级成功但页面没变"——二进制换对了、前端换错地方——现在不再靠猜,升级页会直接甩一条红色告警,告诉你去哪一行改配置。

历史坑(仍值得记)

有个版本升级卡在 95% 不动,等一分钟报超时;然后再点一次,又说升级完成了。查下来是两件事叠在一起:后端其实早就诊断出"文件已经换好了,但进程还没重启"并把它返回前端——但前端从来没读过这个字段;同时 os.Executable() 在二进制被 rename 覆盖之后,返回的路径会变成 原路径 (deleted),拿这个路径去 exec 必然 ENOENT。

修法:新增一个独立的"只重启、不碰文件"的接口;前端超时后读那个"待重启"状态自动重试一次;exec 之前对路径做一次 (deleted) 后缀裁剪。写出来是因为——如果你也在做自更新的东西,这个坑你大概率会踩。


四、授权更硬了:HMAC + Ed25519 双签名(1.5.5 最大变化)

这是和上一版比最实质的变化,单独说。

两层签名,各管一件事:

  • 第一层 HMAC-SHA256(时间戳窗口默认 300 秒 + nonce 去重默认 15 分钟):挡住明文改参数、随手伪造请求、重放攻击。但密钥 app_secret 客户端也有,所以它只防"第三方",不防"你自己"。
  • 第二层 Ed25519:母站的私钥只存在母站(首次启动自动生成,落 storage/license_sign.key),每个授权响应再签一个 sig2;客户版二进制把公钥编译进去了。客户拿到程序,也造不出能骗过自己子站的假母站响应——因为私钥不在他手上。

这直接堵死了上一版我们坦白过的那个口子。当时写"母站私钥签名、客户端只持公钥验签"说还没做到,现在做到了。

运维必看的约束:客户版二进制是和母站公钥"绑定"发行的。母站的 license_sign.key必须备份;丢了 = 所有已发出的客户版子站验签失败、集体锁死。要换母站或换密钥,得重新出客户版二进制。

通用版 vs 客户版:

通用版
客户版
用途
你自用 / 当母站
卖给客户,装成子站
授权段缺失
不校验,母站行为不变
编译期注入 ForceLicense=1,缺失即锁定(fail-closed)
宽限/离线/心跳
按你设的来,不钳制
被夹到 7 / 7 / 24 天,客户改配置也绕不过

等于在"源码万一交出去"的前提下,把"改配置白嫖"这条路彻底焊死。


五、授权能力清单(这些老本行没动)

  • 激活码激活:用户填码,从"未授权"变"已授权";
  • 心跳校验:插件定期跟后台报平安,后台随时知道这张码还在不在用、用在几台设备;
  • 设备指纹绑定:一码限几台,换电脑要重新激活,防一码全网分享;
  • 离线宽限:断网也能先用着(按你设的天数),联网自动补校验,不会一断网就罢工;
  • 未授权拦截:没激活时核心功能自动锁住,弹授权卡片引导激活。你在功能入口加一行门禁判断,剩下的套件帮你拦;
  • 域名绑定:example.com 只认这一个域名;*.example.com 认本体加任意层子域;留空则客户第一次激活时自动绑当时访问的域名。换域名不用重新发码——你在母站改一下绑定域名,客户下次心跳(默认 6 小时)自动恢复;想立刻生效,让他在「软件授权」页点「立即校验」。

其中授权状态文件是 AES-256-GCM 加密 + 认证(改一个字节就判校验失败并锁定),设备指纹同时作为加密的附加认证数据,改了就解不开;二进制经 garble 混淆(标识符重命名、字符串字面量加密、去路径信息);校验点同时落在中间件和对外签发接口两处,摘掉中间件不生效。


六、坦白四件事

① 完全合规 MV3。所有联网请求在 Service Worker 里发,不走页面上下文,避开业务站点的 CSP;定时用 chrome.alarms 而不是 setInterval(SW 闲置会被回收);不上 eval/new Function。

② 双签名挡的是"伪造母站 / 伪造响应 / 重放",挡不住"把源码整段删了重编译"。把整个源码交给客户,上面这些都拦不住——这是源码交付的天然上限。但我们把"改配置白嫖"焊死了,这是实质进步,不假装没代价也没假装一步到位。

③config.yaml不加密,也不是靠加密防白嫖。配置文件运行时必须能解密,钥匙只能在程序里,加密等于没加密。真正起作用的是"客户版改了就锁死 + 私钥在母站"。别把"看不到"当成"防得住"。

④ 混淆的代价是线上 panic 堆栈变乱码。所以发布包里的二进制是混淆版;排错请用 --plain 版本复现行号,修好了再回混淆版验证。我们有这条流程,不假装没代价。


七、装一次,十分钟(Go + Vue 版部署)

不需要装 Go、不需要装 PHP,只要有 MySQL。

步骤
做什么
1
宝塔建个库,字符集选 utf8mb4
2
上传 Go 二进制,把 config.yaml.example 复制成 config.yaml,改 5 项(域名、库名、库账号、库密码、一个随机 JWT 密钥)
3
启动——14 张表会在首次启动时自动建好,不用导 SQL
4
把 Vue 后台 dist 解压到网站根目录的 admin/ 下
5
Nginx 加两段反代,重载

Nginx 那一步有两个坑,提前说(真实踩过):

# 必须加 ^~,否则宝塔默认的 location ~ \.php$ 正则会把 /api/ 抢走,# 请求被丢给 PHP-FPM,而本程序根本不需要 PHPlocation ^~ /api/ {    proxy_pass http://127.0.0.1:8080;    proxy_set_header Host $host;    proxy_set_header X-Real-IP $remote_addr;    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;    proxy_set_header X-Forwarded-Proto $scheme;}# 必须用 root 不用 alias:try_files 配 alias 会让回落路径反复重入本段,# 内部重定向超限,Nginx 直接吐 500 而不是 404location ^~ /admin/ {    root /www/wwwroot/你的站点目录;    index index.html;    try_files $uri $uri/ /admin/index.html;}

这两个坑看懂两行注释,能省你半天。安全加固是内置的,不用额外配:全局安全响应头 + CSP、验证码和登录都有限流、连续 5 次密码错锁 15 分钟、账号存在与否对外统一提示、改密后旧 token 立即失效、密码至少 10 位且带大小写和数字并过一遍常见弱口令黑名单。

登录进去第一件事:把默认的 admin / admin888 改掉。


八、接插件:8 步,全程复制粘贴

后台左侧菜单有个「接入向导」,面向的是完全不会写代码的人。

它的第一屏不是步骤,是一句提醒——先分清三件事:

  • 接入向导 = 给你的 Chrome 插件接上授权
  • 软件授权 = 给这个后台程序自己上授权(把后台卖给客户时才用)
  • 系统升级 = 升级后台程序自己,不是发插件新版的地方(插件新版走「应用管理 → 版本」)

第三条是我们收到最多的误会:不少人把插件的新版 zip 传到了「系统升级」页。

然后 8 步,每一步写清楚"点哪里、填什么、看到什么算成功":

建应用 → 拿密钥 → 下载授权套件 → 放进 src/license/ → 改 3 行配置 → 写 3 行胶水 → 打包 → 验证

第 3 步的套件,现在点页面右上角的「下载授权套件 (.zip)」就下来了,里面是:

README.mdsrc/license/license.jssrc/license/license-sw.jssrc/license/license-panel.jssrc/license/license-panel.css

注意它故意把src这一层也打在包里。如果把 license.js 直接放在 zip 根目录,很多人会顺手拖到扩展根目录,路径就全错了。

还有一点很重要:这份套件是编进程序里的模板,不含任何真实凭据。你的密钥在你自己的后台里,填的时候现抄。

要改的 3 行是:后台地址、插件标识、APP SECRET。要写的 3 行是:Service Worker 里一次门禁注册 + 面板里一次状态读取 + 一个拦截。


九、把后台本身也卖出去

这是个后来居上的用法:你运营一个母站,客户各装一个子站。

客户服务器上跑的是客户版,只是 config.yaml 里多了一段 license,指向你的母站。母站不用改任何代码,只要把「千猫授权后台」当成一个普通应用登记进去。

效果:

状态
客户的后台页面
客户的插件接口
正常
全部可用
正常
宽限期(快到期/到期 7 天内/很久没同步)
全部可用,顶部黄条提示
正常
已锁定(未激活/过期超宽限/被停用/设备不符/域名不符)
只能进「软件授权」页
返回 code 1005,版本包下载 403

而你在母站发版,所有客户的子站都是一键升级。

等于你既卖插件,又能把"给插件做授权"这件事本身做成产品。一套系统,两种卖法。


十、谁该用

  • 你做了一个实用型 Chrome 插件,想把它变成收费产品;
  • 你用激活码 / 买断 / 限期授权的方式变现;
  • 你的客户已经上两位数,每次发版都在群里发文件;
  • 你在做多个插件,希望一套后台统一管授权和版本;
  • 你想把这套后台自己再卖一份出去。

符合任何一条,都值得看看。


一个典型场景

你做了一个"抖店售后申诉助手",卖给电商卖家。

买家付款,你从后台生成激活码发给他;他填码激活,插件解锁全部功能。

两周后你修了个 bug、加了新筛选项,母站上传新版——客户打开后台,看到一个红按钮,点一下,进度条走完,页面刷新,就是新版了。 他不会问你"我该下哪个",你也不用在群里解释。

有买家退款?后台把那张码一键禁用,他那边插件立刻回到未授权状态。

又过半年,你决定把这套后台也卖给三个同行。他们各自装一份客户版子站,你给他们发授权码;你发新版,他们也是一键升级。

从收费、到激活、到更新、到停用、到再分发,整条链路都不用你碰代码。


结语

把插件做出来只是第一步。让它能收费、能更新、能管住,才算一个产品。

上一篇文章我们说的是"改 3 行代码就能接上授权"。这一版我们想说的是:发版这件事,不该再占用你的时间——而且这次,是 Go + Vue 重写的整套。

如果你正在做 Chrome 插件,正被"授权"和"发版"这两件事拖着,欢迎来看看。


千猫网络 · 让每一个 Chrome 插件都安心变现

回首页,搜索你想要的文档

在首页搜索框输入关键词,快速定位资料

文库首页搜索指引 立即搜索

相关学习资料