DEVELOPER GUIDE
客户端接入文档
ThinkPHP 6 服务端 · HMAC-SHA256 请求与响应签名 · UTF-8 JSON
1. 接入准备
在管理后台「产品管理」中创建产品,取得 app_key 与 app_secret。app_secret 只应保存在你的客户端安全区域或服务端,不要上传到公开仓库。
- 接口根地址
- https://sq.yuanan6.cn/api/v1
- 请求方式
- POST,application/x-www-form-urlencoded
- 响应格式
- JSON,业务处理成功时 code = 0
- 时间窗口
- 默认 300 秒,客户端需通过 NTP 校准时间
2. 签名规则
- 移除
sign、sign_type以及空值参数。 - 按参数名 ASCII 升序排序。
- 键和值分别执行 RFC 3986
rawurlencode,以key=value和&拼接。 - 以
app_secret为密钥计算HMAC-SHA256,输出小写十六进制。
$sign = hash_hmac('sha256', canonical($params), $appSecret);所有接口还必须包含 timestamp(秒级 Unix 时间戳)和 nonce(8-64 位随机字符串)。相同 nonce 在时间窗内不能重复使用。
响应中的 sign 计算方式:移除 sign 后,将其余响应字段按原顺序用 JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES 编码,再计算 HMAC-SHA256。
3. PHP 7.4 完整示例
示例激活后执行一次验证。其他语言按相同签名规则实现即可。
<?php
$baseUrl = 'https://sq.yuanan6.cn';
$appKey = '你的 app_key';
$appSecret = '你的 app_secret';
$licenseKey = 'XL-XXXXX-XXXXX-XXXXX-XXXXX';
function canonical(array $params): string {
unset($params['sign'], $params['sign_type']);
$params = array_filter($params, function ($value) {
return is_scalar($value) && $value !== '';
});
ksort($params, SORT_STRING);
$pairs = [];
foreach ($params as $key => $value) {
$pairs[] = rawurlencode((string) $key) . '=' . rawurlencode((string) $value);
}
return implode('&', $pairs);
}
function apiRequest($baseUrl, $endpoint, $appKey, $appSecret, array $data) {
$params = array_merge($data, [
'app_key' => $appKey,
'timestamp' => time(),
'nonce' => bin2hex(random_bytes(16)),
]);
$params['sign'] = hash_hmac('sha256', canonical($params), $appSecret);
$ch = curl_init($baseUrl . '/api/v1/' . $endpoint);
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => http_build_query($params),
CURLOPT_RETURNTRANSFER => true,
CURLOPT_CONNECTTIMEOUT => 5,
CURLOPT_TIMEOUT => 15,
]);
$raw = curl_exec($ch);
if ($raw === false) {
throw new RuntimeException(curl_error($ch));
}
curl_close($ch);
$response = json_decode($raw, true);
if (!is_array($response) || !isset($response['sign'])) {
throw new RuntimeException('响应格式或签名缺失');
}
$sign = $response['sign'];
unset($response['sign']);
$json = json_encode($response, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES);
if (!hash_equals(hash_hmac('sha256', $json, $appSecret), $sign)) {
throw new RuntimeException('响应验签失败');
}
return $response;
}
// 设备指纹应由客户端稳定生成,不要使用 IP 或随机值。
$deviceId = hash('sha256', '稳定的设备特征');
$result = apiRequest($baseUrl, 'activate', $appKey, $appSecret, [
'license_key' => $licenseKey,
'device_id' => $deviceId,
]);
if ($result['code'] !== 0) {
exit($result['message']);
}
print_r($result['data']);4. 接口列表
| 接口 | 路径 | 业务参数 | 用途 |
|---|---|---|---|
| POST | /api/v1/config | 仅通用参数 | 获取版本、更新地址和公告 |
| POST | /api/v1/activate | license_key, device_id / domain | 首次激活并开始计时,重复激活幂等 |
| POST | /api/v1/verify | license_key, device_id / domain | 验证授权、到期与绑定状态 |
| POST | /api/v1/heartbeat | 同 verify | 验证并刷新绑定在线状态 |
| POST | /api/v1/deactivate | license_key, binding_type, identifier | 解绑设备或域名,消耗解绑次数 |
binding_type 为 device 或 domain;identifier 为对应原始 device_id 或 domain。解绑后再次激活不会重复占用历史绑定。
5. 通用参数
| 参数 | 必填 | 说明 |
|---|---|---|
| app_key | 是 | 产品公开接入标识 |
| timestamp | 是 | 秒级 Unix 时间戳 |
| nonce | 是 | 每次请求不同的 8-64 位随机字符串 |
| sign | 是 | HMAC-SHA256 小写签名 |
| device_id | 按产品 | 8-128 位字母、数字或 _ . : - |
| domain | 按产品 | 纯域名或 IP,可传 URL 由服务端提取主机名 |
6. 常见状态码
| 0 | 处理成功 |
| 40001 / 40010 | 缺少通用参数或绑定参数 |
| 40101 / 40102 / 40103 | 签名错误、时间过期、重复请求 |
| 40302 / 40303 | 授权冻结或产品停用 |
| 40401 | 授权不存在或不属于产品 |
| 41001 / 41003 | 授权到期或尚未激活 |
| 41004 / 41005 / 41008 | 绑定不匹配、达到上限、解绑次数用完 |
| 42901 / 50000 | 请求限流或服务暂时不可用 |
7. 生产接入建议
- 全站启用 HTTPS;客户端设置连接与读取超时,不要关闭 TLS 证书校验。
- 心跳间隔使用响应中的 heartbeat_after,不要高频请求。
- 验证响应签名与 product 字段,防止伪造或串用产品响应。
- 设备指纹只保存稳定哈希,不采集无关隐私信息;解绑属于敏感操作,应由用户主动确认。
- 网络不可用时采用适合你产品的有限宽限策略,并在恢复联网后立即验证,不要将异常一律视为授权有效。
- 保存 request_id 用于售后定位,不要记录 app_secret 或完整支付通知密钥。