VS开放平臺 POST接口簽名规范
文档版本:V1.0 修订日期:2026-03-16 适用范围:VS开放平臺所有 POST 類型 API 接口
1. 文档说明
本文档定义了VS开放平臺POST接口的簽名生成与校验规范,所有调用平臺POST接口的請求必須遵循本规范完成簽名,否则請求将被服务端拒绝。文档包含簽名核心规则、标准化实现流程、多语言SDK程式碼示例及异常排查方案,供开發者对接参考。
2. 术语定义
| 术语 | 定义 |
|---|---|
| API Key | 平臺分配的调用方身份識別,对应環境變數 VS_OPEN_API_KEY,用于請求头識別调用方 |
| Secret Key | 平臺分配的簽名密钥,对应環境變數 VS_OPEN_SECRET_KEY,仅用于本地簽名,禁止传输 |
| Base URL | 平臺API网关基础地址,对应環境變數 VS_OPEN_API_BASE_URL,如https://api.valuescan.io/api/open/v1 |
| X-TIMESTAMP | 13位毫秒级时间戳,用于防重放攻击,服务端校验請求时间有效性 |
| X-SIGN | HMAC-SHA256算法生成的簽名结果,16进制小写字符串,用于驗證請求完整性 |
| Raw Body | POST請求的原始請求體,未经过任何格式化、压缩、修改的字符串 |
| Path | API接口相对路径,如/api/v1/order/create,需拼接Base URL使用 |
3. 簽名目的
- 身份认证:通过API Key确认請求来源为平臺授权的合法调用方;
- 防篡改:通过簽名驗證請求體内容未被恶意修改;
- 防重放:通过时间戳限制請求有效期(服务端預設校验窗口为5分鐘);
- 資料安全:无需传输Secret Key,降低密钥泄露風險。
4. 核心约束
- 請求類型约束:仅适用于POST接口,請求體仅支持Raw格式(如JSON、FormData等需以原始字符串参与簽名);
- 請求體约束:参与簽名的Raw Body必須与实际發送的請求體完全一致,禁止做去空格、换行、缩进调整、欄位重排序等任何修改;
- 时间戳约束:X-TIMESTAMP必須为13位毫秒级时间戳,且与服务端时间差值≤5分鐘;
- 编码约束:所有字符串(請求體、密钥、待簽名字符串)均采用UTF-8编码;
- 簽名格式约束:X-SIGN必須为HMAC-SHA256算法生成的64位16进制小写字符串,禁止转换为大写或Base64格式。
5. 簽名流程
5.1 整體流程
mermaid
flowchart TD
A[取得API Key/Secret Key/Base URL] --> B[生成13位毫秒时间戳]
B --> C[取得POST原始請求體Raw Body]
C --> D[拼接:待簽名字符串=时间戳+Raw Body]
D --> E[使用Secret Key执行HMAC-SHA256簽名]
E --> F[簽名结果转16进制小写字符串]
F --> G[构造請求头:X-API-KEY/X-TIMESTAMP/X-SIGN]
G --> H[拼接Base URL+Path,發送POST請求]5.2 详细步骤
步骤1:准备配置项
从VS开放平臺控制臺取得API Key、Secret Key和Base URL,并配置为系統環境變數:
- Linux/Mac系統:bash
export VS_OPEN_API_KEY="你的API Key" export VS_OPEN_SECRET_KEY="你的Secret Key" export VS_OPEN_API_BASE_URL="你的Base URL(如https://api.valuescan.io/api/open/v1)" - Windows系統:cmd
set VS_OPEN_API_KEY=你的API Key set VS_OPEN_SECRET_KEY=你的Secret Key set VS_OPEN_API_BASE_URL=你的Base URL(如https://api.valuescan.io/api/open/v1)
步骤2:生成时间戳
生成当前时间的13位毫秒级时间戳,示例:
- Python:
str(int(time.time() * 1000)) - Java:
String.valueOf(System.currentTimeMillis()) - JavaScript:
Date.now().toString()
步骤3:取得原始請求體
读取POST請求的Raw Body原始字符串,若請求體为空则传空字符串""。
步骤4:拼接待簽名字符串
按固定规则拼接,无分隔符:
待簽名字符串 = X-TIMESTAMP + Raw Body步骤5:执行HMAC-SHA256簽名
使用Secret Key作为密钥,对拼接后的字符串执行HMAC-SHA256加密,生成16进制小写字符串。 服务端核心校验算法(Java):
java
import cn.hutool.crypto.digest.HMac;
import cn.hutool.crypto.digest.HmacAlgorithm;
import java.nio.charset.StandardCharsets;
public static String hmacSign(String content, String hmacKey) {
if (content == null || content.isEmpty() || hmacKey == null || hmacKey.isEmpty()) {
throw new IllegalArgumentException("簽名内容和密钥不能为空");
}
HMac hMac = new HMac(HmacAlgorithm.HmacSHA256, hmacKey.getBytes(StandardCharsets.UTF_8));
return hMac.digestHex(content); // 生成16进制小写簽名
}步骤6:构造請求头
将以下3个欄位加入POST請求头:
| 請求头名称 | 取值来源 |
|---|---|
| X-API-KEY | 環境變數 VS_OPEN_API_KEY |
| X-TIMESTAMP | 步骤2生成的13位毫秒时间戳 |
| X-SIGN | 步骤5生成的16进制小写簽名 |
步骤7:發送請求
拼接Base URL + API Path得到完整請求地址,携带簽名請求头和原始請求體發送POST請求。
6. 簽名Header生成+POST调用SDK
6.1 Python SDK
6.1.1 完整程式碼(含BaseUrl+Path调用)
python
import os
import hmac
import hashlib
import time
import requests
from urllib.parse import urljoin
# 全局配置 - 从環境變數读取Base URL
BASE_URL = os.getenv("VS_OPEN_API_BASE_URL")
if not BASE_URL:
raise EnvironmentError("環境變數 VS_OPEN_API_BASE_URL 未配置,请先配置平臺基础地址")
class VSAPISign:
"""VS开放平臺POST接口簽名+請求工具類"""
@staticmethod
def get_sign_headers(raw_body: str) -> dict:
"""
生成POST接口簽名請求头
Args:
raw_body: POST原始請求體字符串(保持原始格式,无任何修改)
Returns:
dict: 包含X-API-KEY、X-TIMESTAMP、X-SIGN的請求头字典
Raises:
ValueError: 環境變數未配置API Key/Secret Key时抛出
"""
# 从環境變數读取密钥
api_key = os.getenv("VS_OPEN_API_KEY")
secret_key = os.getenv("VS_OPEN_SECRET_KEY")
# 密钥校验
if not api_key or not secret_key:
raise ValueError(
"環境變數配置异常:未检測到VS_OPEN_API_KEY或VS_OPEN_SECRET_KEY\n"
"Linux/Mac配置命令:\n"
"export VS_OPEN_API_KEY='你的API Key' && export VS_OPEN_SECRET_KEY='你的Secret Key'\n"
"Windows配置命令:\n"
"set VS_OPEN_API_KEY=你的API Key && set VS_OPEN_SECRET_KEY=你的Secret Key"
)
# 生成13位毫秒时间戳
timestamp = str(int(time.time() * 1000))
# 拼接待簽名字符串
sign_content = timestamp + raw_body
# HMAC-SHA256簽名(UTF-8编码),生成16进制小写字符串
hmac_obj = hmac.new(
secret_key.encode("utf-8"),
sign_content.encode("utf-8"),
digestmod=hashlib.sha256
)
sign = hmac_obj.hexdigest()
# 构造簽名請求头
return {
"X-API-KEY": api_key,
"X-TIMESTAMP": timestamp,
"X-SIGN": sign,
"Content-Type": "application/json; charset=utf-8"
}
@staticmethod
def send_post_request(path: str, raw_body: str, timeout: int = 10) -> requests.Response:
"""
拼接BaseUrl+Path,發送带簽名的POST請求
Args:
path: API接口相对路径(如/api/v1/order/create)
raw_body: POST原始請求體字符串
timeout: 請求超时时间(秒),預設10秒
Returns:
requests.Response: 接口回應对象
Raises:
requests.exceptions.RequestException: 請求失败时抛出
"""
# 拼接完整URL(自动处理Path开头是否有/的问题)
full_url = urljoin(BASE_URL, path)
# 生成簽名請求头
headers = VSAPISign.get_sign_headers(raw_body)
# 發送POST請求
response = requests.post(
url=full_url,
headers=headers,
data=raw_body.encode("utf-8"),
timeout=timeout
)
return response
# 调用示例
if __name__ == "__main__":
# 1. API接口相对路径
api_path = "/api/v1/order/create"
# 2. 原始請求體(保持与实际發送的完全一致)
raw_body = """{
"order_no": "ORD20260316001",
"amount": 100.00,
"product_id": "P10001"
}"""
try:
# 3. 發送带簽名的POST請求
response = VSAPISign.send_post_request(api_path, raw_body)
# 4. 处理回應
print(f"回應状態码:{response.status_code}")
print(f"回應内容:{response.text}")
except Exception as e:
print(f"請求失败:{str(e)}")6.1.2 使用说明
- 環境依赖:Python 3.6+,需安装requests程式庫(
pip install requests); - 配置项:按5.2.1节配置
VS_OPEN_API_KEY、VS_OPEN_SECRET_KEY、VS_OPEN_API_BASE_URL環境變數; - 调用方式:
- 导入
VSAPISign類; - 调用
send_post_request方法,传入API相对路径和原始請求體; - 方法自动拼接BaseUrl+Path,生成簽名并發送請求;
- 导入
- 适配调整:根据实际請求體類型修改
Content-Type(如表单請求體改为application/x-www-form-urlencoded)。
6.2 JavaScript/Node.js SDK
6.2.1 完整程式碼(含BaseUrl+Path调用)
javascript
/**
* VS开放平臺POST接口簽名+請求工具(Node.js)
* 依赖:Node.js 16+(内置crypto/fetch/url,无需额外安装依赖)
*/
const crypto = require('crypto');
const process = require('process');
const { URL } = require('url');
// 全局配置 - 从環境變數读取Base URL
const BASE_URL = process.env.VS_OPEN_API_BASE_URL;
if (!BASE_URL) {
throw new Error("環境變數 VS_OPEN_API_BASE_URL 未配置,请先配置平臺基础地址");
}
/**
* 生成符合规范的簽名請求头
* @param {string} rawBody POST原始請求體字符串(保持原始格式)
* @returns {object} 包含X-API-KEY、X-TIMESTAMP、X-SIGN的請求头字典
* @throws {Error} 密钥未配置时抛出异常
*/
function buildSignHeader(rawBody) {
// 从環境變數读取密钥
const apiKey = process.env.VS_OPEN_API_KEY;
const secretKey = process.env.VS_OPEN_SECRET_KEY;
// 密钥校验
if (!apiKey) {
throw new Error('環境變數 VS_OPEN_API_KEY 未配置,请检查配置');
}
if (!secretKey) {
throw new Error('環境變數 VS_OPEN_SECRET_KEY 未配置,请检查配置');
}
// 生成13位毫秒时间戳
const timestamp = Date.now().toString();
// 拼接待簽名字符串(时间戳 + 原始請求體)
const signContent = timestamp + rawBody;
// HMAC-SHA256簽名,生成16进制小写字符串
const hmac = crypto.createHmac('sha256', secretKey);
hmac.update(signContent, 'utf8');
const sign = hmac.digest('hex');
// 构造請求头
return {
'X-API-KEY': apiKey,
'X-TIMESTAMP': timestamp,
'X-SIGN': sign,
'Content-Type': 'application/json; charset=utf-8',
'Accept': '*/*'
};
}
/**
* 拼接BaseUrl+Path,發送带簽名的POST請求
* @param {string} path API接口相对路径(如/api/v1/order/create)
* @param {object|string} data 請求資料(JSON对象/原始字符串)
* @param {number} timeout 超时时间(毫秒),預設10000毫秒
* @returns {Promise<object>} API回應结果(JSON对象)
* @throws {Error} 請求失败/超时/回應异常时抛出
*/
async function vsPost(path, data, timeout = 10000) {
// 統一处理請求體为原始字符串
const rawBody = typeof data === 'object' ? JSON.stringify(data) : data;
// 拼接完整URL(自动处理Path开头是否有/的问题)
const fullUrl = new URL(path, BASE_URL).href;
// 生成簽名請求头
const headers = buildSignHeader(rawBody);
// 配置超时控制器
const controller = new AbortController();
const timeoutId = setTimeout(() => controller.abort(), timeout);
try {
// 發送POST請求
const response = await fetch(fullUrl, {
method: 'POST',
headers: headers,
body: rawBody,
signal: controller.signal
});
clearTimeout(timeoutId);
// 校验HTTP状態码
if (!response.ok) {
throw new Error(`HTTP請求失败,状態码:${response.status},状態信息:${response.statusText}`);
}
// 解析回應结果
return await response.json();
} catch (error) {
clearTimeout(timeoutId);
if (error.name === 'AbortError') {
throw new Error(`請求超时(${timeout}ms):${fullUrl}`);
}
throw new Error(`請求失败:${error.message}`);
}
}
// 模块导出(支持其他文件引用)
module.exports = { buildSignHeader, vsPost };
// 调用示例
if (require.main === module) {
// 1. API接口相对路径
const apiPath = '/api/v1/order/create';
// 2. 請求資料(JSON对象,内部自动转为原始字符串)
const requestData = {
order_no: "ORD20260316001",
amount: 100.00,
product_id: "P10001"
};
// 3. 發送請求
vsPost(apiPath, requestData)
.then(result => {
console.log('請求成功,回應结果:');
console.log(JSON.stringify(result, null, 2));
})
.catch(error => {
console.error('請求失败:', error.message);
});
}6.2.2 使用说明
- 環境依赖:Node.js 16+(内置
crypto/fetch/url,无需额外安装依赖); - 配置项:
- Linux/Mac终端:bash
export VS_OPEN_API_KEY="你的API Key" export VS_OPEN_SECRET_KEY="你的Secret Key" export VS_OPEN_API_BASE_URL="你的Base URL" - Windows命令行:cmd
set VS_OPEN_API_KEY=你的API Key set VS_OPEN_SECRET_KEY=你的Secret Key set VS_OPEN_API_BASE_URL=你的Base URL
- Linux/Mac终端:
- 调用方式:
- 编程式调用:导入
vsPost函数,传入API相对路径和請求資料; - 命令行调用(可扩展):基于现有逻辑封装命令行参数(--path/--data);
- 编程式调用:导入
- 适配调整:根据实际請求體類型修改
Content-Type(如表单請求體改为application/x-www-form-urlencoded)。
7. 完整示例
7.1 基础配置
- Base URL:
https://api.valuescan.io/api/open/v1 - API Key:
VS_API_20260316001 - Secret Key:
VS_SECRET_8e9f7d6c5b4a3210 - API Path:
/api/v1/order/create - 原始請求體:json
{ "user_id": "U10001", "action": "create_order", "params": {"goods_id": "G001", "num": 2} } - 生成的时间戳:
1710585600000
7.2 簽名計算
- 待簽名字符串:
1710585600000{ "user_id": "U10001", "action": "create_order", "params": {"goods_id": "G001", "num": 2} } - 执行HMAC-SHA256簽名(使用Secret Key),生成簽名:
5f8d7e6c5b4a32109876543210abcdef5f8d7e6c5b4a32109876543210abcdef(示例值)
7.3 完整請求信息
- 完整URL:
https://api.valuescan.io/api/v1/order/create - 請求头:http
X-API-KEY: VS_API_20260316001 X-TIMESTAMP: 1710585600000 X-SIGN: 5f8d7e6c5b4a32109876543210abcdef5f8d7e6c5b4a32109876543210abcdef Content-Type: application/json; charset=utf-8 - 請求體:原始JSON字符串(与待簽名的rawBody一致)