Files
CKB-Interface/docs/open-api-sign.md
2026-03-24 10:39:16 +08:00

215 lines
6.6 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 存客宝开放 API — 鉴权规范V1
> 适用接口:`/v1/open/*`
---
## 一、整体流程
```
第一步 POST /v1/open/auth/token
携带apiKey + account + timestamp + sign
服务端验签后返回 JWT Token有效期 2 小时)
第二步 POST /v1/open/scenarios (或其他业务接口)
Header: Authorization: Bearer <token>
无需再传 apiKey / sign与存客宝内部接口完全兼容
```
---
## 二、API Key 说明
| 属性 | 描述 |
|--------------|----------------------------------------------------------------|
| **颁发对象** | 每个存客宝账号(`ck_users`)一把专属 API Key |
| **格式** | `5 组 × 5 位`,大小写字母 + 数字,组间以 `-` 分隔 |
| **示例** | `aB3k9-Z8c1Q-0f4Xk-M9n2P-1A2b3` |
| **获取方式** | 门店端 → 用户中心 → 对外接口 → 查看/生成 API Key |
| **有效期** | 永久有效,可随时点击"重新生成"覆盖旧 Key旧 Key 立即失效) |
---
## 三、第一步:获取 JWT Token
### 接口
```
POST /v1/open/auth/token
Content-Type: application/json
```
### 请求参数
| 字段 | 类型 | 必填 | 说明 |
|-------------|--------|------|---------------------------------------------------|
| `apiKey` | string | 是 | 账号专属 API Key |
| `account` | string | 是 | 登录账号(`ck_users.account` |
| `timestamp` | int | 是 | 当前秒级 Unix 时间戳(与服务器时差不超过 5 分钟)|
| `sign` | string | 是 | 签名值,生成方式见下方 |
### 签名算法
只有三个固定字段参与签名,业务参数不参与:
```
stringToSign = account值 + timestamp值 ← 字段名 ASCII 升序,直接拼接值
firstMd5 = MD5(stringToSign)
sign = MD5(firstMd5 + apiKey)
```
> `account` < `timestamp`ASCII 升序),所以拼接顺序固定为:`account值 + timestamp值`
### 成功响应
```json
{
"code": 200,
"message": "success",
"data": {
"token": "eyJ...",
"expires_in": 7200
}
}
```
### 常见错误
| code | message | 原因 |
|------|-----------------|----------------------------------------|
| 400 | apiKey不能为空 | 未传 `apiKey` |
| 400 | account不能为空 | 未传 `account` |
| 400 | sign不能为空 | 未传 `sign` |
| 400 | timestamp不能为空 | 未传 `timestamp` |
| 400 | 请求已过期 | `timestamp` 与服务器时差超 5 分钟 |
| 401 | 无效的apiKey | Key 不存在、账号不匹配或账号已禁用 |
| 401 | 签名验证失败 | account / timestamp / apiKey 值有误 |
---
## 四、第二步:调用业务接口
拿到 Token 后,所有 `/v1/open/*` 接口在 HTTP Header 中携带:
```
Authorization: Bearer <token>
```
不再需要 `apiKey``sign``timestamp` 参数。
Token 过期2 小时)后重新请求 `/v1/open/auth/token` 换新 Token。
---
## 五、示例代码
### PHP
```php
$apiKey = 'aB3k9-Z8c1Q-0f4Xk-M9n2P-1A2b3';
$account = 'user001';
$timestamp = (string)time();
// 生成签名
$stringToSign = $account . $timestamp; // account < timestampASCII
$firstMd5 = md5($stringToSign);
$sign = md5($firstMd5 . $apiKey);
// 获取 Token
$response = file_get_contents('https://ckbapi.quwanzhi.com/v1/open/auth/token', false,
stream_context_create(['http' => [
'method' => 'POST',
'header' => 'Content-Type: application/json',
'content' => json_encode(compact('apiKey', 'account', 'timestamp', 'sign')),
]])
);
$token = json_decode($response, true)['data']['token'];
// 调用业务接口
$response2 = file_get_contents('https://ckbapi.quwanzhi.com/v1/open/scenarios', false,
stream_context_create(['http' => [
'method' => 'POST',
'header' => "Authorization: Bearer {$token}\r\nContent-Type: application/json",
'content' => json_encode([
'planId' => 42,
'phone' => '13800000000',
'name' => '张三',
'source' => '百度推广',
]),
]])
);
```
### JavaScript / Node.js
```javascript
const crypto = require('crypto');
const apiKey = 'aB3k9-Z8c1Q-0f4Xk-M9n2P-1A2b3';
const account = 'user001';
const timestamp = String(Math.floor(Date.now() / 1000));
// 签名
const firstMd5 = crypto.createHash('md5').update(account + timestamp, 'utf8').digest('hex');
const sign = crypto.createHash('md5').update(firstMd5 + apiKey, 'utf8').digest('hex');
// 获取 Token
const res = await fetch('https://ckbapi.quwanzhi.com/v1/open/auth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ apiKey, account, timestamp, sign }),
});
const { token } = (await res.json()).data;
// 调用业务接口
const res2 = await fetch('https://ckbapi.quwanzhi.com/v1/open/scenarios', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${token}` },
body: JSON.stringify({ planId: 42, phone: '13800000000', name: '张三' }),
});
```
### Python
```python
import hashlib, time, requests
api_key = 'aB3k9-Z8c1Q-0f4Xk-M9n2P-1A2b3'
account = 'user001'
timestamp = str(int(time.time()))
# 签名
first_md5 = hashlib.md5((account + timestamp).encode()).hexdigest()
sign = hashlib.md5((first_md5 + api_key).encode()).hexdigest()
# 获取 Token
r = requests.post('https://ckbapi.quwanzhi.com/v1/open/auth/token',
json={'apiKey': api_key, 'account': account,
'timestamp': timestamp, 'sign': sign})
token = r.json()['data']['token']
# 调用业务接口
r2 = requests.post('https://ckbapi.quwanzhi.com/v1/open/scenarios',
headers={'Authorization': f'Bearer {token}'},
json={'planId': 42, 'phone': '13800000000', 'name': '张三'})
```
---
## 六、签名自测用例
| 字段 | 值 |
|-----------|------------------------------------|
| apiKey | `TestKey-12345-ABCDE-67890-xYzWv` |
| account | `user001` |
| timestamp | `1710000000` |
计算过程:
```
stringToSign = "user001" + "1710000000" = "user0011710000000"
firstMd5 = MD5("user0011710000000")
sign = MD5(firstMd5 + "TestKey-12345-ABCDE-67890-xYzWv")
```