Skip to content

Quy chuẩn ký API POST nền tảng VS Open

Phiên bản tài liệu: V1.0
Ngày sửa đổi: 2026-03-16
Phạm vi áp dụng: Mọi API loại POST của nền tảng VS Open

1. Mô tả tài liệu

Tài liệu này định nghĩa quy chuẩn tạo và xác minh chữ ký cho API POST của nền tảng VS Open. Mọi yêu cầu gọi API POST của nền tảng phải tuân thủ quy chuẩn ký trong tài liệu; nếu không, yêu cầu sẽ bị máy chủ từ chối. Tài liệu gồm quy tắc ký cốt lõi, quy trình triển khai chuẩn hóa, ví dụ SDK đa ngôn ngữ và hướng dẫn xử lý ngoại lệ, phục vụ tham khảo khi tích hợp.

2. Định nghĩa thuật ngữ

Thuật ngữĐịnh nghĩa
API KeyĐịnh danh caller do nền tảng cấp, tương ứng biến môi trường VS_OPEN_API_KEY, dùng trong header để nhận diện caller
Secret KeyKhóa ký do nền tảng cấp, tương ứng biến môi trường VS_OPEN_SECRET_KEY, chỉ dùng ký cục bộ, cấm truyền tải
Base URLĐịa chỉ cơ sở cổng API nền tảng, tương ứng biến môi trường VS_OPEN_API_BASE_URL, ví dụ https://api.valuescan.io/api/open/v1
X-TIMESTAMPTimestamp mili giây 13 chữ số, chống replay; máy chủ xác minh thời gian yêu cầu
X-SIGNKết quả chữ ký HMAC-SHA256, chuỗi hex chữ thường, dùng xác minh toàn vẹn yêu cầu
Raw BodyBody gốc của yêu cầu POST, chuỗi chưa qua bất kỳ định dạng, nén hay chỉnh sửa nào
PathĐường dẫn tương đối của API, ví dụ /api/v1/order/create, nối sau Base URL khi sử dụng

3. Mục đích ký

  • Xác thực danh tính: Xác nhận nguồn yêu cầu là caller được nền tảng ủy quyền hợp lệ qua API Key;
  • Chống sửa đổi: Xác minh nội dung body không bị thay đổi độc hại qua chữ ký;
  • Chống replay: Giới hạn thời hạn yêu cầu qua timestamp (cửa sổ xác minh mặc định phía máy chủ là 5 phút);
  • An toàn dữ liệu: Không cần truyền Secret Key, giảm rủi ro lộ khóa.

4. Ràng buộc cốt lõi

  1. Ràng buộc loại yêu cầu: Chỉ áp dụng cho API POST; body chỉ hỗ trợ định dạng Raw (JSON, FormData, v.v. phải tham gia ký dưới dạng chuỗi gốc);
  2. Ràng buộc body: Raw Body tham gia ký phải trùng khớp hoàn toàn với body thực tế gửi đi; cấm bỏ khoảng trắng, xuống dòng, chỉnh thụt lề, sắp xếp lại trường, v.v.;
  3. Ràng buộc timestamp: X-TIMESTAMP phải là timestamp mili giây 13 chữ số, chênh lệch với giờ máy chủ ≤ 5 phút;
  4. Ràng buộc mã hóa: Mọi chuỗi (body, khóa, chuỗi cần ký) dùng mã hóa UTF-8;
  5. Ràng buộc định dạng chữ ký: X-SIGN phải là chuỗi hex chữ thường 64 ký tự do HMAC-SHA256 tạo; cấm chuyển HOA hoặc Base64.

5. Quy trình ký

5.1 Quy trình tổng thể

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 Các bước chi tiết

Bước 1: Chuẩn bị cấu hình

Lấy API Key, Secret Key và Base URL từ console nền tảng VS Open, cấu hình thành biến môi trường hệ thống:

  • Hệ thống 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)"
  • Hệ thống 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)

Bước 2: Tạo timestamp

Tạo timestamp mili giây 13 chữ số của thời điểm hiện tại, ví dụ:

  • Python: str(int(time.time() * 1000))
  • Java: String.valueOf(System.currentTimeMillis())
  • JavaScript: Date.now().toString()

Bước 3: Lấy body gốc

Đọc chuỗi Raw Body gốc của yêu cầu POST; nếu body rỗng thì truyền chuỗi rỗng "".

Bước 4: Ghép chuỗi cần ký

Ghép theo quy tắc cố định, không ký tự phân tách:

待签名字符串 = X-TIMESTAMP + Raw Body

Bước 5: Thực thi chữ ký HMAC-SHA256

Dùng Secret Key làm khóa, thực thi mã hóa HMAC-SHA256 trên chuỗi đã ghép, tạo chuỗi hex chữ thường. Thuật toán xác minh cốt lõi phía máy chủ (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进制小写签名
}

Bước 6: Tạo header yêu cầu

Thêm 3 trường sau vào header POST:

Tên headerNguồn giá trị
X-API-KEYBiến môi trường VS_OPEN_API_KEY
X-TIMESTAMPTimestamp mili giây 13 chữ số tạo ở bước 2
X-SIGNChữ ký hex chữ thường tạo ở bước 5

Bước 7: Gửi yêu cầu

Ghép Base URL + API Path để có địa chỉ yêu cầu đầy đủ, gửi POST kèm header chữ ký và body gốc.

6. SDK tạo header ký và gọi POST

6.1 Python SDK

6.1.1 Mã đầy đủ (gồm 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 Hướng dẫn sử dụng

  1. Phụ thuộc môi trường: Python 3.6+, cần cài thư viện requests (pip install requests);
  2. Cấu hình: Theo mục 5.2.1, cấu hình biến môi trường VS_OPEN_API_KEY, VS_OPEN_SECRET_KEY, VS_OPEN_API_BASE_URL;
  3. Cách gọi:
    • Import lớp VSAPISign;
    • Gọi phương thức send_post_request, truyền đường dẫn tương đối APIbody gốc;
    • Phương thức tự ghép BaseUrl + Path, tạo chữ ký và gửi yêu cầu;
  4. Điều chỉnh tương thích: Sửa Content-Type theo loại body thực tế (ví dụ form dùng application/x-www-form-urlencoded).

6.2 JavaScript/Node.js SDK

6.2.1 Mã đầy đủ (gồm 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 Hướng dẫn sử dụng

  1. Phụ thuộc môi trường: Node.js 16+ (tích hợp sẵn crypto/fetch/url, không cần cài thêm);
  2. Cấu hình:
    • Terminal 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"
    • Dòng lệnh Windows:
      cmd
      set VS_OPEN_API_KEY=你的API Key
      set VS_OPEN_SECRET_KEY=你的Secret Key
      set VS_OPEN_API_BASE_URL=你的Base URL
  3. Cách gọi:
    • Gọi lập trình: import hàm vsPost, truyền đường dẫn tương đối APIdữ liệu yêu cầu;
    • Gọi dòng lệnh (có thể mở rộng): bọc tham số --path/--data dựa trên logic hiện có;
  4. Điều chỉnh tương thích: Sửa Content-Type theo loại body thực tế (ví dụ form dùng application/x-www-form-urlencoded).

7. Ví dụ đầy đủ

7.1 Cấu hình cơ bản

  • 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
  • Body gốc:
    json
    {
        "user_id": "U10001",
        "action": "create_order",
        "params": {"goods_id": "G001", "num": 2}
    }
  • Timestamp đã tạo: 1710585600000

7.2 Tính toán chữ ký

  1. Chuỗi cần ký:
    1710585600000{
        "user_id": "U10001",
        "action": "create_order",
        "params": {"goods_id": "G001", "num": 2}
    }
  2. Thực thi chữ ký HMAC-SHA256 (dùng Secret Key), tạo chữ ký: 5f8d7e6c5b4a32109876543210abcdef5f8d7e6c5b4a32109876543210abcdef (giá trị ví dụ)

7.3 Thông tin yêu cầu đầy đủ

  • URL đầy đủ: https://api.valuescan.io/api/v1/order/create
  • Header:
    http
    X-API-KEY: VS_API_20260316001
    X-TIMESTAMP: 1710585600000
    X-SIGN: 5f8d7e6c5b4a32109876543210abcdef5f8d7e6c5b4a32109876543210abcdef
    Content-Type: application/json; charset=utf-8
  • Body: chuỗi JSON gốc (trùng với rawBody dùng để ký)