flnxi-Wu/docs/其他项目接入许可证系统说明.md
2026-06-02 19:25:48 +08:00

19 KiB
Raw Blame History

其他项目接入许可证验证系统说明

本文档面向“其他项目负责人”。

目标只有一个:让你的项目按统一方式接入当前 license-server 多项目验证系统,而不是各项目再各自实现一套授权服务。

这是一份给外部项目用的接入说明,不是 FLNXI 主项目的日常运维入口。FLNXI 主项目的真相和入口请优先看 README.md、docs/README.md 和 docs/deployment/部署说明.md。

说明:

  • 当前仓库自己的主项目,确实主要面向 Docker / NAS / Linux 私有化部署
  • 但“其他项目接入许可证系统”这件事,本身不要求项目一定跑在 Docker 里
  • 只要项目侧有一个可信执行层可以承接许可证逻辑,就可以接入
  • 对外沟通时可先发本文档作为接入主说明;涉及 FLNXI 主项目部署、当前生产配置或安全评估结论时,必须回到当前代码和对应部署文档复核,不能把本文当成全部事实来源

1. 先说结论

当前验证系统已经支持多项目接入,但新项目接入不是“零配置自动生效”,而是需要完成一次标准初始化。

接入时分成两部分:

  • 验证系统侧负责:建项目、配项目库、开后台权限、准备联调环境
  • 项目侧负责:按统一接口接入、配置项目编码、完成激活/验证/心跳/版本检查联调

只要你的项目能遵守本文档里的接口和配置约定,通常不需要再改 license-server 架构代码。

补充一条很重要:

  • 项目侧运行环境不限定为 Docker。容器、Kubernetes、Linux 服务、Windows 服务、桌面程序配套本地服务,都可以接入

2. 谁负责什么

2.1 验证系统负责人负责

  • 在 license-server 主库中创建项目
  • 为项目分配唯一 projectCode
  • 配置该项目对应的许可证数据库连接
  • 初始化该项目库的许可证表结构
  • 如有需要,为项目负责人创建后台账号并分配项目权限
  • 提供联调地址、后台地址和项目编码

2.2 项目负责人负责

  • 在自己项目中接入许可证验证流程
  • 配置 LICENSE_SERVER_URL 和 LICENSE_PROJECT_CODE
  • 接通激活、验证、心跳接口
  • 如项目有安装代理或版本升级能力,接通版本检查接口
  • 完成自测并配合联调

3. 项目接入前要准备什么

项目负责人至少准备以下信息交给验证系统负责人:

  • 项目名称
  • 项目唯一编码 projectCode
  • 项目联系人
  • 项目当前运行方式
    • 例如:Docker / Kubernetes / Linux 服务 / Windows 服务 / 桌面程序本地服务 / 其它
  • 项目是否有自己的后端或本地服务层
  • 是否需要付费激活
  • 是否需要免费试用
  • 是否需要版本检查
  • 是否需要 Agent 版本检查
  • 该项目的许可证数据库连接信息
    • dbHost
    • dbPort
    • dbName
    • dbUser
    • dbPassword
    • dbSchema
  • 需要接入后台管理的人员名单

projectCode 很关键,后续所有公开接口都会用到它,而且必须全链路一致。

建议格式:

小写英文 / 数字 / 连字符
示例:flnxi-sucai

4. 验证系统会给你什么

验证系统负责人完成初始化后,至少会给项目方这些信息:

LICENSE_SERVER_URL=https://license.example.com
LICENSE_PROJECT_CODE=your-project-code
LICENSE_REQUEST_SIGNING_SECRET=shared-secret-at-least-32-chars

如果项目需要后台管理,还会给:

  • 后台地址:/admin/login
  • 登录账号
  • 项目是否已分配到该账号

4.1 联调地址和账号获取规则

本文档是对外接入说明,不记录内部服务器 IP、SSH 登录方式、后台账号或一次性联调密码。

正式联调时,由验证系统负责人通过独立渠道提供:

  • 当前可用的 LICENSE_SERVER_URL
  • 当前项目的 LICENSE_PROJECT_CODE
  • 后台入口和后台账号(如需要)
  • 测试许可证或测试发证方式
  • 请求签名共享密钥 LICENSE_REQUEST_SIGNING_SECRET

不要把历史联调地址、个人账号或服务器登录方式写进项目代码、.env.example 或对外文档。

5. 统一接口规则

5.1 现在真正可用的公开接口

所有可用接口都必须带项目编码:

POST /api/license/{projectCode}/activate
POST /api/license/{projectCode}/verify
POST /api/license/{projectCode}/heartbeat
POST /api/license/{projectCode}/register-free
POST /api/license/{projectCode}/premium-modules
POST /api/license/{projectCode}/premium-modules/{moduleId}
POST /api/license/{projectCode}/premium-modules/{moduleId}/pull-token
POST /api/license/{projectCode}/generate
GET  /api/registry/auth

GET  /api/version/{projectCode}/check
GET  /api/version/{projectCode}/agent

5.1.1 请求签名要求

activate、verify、heartbeat、register-free、premium-modules、premium-modules/{moduleId} 和 premium-modules/{moduleId}/pull-token 当前不是“只带项目编码就能访问”。项目侧可信执行层必须使用 LICENSE_REQUEST_SIGNING_SECRET 对请求体做 HMAC 签名,并把下面三个字段放进请求体:

{
  "timestamp": 1779753600000,
  "nonce": "32位随机十六进制字符串",
  "signature": "sha256-hmac-hex"
}

签名计算口径以当前代码 lib/license/request-signature.ts 和 license-server/lib/request-signature.ts 为准:签名内容包含 action、projectCode 和按 key 排序后的请求体,且签名时会排除 signature 字段本身。远端会拒绝缺签名、签名错误、时间超出窗口和重复 nonce 的请求。

这和“验证许可证返回值的 RSA 签名”是两件事:请求签名用于防止外部伪造请求;许可证返回签名用于确认许可证内容没有被篡改。

如果项目需要拆分付费模块,还要额外接短期拉包凭证:验证系统先按 LicenseModuleGrant 判断该许可证是否允许使用某模块,再给 15 分钟有效的 pullToken。Registry 前置代理通过 GET /api/registry/auth 校验 token、仓库路径和 digest 后才放行拉取。发布账号只给发布方推镜像使用,不应发给客户现场。

5.2 禁止再使用的旧接口

以下旧接口已经废弃,当前统一返回 410:

/api/license/activate
/api/license/verify
/api/license/heartbeat
/api/license/register-free
/api/license/generate
/api/version/check
/api/version/agent

如果你的项目还在调用这些旧路径,说明还没有接完。

5.3 AI 落地时必须遵守的最少规则

如果项目负责人是用 AI 做接入,至少要让 AI 严格遵守这 6 条:

  • 只能调用带 projectCode 的新接口
  • 不要继续使用任何旧的无项目接口
  • 不要让前端直接连 license-server
  • 必须有一个可信执行层承接验证逻辑
  • 必须做本地许可证缓存,并校验服务端返回许可证的签名
  • LICENSE_PROJECT_CODE 必须与验证系统登记值完全一致

6. 推荐接入方式与运行环境要求

推荐模式:由你项目里的“可信执行层”做代理,不要让前端、纯脚本或纯静态页面直接调用远端 license-server。

原因:

  • 可以隐藏真实验证服务器地址
  • 可以在你自己的服务层生成硬件指纹
  • 可以在你自己的服务层做签名验证和本地缓存
  • 可以减少前端暴露敏感校验逻辑

这里的“可信执行层”不要求一定是 Docker 容器里的后端,它可以是:

  • Web 项目的后端接口
  • 桌面程序配套的本地服务或守护进程
  • 安装代理、Launcher、常驻进程
  • 业务服务自身的服务端模块
  • Kubernetes / 容器里的应用服务

只要满足这 4 条,就具备接入基础:

  • 能稳定读取 LICENSE_SERVER_URL 和 LICENSE_PROJECT_CODE
  • 能访问远端 license-server
  • 能处理硬件指纹、本地许可证缓存、签名校验
  • 能避免把敏感逻辑全部暴露给前端或最终用户

推荐链路示例:

浏览器
  -> 你的项目后端 /api/license/*
  -> license-server /api/license/{projectCode}/*

桌面程序
  -> 本地服务 / 守护进程
  -> license-server /api/license/{projectCode}/*

业务服务
  -> 自身服务端模块
  -> license-server /api/license/{projectCode}/*

如果你的项目有安装 Agent,版本检查也建议走统一配置:

LICENSE_SERVER_URL + LICENSE_PROJECT_CODE

不推荐的方式:

  • 纯前端页面直接请求 license-server
  • 把项目编码、签名校验和完整验证逻辑全部暴露到浏览器端
  • 没有任何本地可信层,却想直接做安全授权

如果你的项目没有独立后端,也没有本地服务层,请先补一个最小代理层,再谈接入。

7. 项目侧至少要接通的能力

7.0 通用返回结构说明

涉及许可证返回时,核心结构按下面理解即可:

{
  "data": {
    "type": "FREE | STANDARD | PRO",
    "limits": {
      "maxUsers": 10,
      "maxMaterials": 100
    },
    "features": {
      "customTemplates": false,
      "systemSettings": false,
      "departmentIsolation": false,
      "apiAccess": false,
      "intranetPenetration": false
    },
    "expiresAt": null,
    "issuedAt": "2026-03-13T00:00:00.000Z"
  },
  "signature": "base64-signature",
  "issuedAt": "2026-03-13T00:00:00.000Z"
}

状态码通常按下面理解:

  • 200:请求成功
  • 400:参数错误、请求过期、格式不合法
  • 403:许可证禁用、过期、指纹不匹配、无权限
  • 404:项目不存在、许可证不存在、激活记录不存在
  • 409:许可证已在其他设备激活
  • 410:还在调用旧接口
  • 500:服务端错误

7.1 激活

用于输入激活码后完成首次授权。

目标接口:

POST /api/license/{projectCode}/activate

最少请求字段:

{
  "licenseKey": "XXXX-XXXX-XXXX-XXXX",
  "fingerprint": {
    "machineId": "xxx",
    "hostname": "xxx",
    "macAddress": "xxx",
    "instanceId": "xxx"
  },
  "timestamp": 1700000000000
}

成功后会返回签名许可证,项目侧应自行保存本地许可证缓存。

成功响应可按下面理解:

{
  "success": true,
  "license": {
    "data": {
      "type": "STANDARD",
      "limits": {
        "maxUsers": -1,
        "maxMaterials": -1
      },
      "features": {
        "customTemplates": true,
        "systemSettings": true,
        "departmentIsolation": true,
        "apiAccess": true,
        "intranetPenetration": false
      },
      "expiresAt": null,
      "issuedAt": "2026-03-13T00:00:00.000Z"
    },
    "signature": "base64-signature",
    "issuedAt": "2026-03-13T00:00:00.000Z"
  }
}

7.2 验证

用于已激活实例的定期校验。

目标接口:

POST /api/license/{projectCode}/verify

最少请求字段:

{
  "instanceId": "xxx",
  "fingerprint": {
    "machineId": "xxx",
    "hostname": "xxx",
    "macAddress": "xxx",
    "instanceId": "xxx"
  },
  "timestamp": 1700000000000
}

成功响应可按下面理解:

{
  "valid": true,
  "license": {
    "data": {
      "type": "STANDARD",
      "limits": {
        "maxUsers": -1,
        "maxMaterials": -1
      },
      "features": {
        "customTemplates": true,
        "systemSettings": true,
        "departmentIsolation": true,
        "apiAccess": true,
        "intranetPenetration": false
      },
      "expiresAt": null,
      "issuedAt": "2026-03-13T00:00:00.000Z"
    },
    "signature": "base64-signature",
    "issuedAt": "2026-03-13T00:00:00.000Z"
  }
}

7.3 心跳

用于持续上报实例状态和使用量。

目标接口:

POST /api/license/{projectCode}/heartbeat

最少请求字段:

{
  "instanceId": "xxx",
  "fingerprint": {
    "machineId": "xxx",
    "hostname": "xxx",
    "macAddress": "xxx",
    "instanceId": "xxx"
  },
  "usage": {
    "userCount": 10,
    "materialCount": 100
  },
  "timestamp": 1700000000000
}

成功响应可按下面理解:

{
  "acknowledged": true,
  "license": {
    "data": {
      "type": "STANDARD",
      "limits": {
        "maxUsers": -1,
        "maxMaterials": -1
      },
      "features": {
        "customTemplates": true,
        "systemSettings": true,
        "departmentIsolation": true,
        "apiAccess": true,
        "intranetPenetration": false
      },
      "expiresAt": null,
      "issuedAt": "2026-03-13T00:00:00.000Z"
    },
    "signature": "base64-signature",
    "issuedAt": "2026-03-13T00:00:00.000Z"
  }
}

7.4 免费试用

如果你的项目支持免费试用,再接:

POST /api/license/{projectCode}/register-free

最少请求字段:

{
  "fingerprint": {
    "machineId": "xxx",
    "hostname": "xxx",
    "macAddress": "xxx",
    "instanceId": "xxx"
  },
  "customerName": "某某公司",
  "contactInfo": "手机号/微信/邮箱",
  "timestamp": 1700000000000
}

成功响应可按下面理解:

{
  "success": true,
  "data": {
    "activationCode": "FLNXI-FREE-XXXX-XXXX-XXXX-XXXX",
    "plan": "FREE",
    "planName": "免费版",
    "isExisting": false
  }
}

7.5 版本检查

如果你的项目有升级提示或安装代理,需要接:

GET /api/version/{projectCode}/check
GET /api/version/{projectCode}/agent

版本检查响应可按下面理解:

{
  "latestVersion": "1.0.0",
  "minRequiredVersion": "1.0.0",
  "releaseNotes": "更新说明",
  "releaseDate": "2026-03-13T00:00:00.000Z",
  "isSecurityUpdate": false,
  "isCriticalUpdate": false,
  "downloadSize": "120MB"
}

Agent 版本检查响应可按下面理解:

{
  "latestVersion": "1.0.0",
  "releaseNotes": "更新说明",
  "releaseDate": "2026-03-13T00:00:00.000Z"
}

7.6 付费激活码生成

如果你的项目需要由系统接口生成付费激活码,再接:

POST /api/license/{projectCode}/generate

该接口不是普通前端直调接口。当前真实要求:

  • Header 必须带 X-API-Key
  • 请求体必须带 timestamp
  • 请求体必须带 sign
  • sign 当前按 md5(orderNo + timestamp + apiKey) 校验
  • 请求时间窗口为 5 分钟

最少请求字段:

{
  "orderNo": "ORDER-20260513-001",
  "plan": "STANDARD",
  "customerEmail": "customer@example.com",
  "customerName": "某某公司",
  "contactInfo": "联系人电话/微信/邮箱",
  "validDays": 365,
  "timestamp": 1778660000000,
  "sign": "md5-sign"
}

当前 plan 仅支持 STANDARD 和 PRO。FREE 走免费试用注册链路,不走发证接口。

8. 项目配置要求

项目侧至少要能提供这三个配置项:

LICENSE_SERVER_URL=https://license.example.com
LICENSE_PROJECT_CODE=your-project-code
LICENSE_REQUEST_SIGNING_SECRET=shared-secret-at-least-32-chars

这三个值不一定非要写在 .env 里,也可以放在:

  • 系统环境变量
  • 项目配置文件
  • 服务启动参数
  • Kubernetes Secret / ConfigMap
  • Windows 服务配置
  • 桌面程序本地安全配置

核心不是“必须用环境变量”,而是“运行时必须能稳定、安全地读到这些值”。

要求:

  • LICENSE_PROJECT_CODE 必须和验证系统中登记的项目编码完全一致
  • LICENSE_REQUEST_SIGNING_SECRET 必须和许可证服务中同项目使用的共享密钥一致,至少 32 字符,不能放到浏览器端
  • 不要在多个地方写不同的项目编码
  • 不要继续写死旧的无项目接口地址

9. 后台管理规则

接入完成后,后台将按项目维度工作。

常见路径:

/admin/select-project
/admin/{projectCode}
/admin/{projectCode}/licenses
/admin/{projectCode}/alerts
/admin/{projectCode}/versions
/admin/{projectCode}/customers
/admin/{projectCode}/api-keys

规则:

  • 普通管理员只能看到被分配的项目
  • 超级管理员可以管理所有项目
  • 是否给项目负责人开放后台,由验证系统负责人决定

10. 标准接入流程

建议严格按下面顺序推进:

  1. 项目方提交接入申请
  2. 验证系统负责人创建项目并初始化项目库
  3. 验证系统负责人返回 LICENSE_SERVER_URL 和 LICENSE_PROJECT_CODE
  4. 项目方确认由谁承接许可证逻辑
    • 后端
    • 本地服务
    • 安装代理
    • 其它可信执行层
  5. 项目方在自己的可信执行层接入激活、验证、心跳接口
  6. 如有需要,项目方接入免费试用和版本检查
  7. 项目方完成本地自测
  8. 双方做联调
  9. 联调通过后再进入正式环境

11. 联调验收清单

上线前至少确认这些项:

  • 能用正确的 projectCode 激活
  • 激活成功后能拿到许可证并本地保存
  • 验证接口能返回有效结果
  • 心跳接口能正常返回
  • 许可证禁用后,项目侧能识别失效状态
  • 旧的无项目接口已经不再被调用
  • 如启用免费试用,免费注册链路可用
  • 如启用版本检查,版本接口能返回数据
  • 后台能进入对应项目,而不是进入错误项目

12. 常见错误

12.1 项目编码不一致

最常见问题。

现象:

  • 返回 404 项目不存在
  • 或者进入了错误项目的数据

处理:

  • 检查项目方实际运行配置里的 LICENSE_PROJECT_CODE
  • 检查验证系统主库中登记的 projects.code

12.2 还在调用旧接口

现象:

  • 返回 410

处理:

  • 全量搜索项目代码里的旧路径
  • 改成 /api/license/{projectCode}/...
  • 改成 /api/version/{projectCode}/...

12.3 前端直连验证服务器

现象:

  • 暴露远端地址
  • 暴露部分校验逻辑
  • 后续切环境不方便

处理:

  • 改成项目里的可信执行层代理,不一定非要是 Web 后端

12.4 没有做本地缓存和签名校验

现象:

  • 每次使用都强依赖网络
  • 许可证返回后没有校验服务端签名

处理:

  • 在项目的可信执行层保存许可证缓存
  • 对返回许可证做签名验证;这和 activate / verify / heartbeat 请求本身是否带签名不是一回事
  • 再决定是否放行业务功能

13. 什么时候需要改验证系统代码

一般不需要。

只有在以下情况,才可能需要验证系统侧继续开发:

  • 你的项目许可证模型与现有模型明显不同
  • 你的项目需要新的授权等级或功能开关
  • 你的项目需要新的使用量统计字段
  • 你的项目需要不同的激活/验证规则
  • 你的项目需要新的后台管理能力

如果只是普通的“接一个新项目”,通常不需要改 license-server 业务代码。

14. 接入时建议直接同步确认的事项

为了减少来回沟通,建议项目负责人一次性确认这 10 项:

  1. 项目编码叫什么
  2. 项目实际运行环境是什么
  3. 由谁承接许可证逻辑
  4. 是否要独立许可证库
  5. 是否要免费试用
  6. 是否要版本检查
  7. 是否要 Agent 版本检查
  8. 谁有后台权限
  9. 测试环境地址和正式环境地址分别是什么
  10. 验收标准是什么

15. 补充说明

如果你是“项目负责人”而不是“验证系统管理员”,你不需要自己去改 license-server 服务器内部配置。

你真正需要做的是:

  • 把项目接入参数拿到手
  • 按统一接口把自己项目接上去
  • 把联调和验收跑通

对外接入可先看本文件;涉及部署、线上地址、安全结论或 FLNXI 主项目差异时,必须同步引用当前代码和对应部署说明,不能只发旧快照或内部台账。

下面这些文档属于当前项目内部补充资料,不是对方接入时的必读项:

  • docs/许可证服务接入说明.md

历史安全评估快照和内部整改台账不建议对外发送;如果必须引用,需要先按当前代码、部署文件和实际环境重新复核。