开发文档

从部署服务端到在你的应用中完成第一次许可证验证,跟随本指南逐步完成。

快速开始

本指南帮你在十分钟内完成:部署服务端 → 签发许可证 → 在应用中验证。

第一步:启动服务端

bash
docker compose up -d
lmcli init --admin admin@example.com

第二步:签发一张许可证

bash
lmcli license create \
  --product "MyApp" \
  --type subscription \
  --seats 5 \
  --expires 2026-12-31

第三步:在你的应用中激活

选择下方任意一种语言的 SDK,调用 activate 即可完成设备绑定与授权校验。

还没有安装服务端?前往 下载中心 获取对应平台的安装包。

部署服务端

系统要求

组件最低要求推荐配置
CPU / 内存2 核 / 4 GB4 核 / 8 GB
数据库PostgreSQL 14+PostgreSQL 16
缓存(可选)—Redis 7
运行时Docker 24+Docker 26+ / Compose v2

端口说明

端口服务说明
8443API 服务SDK 与第三方系统接入入口(HTTPS)
8080管理后台Web 控制台
5099浮动授权服务企业内网席位分配
生产环境务必修改默认管理员密码,并为 API 服务配置受信任的 TLS 证书。

核心概念

  • 产品(Product):你要授权分发的软件,所有许可证都归属于某个产品。
  • 许可证(License):一张授权凭证,包含类型(永久 / 订阅 / 试用)、席位数与到期时间。
  • 激活(Activation):将许可证与具体设备指纹绑定的过程,消耗一个席位。
  • 浮动授权(Floating License):由内网服务器按并发数动态分配席位,设备退出即释放。
  • 宽限期(Grace Period):设备断网后仍可离线运行的天数,可按产品配置。

SDK 总览

五种官方 SDK 提供一致的接口设计:activate(激活)、verify(校验)、heartbeat(心跳)、deactivate(释放)。

语言包名最低版本
C# / .NETLicenseManager.Client.Sdk.NET 6
Node.js@license-manager/sdkNode 18
Pythonlicensemanager-sdkPython 3.9
Gogithub.com/license-manager/sdk-goGo 1.21
Rustlicense_managerRust 1.75

C# / .NET

安装
dotnet add package LicenseManager.Client.Sdk
Program.cs
using LicenseManager.Client.Sdk;
using LicenseManager.Client.Sdk.Models;

var client = new LicenseManagerClient(new LicenseManagerOptions
{
    BaseUrl = "https://api.licensemanager.cn",
    ApiKey  = "keyId:secret",
});

var result = await client.Activations.ActivateAsync(new ActivationRequest
{
    ActivationKey = "LM-XXXX-XXXX-XXXX",
    HardwareId    = "hw-001",
});

if (result.Success)
    Console.WriteLine("激活成功");
else
    Console.WriteLine($"激活失败:{result.FailureCode} {result.FailureMessage}");

Node.js

安装
npm install @license-manager/sdk
index.js
import { LicenseManagerClient } from "@license-manager/sdk";

const client = new LicenseManagerClient({
  baseUrl: "https://api.licensemanager.cn",
  apiKey: "keyId:secret",
});

const result = await client.activations.activate({
  activationKey: "LM-XXXX-XXXX-XXXX",
  hardwareId: "hw-001",
});

if (result.success) console.log("激活成功");
else console.error(`激活失败:${result.failureCode} ${result.failureMessage}`);

Python

安装
pip install licensemanager-sdk
main.py
from licensemanager_sdk import LicenseManagerClient, ActivationRequest

client = LicenseManagerClient(
    base_url="https://api.licensemanager.cn",
    api_key="keyId:secret",
)

result = client.activations.activate(
    ActivationRequest(activation_key="LM-XXXX-XXXX-XXXX", hardware_id="hw-001")
)

if result.success:
    print("激活成功")
else:
    print(f"激活失败:{result.failure_code} {result.failure_message}")

Go

安装
go get github.com/license-manager/sdk-go
main.go
package main

import (
    "fmt"
    lm "github.com/license-manager/sdk-go"
)

func main() {
    client, err := lm.NewClient(lm.ClientOptions{
        BaseURL: "https://api.licensemanager.cn",
        APIKey:  "keyId:secret",
    })
    if err != nil {
        panic(err)
    }

    result, err := client.Activations.Activate(lm.ActivationRequest{
        ActivationKey: "LM-XXXX-XXXX-XXXX",
        HardwareID:    "hw-001",
    })
    if err != nil {
        panic(err)
    }
    if result.Success {
        fmt.Println("激活成功")
    } else {
        fmt.Printf("激活失败:%s %s\n", result.FailureCode, result.FailureMessage)
    }
}

Rust

Cargo.toml
[dependencies]
license_manager = "1.0"
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
main.rs
use license_manager::{ActivationRequest, Client, ClientConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::new(
        ClientConfig::new("https://api.licensemanager.cn").with_api_key("keyId:secret"),
    )?;

    let result = client
        .activate(&ActivationRequest {
            activation_key: "LM-XXXX-XXXX-XXXX".into(),
            hardware_id: Some("hw-001".into()),
            hostname: None,
            os_version: None,
            sdk_version: None,
            user_account: None,
        })
        .await?;

    if result.success {
        println!("激活成功");
    } else {
        println!("激活失败:{:?} {:?}", result.failure_code, result.failure_message);
    }
    Ok(())
}

REST API 参考

所有接口均基于 HTTPS + JSON,路由无前缀(/v1/...)。认证通过 Authorization 头完成,支持三种方案:

方案格式适用场景
BearerAuthorization: Bearer <JWT>用户登录后的会话调用(管理后台 / 门户)
ApiKeyAuthorization: ApiKey <keyId>:<secret>服务端到服务端集成
HmacAuthorization: Hmac <keyId>:<signature>高安全场景:签名为 HMAC-SHA256(secret, timestamp + "\n" + body)(hex 小写)。secret 通过 X-LicenseManager-Api-Key 头单独提供(仅用于服务端校验,不参与签名);时间戳通过 X-LicenseManager-Timestamp 头提供(Unix 秒,容差 ±300 秒)

默认配置下客户端激活类接口(activate / verify / heartbeat / deactivate)允许匿名调用,可通过服务端配置 Auth:AllowPublicActivation=false 收紧为强制认证。

方法路径说明
POST/v1/customer/licenses/activate激活设备(请求体 activationKey / hardwareId / hostname)
POST/v1/customer/licenses/verify校验许可证有效性(licenseKey / hardwareId)
POST/v1/customer/licenses/heartbeat设备心跳续约(licenseKey / hardwareId)
POST/v1/customer/licenses/deactivate释放设备席位(licenseKey / hardwareId)

请求与响应字段统一为 camelCase;成功响应信封 {success:true,data,...},错误信封 {success:false,error:{code,message},...}。

示例:校验许可证

bash
curl -X POST https://api.licensemanager.cn/v1/customer/licenses/verify \
  -H "Authorization: ApiKey $KEY_ID:$SECRET" \
  -H "Content-Type: application/json" \
  -d '{"licenseKey": "LM-XXXX-XXXX-XXXX", "hardwareId": "dev-01"}'

完整的字段定义请参阅仓库中的《LicenseManager-Platform-API-Spec》。

错误码

错误码HTTP说明处理建议
LICENSE_NOT_FOUND422许可证不存在检查 key 是否输入正确
LICENSE_EXPIRED422许可证已过期引导用户续期
LICENSE_REVOKED422许可证已被吊销联系管理员确认
LICENSE_SUSPENDED422许可证已被挂起联系管理员恢复
ACTIVATION_LIMIT_REACHED422激活席位已满释放闲置设备或扩容
HARDWARE_MISMATCH422设备指纹不匹配重新激活当前设备
rate_limit_exceeded429请求过于频繁按 Retry-After 头退避重试

activate / verify 等业务失败统一返回 HTTP 422,错误信封的 error.message 携带上表中的机读 failureCode;仅认证/权限类错误返回 401/403。

故障排查

激活提示 ACTIVATION_LIMIT_REACHED

登录管理后台查看该许可证的设备列表,释放不再使用的设备;或在后台将设备绑定重置后重新激活。

离线环境验证失败

确认应用内嵌的是当前签发密钥对应的公钥;检查设备系统时间是否准确(证书有效期依赖本地时钟)。

Docker 部署后无法访问管理后台

检查 docker compose logs api 输出,确认数据库迁移已完成;并确认防火墙放行了 8080 / 8443 端口。

仍未解决?发送日志与错误码至 support@licensemanager.cn,专业版用户 24 小时内响应。