# 其他项目接入许可证验证系统说明 本文档面向“其他项目负责人”。 目标只有一个:让你的项目按统一方式接入当前 `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` 很关键,后续所有公开接口都会用到它,而且必须全链路一致。 建议格式: ```text 小写英文 / 数字 / 连字符 示例:flnxi-sucai ``` ## 4. 验证系统会给你什么 验证系统负责人完成初始化后,至少会给项目方这些信息: ```env 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 现在真正可用的公开接口 所有可用接口都必须带项目编码: ```text 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 签名,并把下面三个字段放进请求体: ```json { "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`: ```text /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` - 能处理硬件指纹、本地许可证缓存、签名校验 - 能避免把敏感逻辑全部暴露给前端或最终用户 推荐链路示例: ```text 浏览器 -> 你的项目后端 /api/license/* -> license-server /api/license/{projectCode}/* 桌面程序 -> 本地服务 / 守护进程 -> license-server /api/license/{projectCode}/* 业务服务 -> 自身服务端模块 -> license-server /api/license/{projectCode}/* ``` 如果你的项目有安装 Agent,版本检查也建议走统一配置: ```text LICENSE_SERVER_URL + LICENSE_PROJECT_CODE ``` 不推荐的方式: - 纯前端页面直接请求 `license-server` - 把项目编码、签名校验和完整验证逻辑全部暴露到浏览器端 - 没有任何本地可信层,却想直接做安全授权 如果你的项目没有独立后端,也没有本地服务层,请先补一个最小代理层,再谈接入。 ## 7. 项目侧至少要接通的能力 ### 7.0 通用返回结构说明 涉及许可证返回时,核心结构按下面理解即可: ```json { "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 激活 用于输入激活码后完成首次授权。 目标接口: ```text POST /api/license/{projectCode}/activate ``` 最少请求字段: ```json { "licenseKey": "XXXX-XXXX-XXXX-XXXX", "fingerprint": { "machineId": "xxx", "hostname": "xxx", "macAddress": "xxx", "instanceId": "xxx" }, "timestamp": 1700000000000 } ``` 成功后会返回签名许可证,项目侧应自行保存本地许可证缓存。 成功响应可按下面理解: ```json { "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 验证 用于已激活实例的定期校验。 目标接口: ```text POST /api/license/{projectCode}/verify ``` 最少请求字段: ```json { "instanceId": "xxx", "fingerprint": { "machineId": "xxx", "hostname": "xxx", "macAddress": "xxx", "instanceId": "xxx" }, "timestamp": 1700000000000 } ``` 成功响应可按下面理解: ```json { "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 心跳 用于持续上报实例状态和使用量。 目标接口: ```text POST /api/license/{projectCode}/heartbeat ``` 最少请求字段: ```json { "instanceId": "xxx", "fingerprint": { "machineId": "xxx", "hostname": "xxx", "macAddress": "xxx", "instanceId": "xxx" }, "usage": { "userCount": 10, "materialCount": 100 }, "timestamp": 1700000000000 } ``` 成功响应可按下面理解: ```json { "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 免费试用 如果你的项目支持免费试用,再接: ```text POST /api/license/{projectCode}/register-free ``` 最少请求字段: ```json { "fingerprint": { "machineId": "xxx", "hostname": "xxx", "macAddress": "xxx", "instanceId": "xxx" }, "customerName": "某某公司", "contactInfo": "手机号/微信/邮箱", "timestamp": 1700000000000 } ``` 成功响应可按下面理解: ```json { "success": true, "data": { "activationCode": "FLNXI-FREE-XXXX-XXXX-XXXX-XXXX", "plan": "FREE", "planName": "免费版", "isExisting": false } } ``` ### 7.5 版本检查 如果你的项目有升级提示或安装代理,需要接: ```text GET /api/version/{projectCode}/check GET /api/version/{projectCode}/agent ``` 版本检查响应可按下面理解: ```json { "latestVersion": "1.0.0", "minRequiredVersion": "1.0.0", "releaseNotes": "更新说明", "releaseDate": "2026-03-13T00:00:00.000Z", "isSecurityUpdate": false, "isCriticalUpdate": false, "downloadSize": "120MB" } ``` Agent 版本检查响应可按下面理解: ```json { "latestVersion": "1.0.0", "releaseNotes": "更新说明", "releaseDate": "2026-03-13T00:00:00.000Z" } ``` ### 7.6 付费激活码生成 如果你的项目需要由系统接口生成付费激活码,再接: ```text POST /api/license/{projectCode}/generate ``` 该接口不是普通前端直调接口。当前真实要求: - Header 必须带 `X-API-Key` - 请求体必须带 `timestamp` - 请求体必须带 `sign` - `sign` 当前按 `md5(orderNo + timestamp + apiKey)` 校验 - 请求时间窗口为 5 分钟 最少请求字段: ```json { "orderNo": "ORDER-20260513-001", "plan": "STANDARD", "customerEmail": "customer@example.com", "customerName": "某某公司", "contactInfo": "联系人电话/微信/邮箱", "validDays": 365, "timestamp": 1778660000000, "sign": "md5-sign" } ``` 当前 `plan` 仅支持 `STANDARD` 和 `PRO`。`FREE` 走免费试用注册链路,不走发证接口。 ## 8. 项目配置要求 项目侧至少要能提供这三个配置项: ```env 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. 后台管理规则 接入完成后,后台将按项目维度工作。 常见路径: ```text /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` 历史安全评估快照和内部整改台账不建议对外发送;如果必须引用,需要先按当前代码、部署文件和实际环境重新复核。