增量数据推送(暂未实现)
概述
数据推送是指在某个资源发生变化时,自动向订阅的应用发送变化相关的信息请求的功能。
配置
在开发者后台中,配置应用的订阅的地址、Token 和关注的资源。
- 推送地址:一个可访问的公网地址,推送的信息会通过 HTTP POST 请求发送到该地址。在正常的情况下,接口应返回 200 状态码。
- Token:一个字符串,用于参与验证推送请求的合法性。由开发者自行生成。
- 资源:开发者可以选择关注的资源,比如用户、权限等。
请求参数格式
请求方式:POST
Header 参数
| 参数名称 | 参数类型 | 参数说明 |
|---|---|---|
| X-Nonce | string | 随机字符串,用于签名 |
| X-Timestamp | integer | Unix 时间戳 |
| X-Signature | string | 签名信息 |
| X-School-Id | integer | 学校 ID |
Request Body
Content-Type: application/json
{
"delivery_id": "202401010000000001",
"resource": "user",
"events": [
{
"op": "created|updated|deleted",
"identity": 1,
"timestamp": "2024-01-01 00:00:00"
}
]
}
| 字段名称 | 字段类型 | 字段说明 |
|---|---|---|
| delivery_id | string | 本次推送的唯一 ID,开发者可以根据该 ID 进行幂等处理 |
| resource | string | 资源类型,用于标识本次推送所属的资源 |
| events | array | 事件列表,每个元素代表一条事件 |
| events.op | string | 操作类型,可能的值 updated,created,deleted |
| events.identity | string | 资源标识 |
| events.timestamp | string | 操作时间,格式为 2024-12-31 12:00:00 |
推送示例如下:
POST https://yourdomain.com/
Content-Type: application/json
X-Nonce: 9xmas123
X-Timestamp: 1713153015
X-Signature: xxxx
X-School-Id: 1
{
"delivery_id": "202401010000000001",
"resource": "user",
"events": [
{
"op": "updated",
"identity": "1",
"timestamp": "2024-01-01 00:00:00"
},
{
"op": "created",
"identity": "2",
"timestamp": "2024-01-01 00:00:01"
}
]
}
请求认证
为保证推送请求的合法性,我们在请求中加入了签名信息,开发者可以通过验证签名信息来判断请求的合法性。
签名信息的生成方式如下:
- 创建一个字典,写入
nonce(取自X-Nonce)和timestamp(取自X-Timestamp,转换为整型) - 将 Request Body 解析为字典后,合并(merge)进上述字典
- 对合并后的字典递归地按 key 字典序排序(ksort)
- 将排序后的字典编码成 JSON 字符串
- 使用 HMAC-SHA256 算法,以开发者配置的 Token 为密钥,对 JSON 字符串进行签名
- 将签名结果以十六进制字符串形式填入
X-SignatureHeader
签名示例:
预定义的 Token 值:87892dedaf483eeabed6c54e4335fbe5
收到请求如下:
POST https://yourdomain.com/
Content-Type: application/json
X-Nonce: bfcf312b
X-Timestamp: 1713162332
X-Signature: 74b48b7a98c2fb8acbc99f41582390e98b535a4fa2e1b2fa33a1224aa8ff0220
X-School-Id: 1
{"delivery_id":"202404150000000001","resource":"user","events":[{"op":"created","identity":"1","timestamp":"2024-04-15 14:25:32"}]}
校验签名方式如下:
PHP 版本示例
<?php
$token = '87892dedaf483eeabed6c54e4335fbe5';
$nonce = $_SERVER['HTTP_X_NONCE'];
$timestamp = (int)$_SERVER['HTTP_X_TIMESTAMP'];
$signature = $_SERVER['HTTP_X_SIGNATURE'];
$rawBody = file_get_contents('php://input');
$params = [
'nonce' => $nonce,
'timestamp' => $timestamp,
];
$body = json_decode($rawBody, true);
$merged = array_merge($params, $body);
// 递归 ksort
function ksort_recursive(array &$arr): void {
ksort($arr);
foreach ($arr as &$v) {
if (is_array($v)) ksort_recursive($v);
}
}
ksort_recursive($merged);
$json = json_encode($merged, JSON_UNESCAPED_UNICODE);
$hash = hash_hmac('sha256', $json, $token);
if ($hash === $signature) {
echo '验证成功';
} else {
echo '验证失败';
}
Python 版本示例
import hashlib
import hmac
import json
token = '87892dedaf483eeabed6c54e4335fbe5'
# 从请求 Header 中获取
nonce = 'bfcf312b'
timestamp = 1713162332
signature = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
# 从请求 Body 中读取并解析
raw_body = '{"delivery_id":"202404150000000001","resource":"user","events":[{"op":"created","identity":"1","timestamp":"2024-04-15 14:25:32"}]}'
body = json.loads(raw_body)
params = {
'nonce': nonce,
'timestamp': timestamp,
}
merged = {**params, **body}
def ksort_recursive(obj):
if isinstance(obj, dict):
return {k: ksort_recursive(v) for k, v in sorted(obj.items())}
if isinstance(obj, list):
return [ksort_recursive(i) for i in obj]
return obj
sorted_data = ksort_recursive(merged)
json_data = json.dumps(sorted_data, ensure_ascii=False, separators=(',', ':'))
hash_value = hmac.new(token.encode(), json_data.encode(), hashlib.sha256).hexdigest()
if hash_value == signature:
print('验证成功')
else:
print('验证失败')
响应
推送请求的响应应返回 200 状态码,响应 Body 不作要求。