11 KiB
Ops Licence 许可证管理系统
Ops Licence 是供内部人员使用的离线许可证签发管理系统。系统通过“平台名称 + Workspace”维护授权对象,支持许可证签发、签发记录查询、续期和 licence.key 下载,并提供可复制到其他 Go 项目的本地验证 SDK。
系统不依赖目标平台调用在线接口。许可证由内部人员在管理页面生成后,手动交付给使用方;使用方通过 Go SDK 在本地完成真实性、授权对象、有效期和配额验证。
主要功能
- 授权对象:以“平台名称 + Workspace”作为唯一授权标识,区分大小写。
- 直接签发:设置生效日期、到期日期和全部配额,生成并下载许可证文件。
- 续期:从原签发记录发起,生成一份新的许可证文件,原记录和原文件保持不变。
- 签发记录:查看签发类型、授权对象、有效期、配额、签发时间和前一份许可证。
- 再次下载:从历史签发记录重新下载当时生成的许可证文件。
- 本地验证:Go SDK 不访问许可证管理系统,也不绑定具体机器。
- 简约 Web 管理界面:前端已经嵌入 Go 可执行文件,无需单独部署静态站点。
当前系统不包含登录、会话、在线激活、许可证吊销和机器绑定功能。
业务规则
授权对象
平台名称与 Workspace 共同组成一个授权对象,两者都不能为空。同一组合不能重复,并且按原始大小写精确匹配。
- 没有签发记录时:允许修改、删除。
- 存在任意签发记录后:禁止修改、删除。
日期
生效日期和到期日期均采用 YYYY-MM-DD,按照 Asia/Shanghai 自然日判断,首尾日期都包含在有效期内。到期日期不能早于生效日期。
配额
每次直接签发或续期都会保存当次全部配额的完整快照。这里的“全部配额快照”是指:签发记录会固定保存下面 11 个配额在当次签发时的数值,以后不会跟随其他记录变化。续期页面会预填上一份许可证的配额,但提交后会生成新的独立记录。
所有配额都是非负整数,0 表示该项未授权:
| 字段 | 含义 |
|---|---|
max_database |
数据库数量上限 |
max_middleware |
中间件数量上限 |
max_network_device |
网络设备数量上限 |
max_security |
安全设备数量上限 |
max_storage |
存储设备数量上限 |
max_pc |
PC 数量上限 |
max_server |
服务器数量上限 |
max_user |
用户数量上限 |
max_role |
角色数量上限 |
max_permission |
权限数量上限 |
max_menu |
菜单数量上限 |
快速启动
1. 准备 PostgreSQL
数据库名称必须精确为 ops_licence。服务首次启动时会自动执行内嵌迁移,创建本项目自己的数据表:
schema_migrationsauthorization_subjectssigning_keyslicence_issuances
不需要手工建表,也不会使用其他项目的业务表。
2. 配置 YAML
本地配置文件为 D:\work\license\etc\license.yaml。配置只包含服务监听地址、数据库 DSN 和当前签发密钥文件:
server:
listen_addr: "127.0.0.1:8080"
database:
dsn: "postgresql://username:password@127.0.0.1:5432/ops_licence?sslmode=disable"
signing:
private_key_file: "D:\\work\\license\\secrets\\signing-private.pem"
certificate_file: "D:\\work\\license\\secrets\\signing-certificate.json"
注意:
listen_addr必须使用回环 IP,例如127.0.0.1:8080。- DSN 必须显式包含用户名、密码、单一主机、端口、数据库名和
sslmode。 - 支持的
sslmode为disable、require、verify-ca、verify-full。 - 数据库用户名或密码包含特殊字符时,需要使用 URL 百分号编码。
- DSN 不要添加
TimeZone等额外查询参数。 - 服务只读取 YAML,不需要配置数据库环境变量;运行用户已有的
PG*数据库连接环境变量需要清除。 - 签发私钥和签发密钥凭证必须使用绝对路径,并且必须相互匹配。
3. 启动服务
在项目目录执行:
.\bin\ops-licence.exe serve --config D:\work\license\etc\license.yaml
启动成功后访问:
http://127.0.0.1:8080
页面操作
新建授权对象
- 打开“授权对象”。
- 点击“新建授权对象”。
- 填写平台名称和 Workspace 后保存。
如果填写错误,只要该对象还没有签发记录,就可以从详情页修改或删除。
直接签发
- 打开授权对象详情。
- 点击“直接签发”。
- 选择生效日期、到期日期并填写全部配额。
- 点击“签发并下载”。
- 将下载得到的
{许可证ID}.licence.key文件手动交付给使用方。
签发成功后记录不可修改、不可删除。
续期
- 打开“签发记录”。
- 进入需要续期的记录详情。
- 点击“续期”。
- 确认或调整日期和配额。
- 提交后下载新生成的
{许可证ID}.licence.key。
续期不会覆盖旧记录,也不会让旧文件失效;新记录会保存其来源许可证 ID。
密钥管理
许可证采用两级 Ed25519 信任链:
- 根私钥签发“签发密钥凭证”。
- 签发私钥签发具体的许可证。
- SDK 内置根公钥,先验证签发密钥凭证,再验证许可证签名。
根私钥不需要部署到许可证管理服务器。服务器运行时只需要当前签发私钥和对应的签发密钥凭证。
更换签发密钥
使用现有根私钥签发一组新的签发密钥和凭证:
.\bin\ops-licence.exe keys issue `
--root-private D:\keys\root-private.pem `
--private-out D:\keys\signing-private-2026.pem `
--certificate-out D:\keys\signing-certificate-2026.json
输出文件不能预先存在。生成后修改 YAML 中的两个 signing 文件路径并重启服务。
更换签发密钥不需要重新生成或复制 SDK,因为新签发密钥仍由同一个根私钥签发。只有主动更换根信任时,才需要更新 SDK 内置根公钥并重新复制 SDK。
重新建立根信任
当前项目已经包含根公钥。下面的命令只用于明确决定更换整套根信任时,不属于日常签发密钥轮换:
go run .\cmd\root-keygen `
--private-out D:\keys\new-root-private.pem `
--sdk-public-out D:\keys\root_public_key.go
生成后需要用新的 root_public_key.go 替换 SDK 中的根公钥文件,再用新根私钥签发新的签发密钥。更换根信任后必须重新分发 SDK。
Linux 部署
项目提供了 Linux systemd 和 Caddy 配置模板:
deploy/ops-licence.servicedeploy/ops-license-ca.conf(Caddy)deploy/ops-license.conf(Nginx,可选)
文件位置
建议按模板放置:
/data/app/ops-licence
/data/app/etc/license.yaml
/data/app/etc/signing-private.pem
/data/app/etc/signing-certificate.json
Linux YAML 示例:
server:
listen_addr: "127.0.0.1:14522"
database:
dsn: "postgresql://username:password@database-host:5432/ops_licence?sslmode=disable"
signing:
private_key_file: "/data/app/etc/signing-private.pem"
certificate_file: "/data/app/etc/signing-certificate.json"
systemd
文件放到 /data/app 后,安装并启动服务:
cd /data/app
chmod 0755 ./ops-licence
sudo install -m 0644 etc/ops-licence.service /etc/systemd/system/ops-licence.service
sudo systemctl daemon-reload
sudo systemctl enable --now ops-licence
sudo systemctl status ops-licence
查看运行日志:
sudo journalctl -u ops-licence -f
Caddy
deploy/ops-license-ca.conf 将 licence.apinb.com 的 HTTPS 请求反向代理到 127.0.0.1:14522,并使用本地证书文件。部署时按实际域名和证书路径修改后,并入主 Caddyfile 或单独加载:
sudo caddy validate --config deploy/ops-license-ca.conf
sudo systemctl reload caddy
配置示例:
licence.apinb.com {
tls /data/licence/apinb.com.pem /data/licence/apinb.com.key
reverse_proxy 127.0.0.1:14522
}
Go 可执行文件已经包含前端资源,Caddy 只需要反向代理所有路径,不需要另外复制 web/dist。
Go SDK 接入
将整个 sdk/licence 目录复制到目标 Go 项目中,保持目录内所有文件完整。目标项目不需要连接许可证管理系统。
使用时传入许可证文件路径,以及目标程序实际使用的平台名称和 Workspace:
result, err := licence.VerifyFile(
"/etc/example/licence.key",
licence.Subject{
PlatformName: "平台名称",
Workspace: "workspace",
},
)
if err != nil {
return err
}
databaseLimit := result.Quotas.MaxDatabase
expiresOn := result.ExpiresOn
VerifyFile 会一次完成:
- 文件格式和大小检查。
- 根公钥到签发密钥的凭证验证。
- 许可证签名验证。
- 平台名称与 Workspace 精确匹配。
- 按
Asia/Shanghai日期检查是否生效、是否到期。 - 所有配额非负检查。
可以通过 errors.Is 区分主要验证结果:
switch {
case errors.Is(err, licence.ErrNotYetValid):
// 许可证尚未生效
case errors.Is(err, licence.ErrExpired):
// 许可证已经到期
case errors.Is(err, licence.ErrSubjectMismatch):
// 平台名称或 Workspace 不匹配
case errors.Is(err, licence.ErrInvalidSignature):
// 许可证签名无效
case err != nil:
// 文件格式、签发密钥凭证或配额无效
}
复制后,SDK 的 Go 导入路径以目标项目的模块路径为准。
HTTP 接口
管理页面使用以下接口,接口不要求登录或会话:
| 方法 | 路径 | 用途 |
|---|---|---|
GET |
/api/v1/dashboard |
获取首页统计 |
GET |
/api/v1/subjects |
查询授权对象 |
POST |
/api/v1/subjects |
新建授权对象 |
GET |
/api/v1/subjects/:id |
获取授权对象详情 |
PUT |
/api/v1/subjects/:id |
修改未签发的授权对象 |
DELETE |
/api/v1/subjects/:id |
删除未签发的授权对象 |
POST |
/api/v1/subjects/:id/licences |
直接签发并下载许可证 |
GET |
/api/v1/licences |
查询签发记录 |
GET |
/api/v1/licences/:id |
获取签发记录详情 |
GET |
/api/v1/licences/:id/download |
再次下载许可证 |
POST |
/api/v1/licences/:id/renew |
续期并下载新许可证 |
构建
构建环境:Go 1.25.1、Node.js、pnpm。
前端构建结果会直接写入 internal/webui/dist:
Set-Location D:\work\license\web
pnpm.cmd install
pnpm.cmd build
前端有变化时,先构建前端,再构建 Go 程序。
Windows:
Set-Location D:\work\license
go build -buildvcs=false -o bin\ops-licence.exe .\cmd\ops-licence
Linux AMD64:
Set-Location D:\work\license
$env:GOOS = "linux"
$env:GOARCH = "amd64"
go build -buildvcs=false -o dist\ops-licence .\cmd\ops-licence
项目目录
cmd/ops-licence 服务与签发密钥命令
cmd/root-keygen 根密钥生成工具
deploy Linux systemd 与 Caddy/Nginx 模板
docs 设计、实施与使用文档
etc 本地 YAML 配置
internal 服务端实现、数据库迁移和内嵌前端
sdk/licence 可复制的 Go 本地验证 SDK
web Vue 管理界面源码
bin Windows 可执行文件
dist Linux 可执行文件