# SDK 使用说明
## 服务地址与机器码
初始化 SDK 时传入服务端基础地址和机器码。机器码要求为 32 位字母数字字符串,SDK 会自动去除首尾空格并转换为大写。
客户端应保存同一台设备的机器码。授权票据绑定卡密和机器码,换用其他机器码会导致验签失败。
## 接口流程
1. 调用登录或验证接口 `/api/card/verify`。
2. 保存响应中的 `licenseTicket`、`expireUnix` 和 `remainDays`。
3. 按业务周期调用 `/api/card/heartbeat` 更新在线状态。
4. 需要展示设备时调用 `/api/card/devices`。
5. 用户主动解绑时调用 `/api/card/unbind`。
## Python
文件:`bindings/python/license_sdk.py`
```python
from license_sdk import LicenseClient
client = LicenseClient('https://license.example.com', '0123456789abcdef0123456789abcdef')
login = client.login('CARD_KEY', '办公电脑')
if login.get('ok'):
print(login['licenseTicket'])
heartbeat = client.heartbeat('CARD_KEY')
devices = client.list_devices('CARD_KEY')
client.unbind('CARD_KEY')
```
## C#
文件:`bindings/csharp/LicenseClient.cs`,工程 `bindings/csharp/LicenseSdk.csproj`,目标 .NET 6,输出程序集名 `LicenseSdk`。
完整使用说明见 [C#/.NET DLL 使用说明](dotnet-sdk-usage.md)。
编译 DLL:
```bash
cd bindings/csharp && dotnet build -c Release
# 产物:bindings/csharp/bin/Release/net6.0/LicenseSdk.dll
```
引用方式三选一:
- 工程引用:在目标 .csproj 中加 ``;
- 直接引用 DLL:`.../LicenseSdk.dll`;
- NuGet:`dotnet pack` 生成 `LicenseSdk.5.0.0.nupkg` 后本地源安装。
SDK 提供两套等价方法:`LoginAsync` / `HeartbeatAsync` / `ListDevicesAsync` / `UnbindAsync` 返回 JSON 文档(非 2xx 抛异常);带 `ResponseAsync` 后缀的同名方法返回 `ApiResponse`(含 HTTP 状态码与原始响应体,不抛异常,便于展示服务端业务错误)。
```csharp
using System.Net.Http;
using LicenseSdk;
using var http = new HttpClient { BaseAddress = new Uri("https://license.example.com") };
var client = new LicenseClient(http, "0123456789abcdef0123456789abcdef");
var result = await client.LoginAsync("CARD_KEY", "办公电脑");
var heartbeat = await client.HeartbeatAsync("CARD_KEY");
// 需要读取业务错误原因时使用 ResponseAsync 系列
var resp = await client.LoginResponseAsync("CARD_KEY", "办公电脑");
if (resp.OkFlag) { /* resp.Doc 含 licenseTicket / expireUnix / remainDays */ }
```
## Java
文件:`bindings/java/LicenseClient.java`,要求 Java 11 或更高版本。
```java
import license.sdk.LicenseClient;
LicenseClient client = new LicenseClient(
"https://license.example.com",
"0123456789abcdef0123456789abcdef"
);
LicenseClient.ApiResponse login = client.loginResponse("CARD_KEY", "办公电脑");
if (login.statusCode() == 200) {
System.out.println(login.body());
}
LicenseClient.ApiResponse heartbeat = client.heartbeatResponse("CARD_KEY");
```
Java SDK 的原有方法返回 JSON 字符串;带 `Response` 后缀的方法返回 HTTP 状态码和 JSON 响应体。接入方负责解析 `ok`、`licenseTicket`、`expireUnix` 和 `remainDays`。
## C++
文件:`bindings/cpp/license_client.hpp` 和 `sdk-core/include/license_sdk.h`。
```cpp
#include "license_client.hpp"
license_sdk::LicenseClient client("0123456789abcdef0123456789abcdef");
int status = 0;
int64_t expire_unix = 0;
char message[256] = {};
client.login("CARD_KEY", "办公电脑", &status, &expire_unix, message, sizeof(message));
```
C++ 封装与底层 C ABI 当前定义了登录、设备列表和心跳方法。C ABI 的 HTTP 传输由平台绑定接入;接入前应实现 `sdk-core/src/card_flow.c` 中的传输层并传入当前设备的机器码。Python、C# 和 Java 绑定可直接调用 HTTP API。
## 错误处理
客户端应检查响应中的 `ok` 字段和 HTTP 状态。心跳返回 `kick: true` 时,应清理本地授权状态并要求用户重新验证。常见原因包括卡密过期、卡密冻结、设备被解绑或设备被新设备顶替。