认证与签名
OpenAPI 模块的所有接口都需要 MD5 token。每个请求必须带三个 HTTP 请求头。
必需请求头
| 请求头 | 说明 |
|---|---|
x-auth-name | WMG 分配的 API 客户端名称 |
x-auth-seed | 13 位毫秒级 UNIX 时间戳(如 1746700000000) |
x-auth-token | MD5 认证 token(公式见下) |
seed
平台时钟运行在新加坡时间(UTC+08:00),x-auth-seed 的时间窗按该时钟校验。
规则
- 13 位数字 UNIX 时间戳,单位是毫秒 —— 不是秒,也不是格式化后的日期字符串。
- 必须与平台时间相差不超过 ±10 分钟,否则请求会被拒绝并返回
x-auth-seed: TIMEOUT。 - 在发起请求前一刻生成,不要在脚本启动时生成一次后复用。
UNIX 时间戳表示的是一个绝对时刻,因此只要你的系统时钟准确、客户端换算正确,无论机器设置成新加坡时区还是其他时区,得到的数值都相同。下表中的每种写法都能得到与新加坡平台时间一致的值。
x-auth-seed | 新加坡时间(UTC+08:00) |
|---|---|
1746700000000 | 2025-05-08 18:26:40 +08:00 |
1753753069000 | 2025-07-29 09:37:49 +08:00 |
各语言生成 seed
| 语言 | 写法 |
|---|---|
| Bash | SEED=$(date +%s%3N) |
| PowerShell | $SEED = [string]([DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()) |
| Python | seed = str(int(time.time() * 1000)) |
| Node.js | const seed = String(Date.now()); |
| PHP | $SEED = (string)(time() * 1000); |
PowerShell:禁止使用 Get-Date -UFormat %s
这个写法返回的是 10 位秒级值,会被拒绝并返回 x-auth-seed: LENGTH MUST BE 13 DIGITS。在 Windows PowerShell 5.1 下它还是按本地时间换算的,机器设为新加坡时区时会超前 28800 秒(8 小时)。
# Windows PowerShell 5.1,机器时区为 Singapore Standard Time
Get-Date -UFormat %s # 1785149525 <- 秒级,且超前 8 小时
[DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds() # 1785120724000 <- 正确PowerShell 7 下时刻是正确的,所以时钟问题只有在同一份脚本跑到 5.1 上时才暴露;但位数在两个版本下都是错的。请一律使用 [DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds()。
Token 公式
x-auth-token = md5( strtolower(api_name) + api_key + seed )结果为 32 位小写十六进制字符串。请求参数不参与 token 计算。
分步说明
- API name 转小写 —— 把
x-auth-name转为小写。 - 拼接 小写后的名称、你的
api_key、seed字符串 —— 中间不加任何分隔符。 - MD5 哈希 对拼接后字符串的 UTF-8 字节做 MD5。
- 十六进制编码 把原始二进制哈希转成 32 位小写十六进制字符串。
- 把结果放入
x-auth-token请求头。
注意: 拼接时 seed 必须是字符串,不要先转成整数。MD5 的输入是拼接后字符串的原始 UTF-8 字节。
参考实现
Bash
API_NAME="your_api_name"
API_KEY="your_api_key"
SEED=$(date +%s%3N)
TOKEN=$(printf '%s' "$(echo -n "$API_NAME" | tr '[:upper:]' '[:lower:]')${API_KEY}${SEED}" | md5sum | cut -d' ' -f1)
curl -G "https://api.test.wmgdelivery.com/v1/openapi/station/get-stations" \
-H "x-auth-name: $API_NAME" \
-H "x-auth-seed: $SEED" \
-H "x-auth-token: $TOKEN"Windows PowerShell
$API_NAME = "your_api_name"
$API_KEY = "your_api_key"
$SEED = [string]([DateTimeOffset]::UtcNow.ToUnixTimeMilliseconds())
$raw = [System.Text.Encoding]::UTF8.GetBytes($API_NAME.ToLower() + $API_KEY + $SEED)
$md5 = [System.Security.Cryptography.MD5]::Create().ComputeHash($raw)
$TOKEN = -join ($md5 | ForEach-Object { $_.ToString("x2") })
$headers = @{
"x-auth-name" = $API_NAME
"x-auth-seed" = $SEED
"x-auth-token" = $TOKEN
}
$response = Invoke-RestMethod -Uri "https://api.test.wmgdelivery.com/v1/openapi/station/get-stations" -Method Get -Headers $headers
$response | ConvertTo-Json -Depth 10Python
import time, hashlib
import requests
API_NAME = 'your_api_name'
API_KEY = 'your_api_key'
def build_token(api_name: str, api_key: str, seed: str) -> str:
"""seed 必须是毫秒时间戳的字符串形式 —— 不要转成 int。"""
raw = api_name.lower() + api_key + seed
return hashlib.md5(raw.encode()).hexdigest()
seed = str(int(time.time() * 1000))
token = build_token(API_NAME, API_KEY, seed)
resp = requests.get(
'https://api.test.wmgdelivery.com/v1/openapi/station/get-stations',
headers={
'x-auth-name': API_NAME,
'x-auth-seed': seed,
'x-auth-token': token,
},
)
print(resp.json())Node.js / TypeScript
仅使用内置的 node:crypto 模块和全局 fetch(Node.js 18+)。
import crypto from 'node:crypto';
const API_NAME = 'your_api_name';
const API_KEY = 'your_api_key';
function buildToken(apiName: string, apiKey: string, seed: string): string {
// seed 必须保持字符串;拼接后按原始 UTF-8 字节计算
return crypto.createHash('md5')
.update(apiName.toLowerCase() + apiKey + seed)
.digest('hex');
}
const seed = String(Date.now());
const token = buildToken(API_NAME, API_KEY, seed);
const resp = await fetch('https://api.test.wmgdelivery.com/v1/openapi/station/get-stations', {
headers: {
'x-auth-name': API_NAME,
'x-auth-seed': seed,
'x-auth-token': token,
},
});
console.log(JSON.stringify(await resp.json(), null, 2));PHP
<?php
function build_token(string $api_name, string $api_key, string $seed): string
{
return md5(strtolower($api_name) . $api_key . $seed);
}
$API_NAME = 'your_api_name';
$API_KEY = 'your_api_key';
$SEED = (string)(time() * 1000); // 13 位毫秒时间戳
$TOKEN = build_token($API_NAME, $API_KEY, $SEED);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.test.wmgdelivery.com/v1/openapi/station/get-stations');
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'x-auth-name: ' . $API_NAME,
'x-auth-seed: ' . $SEED,
'x-auth-token: ' . $TOKEN,
]);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_TIMEOUT, 10);
$response = curl_exec($ch);
curl_close($ch);
echo json_encode(json_decode($response, true), JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE) . "\n";
?>验证样例
输入
API 凭证:
api_name = "Demo_Client"
api_key = "abc"seed(毫秒时间戳):
seed = "1746700000000"计算
$token = build_token('Demo_Client', 'abc', '1746700000000');结果
拼接后的字符串:
demo_clientabc1746700000000MD5 结果:
6e7190914323a4b7e2f94ba798a3f015这个结果是可复现的 —— 把同样这三个输入(Demo_Client、abc、1746700000000)交给任何 MD5 实现,都必然得到这个值。正式对接前请先用它验证你的实现。
样例分步拆解
1. API name 转小写
"Demo_Client" → "demo_client"2. 拼接
"demo_client" + "abc" + "1746700000000"
= "demo_clientabc1746700000000"3. MD5 哈希
对拼接后字符串的 UTF-8 字节计算 MD5(二进制输出),再转十六进制:
md5("demo_clientabc1746700000000") → 6e7190914323a4b7e2f94ba798a3f015在请求中使用
token 生成后,按如下方式带入请求:
- 把 API name 放入
x-auth-name请求头 - 把 seed 放入
x-auth-seed请求头 - 把计算出的 token 放入
x-auth-token请求头
示例:
x-auth-name: Demo_Client
x-auth-seed: 1746700000000
x-auth-token: 6e7190914323a4b7e2f94ba798a3f015排错
| 现象 | 常见原因 |
|---|---|
x-auth-token: INVALID | 拼接前没有把 API name 转小写;用错了 key 或 seed |
x-auth-seed: TIMEOUT | seed 在脚本启动时生成后复用了,没有在发请求前一刻生成;客户端时钟漂移;或在 Windows PowerShell 5.1 上用了 Get-Date -UFormat %s(见 seed) |
x-auth-seed: LENGTH MUST BE 13 DIGITS | 传了秒级时间戳(10 位)而不是毫秒 —— 需要乘以 1000,见 seed |
认证错误消息
| 错误消息 | 原因 |
|---|---|
x-auth-seed: FORMAT ERROR | seed 不是数字 |
x-auth-seed: LENGTH MUST BE 13 DIGITS | seed 不是恰好 13 位 |
x-auth-seed: TIMEOUT | seed 与平台时间相差超过 10 分钟 |
x-auth-name: INVALID | api_name 不存在或账号已停用 |
x-auth-token: INVALID | token 不匹配 |
标准响应结构
所有接口都返回如下结构的 JSON:
{
"code": 0,
"message": "success",
"data": {}
}| 字段 | 类型 | 说明 |
|---|---|---|
code | integer | 0 = 成功,1 = 失败 |
message | string | 可读的结果描述 |
data | object/array | 响应数据;出错时为 [] |
未预期的服务端异常同样返回 code: 1,message 为 something error。