概述
V2 是 VMOSCloud OpenAPI 的简化签名方案,与原 V4(HMAC-SHA256 派生密钥)方案并存,AK / SK 共用。
已经接入 V4 的客户无需迁移,老 SDK 长期保留。新对接客户建议直接使用 V2:3 个 Header + 1 行 SHA-256 即可完成签名,不需要派生密钥、不需要重排 JSON、不需要 UTC 时间格式化。
获取账号 (AK/SK)
前置准备
在开始使用 API 前,需要获取 Access Key ID 和 Secret Access Key,用于 API 请求鉴权。
操作步骤:
- 登录 VMOSCloud 平台
- 导航至菜单:开发者 -> API
- 复制对应的密钥信息:
AccessKeyID(即 AK)SecretAccessKey(即 SK)
请求 Header
每次请求 Headers 中必须包含以下 3 个鉴权参数:
| 参数名 | 类型 | 必填 | 参数描述 | 示例值 |
|---|---|---|---|---|
X-Access-Key | string | 必须 | Access Key ID 明文 | ak_xxxxxxxxxxxx |
X-Timestamp | string | 必须 | unix 秒级时间戳(10 位整数字符串),±5 分钟窗 | 1747555200 |
X-Sign | string | 必须 | 64 位小写 hex 签名 | 6069dfb4cb3ac57b… |
Content-Type | string | POST必须 | 资源 MIME 类型 | application/json |
签名算法
直接字符串拼接,无分隔符:
signString = SK + X-Timestamp + path + bodyOrQuery
X-Sign = lowerHex( SHA-256( signString_UTF8 ) )
| 字段 | 取值规则 |
|---|---|
SK | Secret Access Key 明文 |
X-Timestamp | 与请求头一致的 10 位 unix 秒(毫秒级会被判过期) |
path | 含 servlet 前缀的完整路径,例如 /vcpcloud/api/padApi/padInfo |
bodyOrQuery | 见下表 |
bodyOrQuery 取值规则
| 请求类型 | bodyOrQuery |
|---|---|
GET | 原始 query string,例如 startDate=2026-05-01&endDate=2026-05-31;无参则空串 |
POST / PUT(JSON) | 原始 body(发什么签什么,不要重排、不要去空格) |
文件上传接口 /uploadFile / /asyncCmd / /syncCmd | 空串(文件 body 不参与签名) |
query string 的参数顺序与 URL 编码必须与实际发送的完全一致,签名前不要重排、不要二次转码。
签名示例
输入:
SK = 9cucpjoyn4xxmkhj3q9el3ce
ts = 1747555200
path = /vcpcloud/api/padApi/padInfo
body = {"padCode":"AC32010601132"}
拼接后的 signString:
9cucpjoyn4xxmkhj3q9el3ce1747555200/vcpcloud/api/padApi/padInfo{"padCode":"AC32010601132"}
X-Sign = lowerHex( SHA-256( signString ) ),可以用任意语言的标准 SHA-256 库验证客户端实现是否正确。
错误码
| code | 含义 |
|---|---|
2019 | 验证签名失败 |
2031 | 无效的密钥(AccessKey 找不到) |
2032 | 请求头缺必要参数 |
2033 | 时间戳过期或格式错误 |
调用示例
curl
TS=$(date +%s)
AK="ak_xxxxxxxxxxxx"
SK="sk_xxxxxxxxxxxx"
URI="/vcpcloud/api/padApi/padInfo"
BODY='{"padCode":"AC32010601132"}'
SIGN=$(printf "%s" "${SK}${TS}${URI}${BODY}" | openssl dgst -sha256 -hex | awk '{print $2}')
curl -X POST "https://api.vmoscloud.com${URI}" \
-H "X-Access-Key: ${AK}" \
-H "X-Timestamp: ${TS}" \
-H "X-Sign: ${SIGN}" \
-H "Content-Type: application/json" \
--data-binary "${BODY}"
java
import com.alibaba.fastjson.JSONObject;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.util.Map;
public class V2ApiRequestUtils {
private static final String BASE_URL = "https://api.vmoscloud.com";
private static final String ACCESS_KEY = "Access Key ID";
private static final String SECRET_ACCESS_KEY = "Secret Access Key";
public static String sendPostRequest(String path, JSONObject params) {
try {
String body = params == null ? "" : params.toJSONString();
String ts = String.valueOf(System.currentTimeMillis() / 1000);
HttpPost req = new HttpPost(BASE_URL + path);
req.setHeader("X-Access-Key", ACCESS_KEY);
req.setHeader("X-Timestamp", ts);
req.setHeader("X-Sign", sign(ts, path, body));
req.setHeader("Content-Type", "application/json");
req.setEntity(new StringEntity(body, StandardCharsets.UTF_8));
try (CloseableHttpClient client = HttpClients.createDefault();
CloseableHttpResponse resp = client.execute(req)) {
return EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8);
}
} catch (Exception e) {
throw new RuntimeException("V2 POST failed: " + path, e);
}
}
public static String sendGetRequest(String path, JSONObject params) {
try {
String query = buildQuery(params);
String ts = String.valueOf(System.currentTimeMillis() / 1000);
HttpGet req = new HttpGet(BASE_URL + path + (query.isEmpty() ? "" : "?" + query));
req.setHeader("X-Access-Key", ACCESS_KEY);
req.setHeader("X-Timestamp", ts);
req.setHeader("X-Sign", sign(ts, path, query));
try (CloseableHttpClient client = HttpClients.createDefault();
CloseableHttpResponse resp = client.execute(req)) {
return EntityUtils.toString(resp.getEntity(), StandardCharsets.UTF_8);
}
} catch (Exception e) {
throw new RuntimeException("V2 GET failed: " + path, e);
}
}
private static String sign(String ts, String path, String body) throws Exception {
String signString = SECRET_ACCESS_KEY + ts + path + body;
byte[] raw = MessageDigest.getInstance("SHA-256")
.digest(signString.getBytes(StandardCharsets.UTF_8));
return String.format("%064x", new BigInteger(1, raw));
}
private static String buildQuery(JSONObject params) {
if (params == null || params.isEmpty()) return "";
StringBuilder sb = new StringBuilder();
for (Map.Entry<String, Object> e : params.entrySet()) {
if (sb.length() > 0) sb.append('&');
sb.append(e.getKey()).append('=').append(e.getValue());
}
return sb.toString();
}
public static void main(String[] args) {
// POST 示例
JSONObject body = new JSONObject();
body.put("padCode", "AC32010601132");
System.out.println(sendPostRequest("/vcpcloud/api/padApi/padInfo", body));
// GET 示例
JSONObject query = new JSONObject();
query.put("startDate", "2026-05-01");
query.put("endDate", "2026-05-31");
System.out.println(sendGetRequest("/vcpcloud/api/padApi/getOrderEquipmentList", query));
}
}
python
import hashlib
import time
import json
import requests
BASE_URL = "https://api.vmoscloud.com"
ACCESS_KEY = "Access Key ID"
SECRET_ACCESS_KEY = "Secret Access Key"
def _sign(ts: str, path: str, body_or_query: str) -> str:
return hashlib.sha256(
(SECRET_ACCESS_KEY + ts + path + body_or_query).encode("utf-8")
).hexdigest()
def send_post(path: str, params: dict | None = None) -> str:
body = json.dumps(params, separators=(",", ":"), ensure_ascii=False) if params else ""
ts = str(int(time.time()))
headers = {
"X-Access-Key": ACCESS_KEY,
"X-Timestamp": ts,
"X-Sign": _sign(ts, path, body),
"Content-Type": "application/json",
}
# 用 data=bytes 避免 requests 改写 body
resp = requests.post(BASE_URL + path, headers=headers, data=body.encode("utf-8"))
return resp.text
def send_get(path: str, params: dict | None = None) -> str:
query = "&".join(f"{k}={v}" for k, v in params.items()) if params else ""
ts = str(int(time.time()))
url = BASE_URL + path + (f"?{query}" if query else "")
headers = {
"X-Access-Key": ACCESS_KEY,
"X-Timestamp": ts,
"X-Sign": _sign(ts, path, query),
}
return requests.get(url, headers=headers).text
if __name__ == "__main__":
# POST
print(send_post("/vcpcloud/api/padApi/padInfo", {"padCode": "AC32010601132"}))
# GET
print(send_get("/vcpcloud/api/padApi/getOrderEquipmentList",
{"startDate": "2026-05-01", "endDate": "2026-05-31"}))
node
const crypto = require("crypto");
const BASE_URL = "https://api.vmoscloud.com";
const ACCESS_KEY = "Access Key ID";
const SECRET_ACCESS_KEY = "Secret Access Key";
function sign(ts, path, bodyOrQuery) {
return crypto
.createHash("sha256")
.update(SECRET_ACCESS_KEY + ts + path + bodyOrQuery, "utf8")
.digest("hex");
}
async function sendPost(path, params) {
const body = params ? JSON.stringify(params) : "";
const ts = Math.floor(Date.now() / 1000).toString();
const resp = await fetch(BASE_URL + path, {
method: "POST",
headers: {
"X-Access-Key": ACCESS_KEY,
"X-Timestamp": ts,
"X-Sign": sign(ts, path, body),
"Content-Type": "application/json",
},
body,
});
return resp.text();
}
async function sendGet(path, params) {
const query = params
? Object.entries(params).map(([k, v]) => `${k}=${v}`).join("&")
: "";
const ts = Math.floor(Date.now() / 1000).toString();
const resp = await fetch(BASE_URL + path + (query ? `?${query}` : ""), {
headers: {
"X-Access-Key": ACCESS_KEY,
"X-Timestamp": ts,
"X-Sign": sign(ts, path, query),
},
});
return resp.text();
}
(async () => {
// POST
console.log(await sendPost("/vcpcloud/api/padApi/padInfo",
{ padCode: "AC32010601132" }));
// GET
console.log(await sendGet("/vcpcloud/api/padApi/getOrderEquipmentList",
{ startDate: "2026-05-01", endDate: "2026-05-31" }));
})();
go
package main
import (
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
)
const (
baseURL = "https://api.vmoscloud.com"
accessKey = "Access Key ID"
secretAccessKey = "Secret Access Key"
)
func sign(ts, path, bodyOrQuery string) string {
sum := sha256.Sum256([]byte(secretAccessKey + ts + path + bodyOrQuery))
return hex.EncodeToString(sum[:])
}
func sendPost(path string, params map[string]interface{}) (string, error) {
body := ""
if params != nil {
b, _ := json.Marshal(params)
body = string(b)
}
ts := strconv.FormatInt(time.Now().Unix(), 10)
req, _ := http.NewRequest("POST", baseURL+path, bytes.NewReader([]byte(body)))
req.Header.Set("X-Access-Key", accessKey)
req.Header.Set("X-Timestamp", ts)
req.Header.Set("X-Sign", sign(ts, path, body))
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
bs, _ := io.ReadAll(resp.Body)
return string(bs), nil
}
func sendGet(path string, params map[string]string) (string, error) {
parts := make([]string, 0, len(params))
for k, v := range params {
parts = append(parts, k+"="+v)
}
query := strings.Join(parts, "&")
ts := strconv.FormatInt(time.Now().Unix(), 10)
url := baseURL + path
if query != "" {
url += "?" + query
}
req, _ := http.NewRequest("GET", url, nil)
req.Header.Set("X-Access-Key", accessKey)
req.Header.Set("X-Timestamp", ts)
req.Header.Set("X-Sign", sign(ts, path, query))
resp, err := http.DefaultClient.Do(req)
if err != nil {
return "", err
}
defer resp.Body.Close()
bs, _ := io.ReadAll(resp.Body)
return string(bs), nil
}
func main() {
// POST
out, _ := sendPost("/vcpcloud/api/padApi/padInfo",
map[string]interface{}{"padCode": "AC32010601132"})
fmt.Println(out)
// GET
out, _ = sendGet("/vcpcloud/api/padApi/getOrderEquipmentList",
map[string]string{"startDate": "2026-05-01", "endDate": "2026-05-31"})
fmt.Println(out)
}
php
<?php
const BASE_URL = "https://api.vmoscloud.com";
const ACCESS_KEY = "Access Key ID";
const SECRET_ACCESS_KEY = "Secret Access Key";
function v2_sign(string $ts, string $path, string $bodyOrQuery): string {
return hash("sha256", SECRET_ACCESS_KEY . $ts . $path . $bodyOrQuery);
}
function send_post(string $path, ?array $params = null): string {
$body = $params === null ? "" : json_encode($params, JSON_UNESCAPED_UNICODE);
$ts = (string) time();
$ch = curl_init(BASE_URL . $path);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $body,
CURLOPT_HTTPHEADER => [
"X-Access-Key: " . ACCESS_KEY,
"X-Timestamp: " . $ts,
"X-Sign: " . v2_sign($ts, $path, $body),
"Content-Type: application/json",
],
]);
$resp = curl_exec($ch);
curl_close($ch);
return $resp;
}
function send_get(string $path, ?array $params = null): string {
$query = $params ? http_build_query($params) : "";
$ts = (string) time();
$url = BASE_URL . $path . ($query ? "?$query" : "");
$ch = curl_init($url);
curl_setopt_array($ch, [
CURLOPT_RETURNTRANSFER => true,
CURLOPT_HTTPHEADER => [
"X-Access-Key: " . ACCESS_KEY,
"X-Timestamp: " . $ts,
"X-Sign: " . v2_sign($ts, $path, $query),
],
]);
$resp = curl_exec($ch);
curl_close($ch);
return $resp;
}
// POST
echo send_post("/vcpcloud/api/padApi/padInfo", ["padCode" => "AC32010601132"]) . PHP_EOL;
// GET
echo send_get("/vcpcloud/api/padApi/getOrderEquipmentList",
["startDate" => "2026-05-01", "endDate" => "2026-05-31"]) . PHP_EOL;
.net
using System;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Linq;
public static class V2ApiRequestUtils
{
private const string BaseUrl = "https://api.vmoscloud.com";
private const string AccessKey = "Access Key ID";
private const string SecretAccessKey = "Secret Access Key";
private static readonly HttpClient Http = new HttpClient();
private static string Sign(string ts, string path, string bodyOrQuery)
{
using var sha = SHA256.Create();
byte[] hash = sha.ComputeHash(Encoding.UTF8.GetBytes(SecretAccessKey + ts + path + bodyOrQuery));
var sb = new StringBuilder(64);
foreach (var b in hash) sb.Append(b.ToString("x2"));
return sb.ToString();
}
public static async Task<string> SendPostAsync(string path, object? body)
{
string bodyJson = body == null ? "" : JsonSerializer.Serialize(body);
string ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
var req = new HttpRequestMessage(HttpMethod.Post, BaseUrl + path)
{
Content = new StringContent(bodyJson, Encoding.UTF8, "application/json")
};
req.Headers.TryAddWithoutValidation("X-Access-Key", AccessKey);
req.Headers.TryAddWithoutValidation("X-Timestamp", ts);
req.Headers.TryAddWithoutValidation("X-Sign", Sign(ts, path, bodyJson));
var resp = await Http.SendAsync(req);
return await resp.Content.ReadAsStringAsync();
}
public static async Task<string> SendGetAsync(string path, Dictionary<string, string>? query)
{
string queryStr = query == null ? ""
: string.Join("&", query.Select(kv => $"{kv.Key}={kv.Value}"));
string ts = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
var url = BaseUrl + path + (queryStr.Length > 0 ? "?" + queryStr : "");
var req = new HttpRequestMessage(HttpMethod.Get, url);
req.Headers.TryAddWithoutValidation("X-Access-Key", AccessKey);
req.Headers.TryAddWithoutValidation("X-Timestamp", ts);
req.Headers.TryAddWithoutValidation("X-Sign", Sign(ts, path, queryStr));
var resp = await Http.SendAsync(req);
return await resp.Content.ReadAsStringAsync();
}
public static async Task Main()
{
// POST
Console.WriteLine(await SendPostAsync("/vcpcloud/api/padApi/padInfo",
new { padCode = "AC32010601132" }));
// GET
Console.WriteLine(await SendGetAsync("/vcpcloud/api/padApi/getOrderEquipmentList",
new Dictionary<string, string> { ["startDate"] = "2026-05-01", ["endDate"] = "2026-05-31" }));
}
}
易语言
纯 Windows API(WinINet + CryptoAPI),无需第三方模块。
.版本 2
.DLL命令 CryptAcquireContext, 逻辑型, "advapi32.dll", "CryptAcquireContextA"
.参数 phProv, 整数型, 传址
.参数 pszContainer, 整数型
.参数 pszProvider, 整数型
.参数 dwProvType, 整数型
.参数 dwFlags, 整数型
.DLL命令 CryptCreateHash, 逻辑型, "advapi32.dll", "CryptCreateHash"
.参数 hProv, 整数型
.参数 Algid, 整数型
.参数 hKey, 整数型
.参数 dwFlags, 整数型
.参数 phHash, 整数型, 传址
.DLL命令 CryptHashData, 逻辑型, "advapi32.dll", "CryptHashData"
.参数 hHash, 整数型
.参数 pbData, 字节集
.参数 dwDataLen, 整数型
.参数 dwFlags, 整数型
.DLL命令 CryptGetHashParam, 逻辑型, "advapi32.dll", "CryptGetHashParam"
.参数 hHash, 整数型
.参数 dwParam, 整数型
.参数 pbData, 字节集
.参数 pdwDataLen, 整数型, 传址
.参数 dwFlags, 整数型
.DLL命令 CryptDestroyHash, 逻辑型, "advapi32.dll", "CryptDestroyHash"
.参数 hHash, 整数型
.DLL命令 CryptReleaseContext, 逻辑型, "advapi32.dll", "CryptReleaseContext"
.参数 hProv, 整数型
.参数 dwFlags, 整数型
.DLL命令 InternetOpen, 整数型, "wininet.dll", "InternetOpenA"
.参数 lpszAgent, 文本型
.参数 dwAccessType, 整数型
.参数 lpszProxy, 整数型
.参数 lpszProxyBypass, 整数型
.参数 dwFlags, 整数型
.DLL命令 InternetConnect, 整数型, "wininet.dll", "InternetConnectA"
.参数 hInternet, 整数型
.参数 lpszServerName, 文本型
.参数 nServerPort, 整数型
.参数 lpszUserName, 整数型
.参数 lpszPassword, 整数型
.参数 dwService, 整数型
.参数 dwFlags, 整数型
.参数 dwContext, 整数型
.DLL命令 HttpOpenRequest, 整数型, "wininet.dll", "HttpOpenRequestA"
.参数 hConnect, 整数型
.参数 lpszVerb, 文本型
.参数 lpszObjectName, 文本型
.参数 lpszVersion, 整数型
.参数 lpszReferrer, 整数型
.参数 lplpszAcceptTypes, 整数型
.参数 dwFlags, 整数型
.参数 dwContext, 整数型
.DLL命令 HttpAddRequestHeaders, 逻辑型, "wininet.dll", "HttpAddRequestHeadersA"
.参数 hRequest, 整数型
.参数 lpszHeaders, 文本型
.参数 dwHeadersLength, 整数型
.参数 dwModifiers, 整数型
.DLL命令 HttpSendRequest, 逻辑型, "wininet.dll", "HttpSendRequestA"
.参数 hRequest, 整数型
.参数 lpszHeaders, 整数型
.参数 dwHeadersLength, 整数型
.参数 lpOptional, 字节集
.参数 dwOptionalLength, 整数型
.DLL命令 InternetReadFile, 逻辑型, "wininet.dll", "InternetReadFile"
.参数 hFile, 整数型
.参数 lpBuffer, 字节集
.参数 dwNumberOfBytesToRead, 整数型
.参数 lpdwNumberOfBytesRead, 整数型, 传址
.DLL命令 InternetCloseHandle, 逻辑型, "wininet.dll", "InternetCloseHandle"
.参数 hInternet, 整数型
.DLL命令 GetSystemTimeAsFileTime, , "kernel32.dll", "GetSystemTimeAsFileTime"
.参数 lpFileTime, 长整数型, 传址
.程序集 程序集1
.程序集变量 主机, 文本型
.程序集变量 ACCESS_KEY, 文本型
.程序集变量 SECRET_ACCESS_KEY, 文本型
.子程序 _启动子程序
主机 = “api.vmoscloud.com”
ACCESS_KEY = “Access Key ID”
SECRET_ACCESS_KEY = “Secret Access Key”
调试输出 (V2请求 (“POST”, “/vcpcloud/api/padApi/padInfo”, “{"padCode":"AC32010601132"}”))
调试输出 (V2请求 (“GET”, “/vcpcloud/api/padApi/getOrderEquipmentList”, “startDate=2026-05-01&endDate=2026-05-31”))
.子程序 取时间戳, 文本型
.局部变量 ft, 长整数型
GetSystemTimeAsFileTime (ft)
返回 (到文本 (求整 (ft ÷ 10000000 - 11644473600)))
.子程序 Sha256十六进制, 文本型
.参数 输入, 文本型
.局部变量 hProv, 整数型
.局部变量 hHash, 整数型
.局部变量 数据, 字节集
.局部变量 摘要, 字节集
.局部变量 长度, 整数型
.局部变量 i, 整数型
.局部变量 b, 整数型
.局部变量 高, 整数型
.局部变量 十六, 文本型
.局部变量 结果, 文本型
十六 = “0123456789abcdef”
数据 = 到字节集 (输入)
.如果真 (CryptAcquireContext (hProv, 0, 0, 24, -268435456) = 假)
返回 (“”)
.如果真结束
CryptCreateHash (hProv, 32780, 0, 0, hHash)
CryptHashData (hHash, 数据, 取字节集长度 (数据), 0)
摘要 = 取空白字节集 (32)
长度 = 32
CryptGetHashParam (hHash, 2, 摘要, 长度, 0)
CryptDestroyHash (hHash)
CryptReleaseContext (hProv, 0)
.计次循环首 (32, i)
b = 摘要 [i]
高 = 求整 (b ÷ 16)
结果 = 结果 + 取文本中间 (十六, 高 + 1, 1) + 取文本中间 (十六, b - 高 × 16 + 1, 1)
.计次循环尾 ()
返回 (结果)
.子程序 V2请求, 文本型
.参数 方法, 文本型
.参数 路径, 文本型
.参数 数据, 文本型
.局部变量 时间戳, 文本型
.局部变量 签名, 文本型
.局部变量 对象, 文本型
.局部变量 请求头, 文本型
.局部变量 回车换行, 文本型
.局部变量 hOpen, 整数型
.局部变量 hConn, 整数型
.局部变量 hReq, 整数型
.局部变量 标志, 整数型
.局部变量 包体, 字节集
.局部变量 缓冲, 字节集
.局部变量 已读, 整数型
.局部变量 响应, 字节集
时间戳 = 取时间戳 ()
签名 = Sha256十六进制 (SECRET_ACCESS_KEY + 时间戳 + 路径 + 数据)
回车换行 = 字符 (13) + 字符 (10)
请求头 = “X-Access-Key: ” + ACCESS_KEY + 回车换行 + “X-Timestamp: ” + 时间戳 + 回车换行 + “X-Sign: ” + 签名
对象 = 路径
.如果 (方法 = “GET”)
.如果真 (数据 ≠ “”)
对象 = 路径 + “?” + 数据
.如果真结束
.否则
请求头 = 请求头 + 回车换行 + “Content-Type: application/json”
包体 = 到字节集 (数据)
.如果结束
标志 = 位或 (8388608, -2147483648)
hOpen = InternetOpen (“VMOSCloud-EYY/1.0”, 0, 0, 0, 0)
hConn = InternetConnect (hOpen, 主机, 443, 0, 0, 3, 0, 0)
hReq = HttpOpenRequest (hConn, 方法, 对象, 0, 0, 0, 标志, 0)
HttpAddRequestHeaders (hReq, 请求头, -1, 536870912)
HttpSendRequest (hReq, 0, 0, 包体, 取字节集长度 (包体))
缓冲 = 取空白字节集 (4096)
.判断循环首 (InternetReadFile (hReq, 缓冲, 4096, 已读) 且 已读 > 0)
响应 = 响应 + 取字节集左边 (缓冲, 已读)
.判断循环尾 ()
InternetCloseHandle (hReq)
InternetCloseHandle (hConn)
InternetCloseHandle (hOpen)
返回 (到文本 (响应))
常见问题
Q1:老客户是否需要迁移到 V2? 不需要。V4 长期保留,老 SDK 继续用即可。详见 V4 签名机制。
Q2:同一对 AK/SK 是否两套方案都能用? 可以。AK/SK 共用,按请求选协议。
Q3:POST body 为空时 bodyOrQuery 填什么? 空字符串。signString = SK + ts + path,末尾不再拼东西。
Q4:multipart 文件上传接口怎么签?bodyOrQuery 留空串,文件 body 不参与签名。涉及接口:/uploadFile、/asyncCmd、/syncCmd。
Q5:时间戳能用毫秒级吗? 不行。必须 10 位 unix 秒。13 位毫秒会因偏离当前时间超 5 分钟直接返回 2033。
Q6:X-Sign 大小写敏感吗? 服务端忽略大小写比较,大小写 hex 都接受。建议客户端统一输出小写。
Q7:如何分流到 V2 / V4? 带 X-Sign 头走 V2;带 Authorization 头走 V4。两套同时启用,按 Header 自动识别。