2026-07-31 18:27:53 +08:00
2026-07-31 16:59:55 +08:00
2026-07-31 18:27:53 +08:00
2026-07-31 18:27:53 +08:00
2026-07-31 16:59:55 +08:00
2026-07-31 16:59:55 +08:00
2026-07-31 16:59:55 +08:00
2026-07-31 18:27:53 +08:00
2026-07-31 16:59:55 +08:00
2026-07-31 16:59:55 +08:00
2026-07-31 18:27:53 +08:00

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_migrations
  • authorization_subjects
  • signing_keys
  • licence_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
  • 支持的 sslmodedisablerequireverify-caverify-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

页面操作

新建授权对象

  1. 打开“授权对象”。
  2. 点击“新建授权对象”。
  3. 填写平台名称和 Workspace 后保存。

如果填写错误,只要该对象还没有签发记录,就可以从详情页修改或删除。

直接签发

  1. 打开授权对象详情。
  2. 点击“直接签发”。
  3. 选择生效日期、到期日期并填写全部配额。
  4. 点击“签发并下载”。
  5. 将下载得到的 {许可证ID}.licence.key 文件手动交付给使用方。

签发成功后记录不可修改、不可删除。

续期

  1. 打开“签发记录”。
  2. 进入需要续期的记录详情。
  3. 点击“续期”。
  4. 确认或调整日期和配额。
  5. 提交后下载新生成的 {许可证ID}.licence.key

续期不会覆盖旧记录,也不会让旧文件失效;新记录会保存其来源许可证 ID。

密钥管理

许可证采用两级 Ed25519 信任链:

  1. 根私钥签发“签发密钥凭证”。
  2. 签发私钥签发具体的许可证。
  3. 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.service
  • deploy/ops-license-ca.confCaddy
  • deploy/ops-license.confNginx可选

文件位置

建议按模板放置:

/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.conflicence.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 可执行文件
Description
凭证管理(可复用)
Readme 36 MiB
Languages
Go 78.8%
Vue 14%
TypeScript 5.2%
CSS 1.8%
HTML 0.2%