This commit is contained in:
2025-11-14 20:43:59 +08:00
parent d1ba8bbab1
commit e35712820f
16 changed files with 606 additions and 14 deletions

View File

@@ -0,0 +1,250 @@
const Service = "DNS";
const Version = "2018-08-01";
const Region = "cn-north-1";
const Host = "dns.volcengineapi.com";
interface Credential {
access_key_id: string;
secret_access_key: string;
service: string;
region: string;
}
interface RequestParam {
body: string;
host: string;
path: string;
method: string;
content_type: string;
date: Date;
query: Record<string, any>;
}
interface SignResult {
Host: string;
'X-Content-Sha256': string;
'X-Date': string;
'Content-Type': string;
Authorization?: string;
}
/**
* 字符串转ArrayBuffer
*/
function stringToArrayBuffer(str: string): ArrayBuffer {
const encoder = new TextEncoder();
return encoder.encode(str).buffer;
}
/**
* ArrayBuffer转十六进制字符串
*/
function arrayBufferToHex(buffer: ArrayBuffer): string {
const hashArray = Array.from(new Uint8Array(buffer));
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
}
/**
* URL编码函数类似Python的quote函数
*/
function encodeURIComponentSafe(str: string): string {
return encodeURIComponent(str)
.replace(/[!'()*]/g, (c) => '%' + c.charCodeAt(0).toString(16).toUpperCase())
.replace(/%20/g, '%20');
}
/**
* 规范化查询参数
*/
function normQuery(params: Record<string, any>): string {
let query = "";
const sortedKeys = Object.keys(params).sort();
for (const key of sortedKeys) {
if (Array.isArray(params[key])) {
for (const k of params[key]) {
query += encodeURIComponentSafe(key) + "=" + encodeURIComponentSafe(k) + "&";
}
} else {
query += encodeURIComponentSafe(key) + "=" + encodeURIComponentSafe(params[key]) + "&";
}
}
query = query.slice(0, -1);
return query.replace(/\+/g, "%20");
}
/**
* HMAC-SHA256 加密 (使用Web Crypto API)
*/
async function hmacSha256(key: ArrayBuffer, content: string): Promise<ArrayBuffer> {
const cryptoKey = await crypto.subtle.importKey(
'raw',
key,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
return crypto.subtle.sign('HMAC', cryptoKey, stringToArrayBuffer(content));
}
/**
* SHA256 哈希算法 (使用Web Crypto API)
*/
async function hashSha256(content: string): Promise<string> {
const hashBuffer = await crypto.subtle.digest('SHA-256', stringToArrayBuffer(content));
return arrayBufferToHex(hashBuffer);
}
/**
* 获取签名
*/
export async function getSignature(
method: string,
query: Record<string, any>,
header: Record<string, string>,
ak: string,
sk: string,
action: string,
body: any
): Promise<SignResult> {
const credential: Credential = {
access_key_id: ak,
secret_access_key: sk,
service: Service,
region: Region,
};
const fullQuery = { Action: action, Version: Version, ...query };
const sortedQuery: Record<string, any> = {};
Object.keys(fullQuery).sort().forEach(key => {
sortedQuery[key] = (fullQuery as any)[key];
});
const requestParam: RequestParam = {
body: "",
host: Host,
path: "/",
method: method,
content_type: "application/json",
date: new Date(),
query: sortedQuery,
};
if (method === "POST") {
requestParam.body = JSON.stringify(body);
}
const xDate = requestParam.date.toISOString().replace(/[:\-]|\.\d{3}/g, '').slice(0, -1) + 'Z';
const shortXDate = xDate.slice(0, 8);
const xContentSha256 = await hashSha256(requestParam.body);
const signResult: SignResult = {
Host: requestParam.host,
'X-Content-Sha256': xContentSha256,
'X-Date': xDate,
'Content-Type': requestParam.content_type,
};
const signedHeadersStr = ["content-type", "host", "x-content-sha256", "x-date"].join(";");
const canonicalRequestStr = [
requestParam.method,
requestParam.path,
normQuery(requestParam.query),
[
"content-type:" + requestParam.content_type,
"host:" + requestParam.host,
"x-content-sha256:" + xContentSha256,
"x-date:" + xDate,
].join("\n"),
"",
signedHeadersStr,
xContentSha256,
].join("\n");
const hashedCanonicalRequest = await hashSha256(canonicalRequestStr);
const credentialScope = [shortXDate, credential.region, credential.service, "request"].join("/");
const stringToSign = ["HMAC-SHA256", xDate, credentialScope, hashedCanonicalRequest].join("\n");
const secretKeyBuffer = stringToArrayBuffer(credential.secret_access_key);
const kDate = await hmacSha256(secretKeyBuffer, shortXDate);
const kRegion = await hmacSha256(kDate, credential.region);
const kService = await hmacSha256(kRegion, credential.service);
const kSigning = await hmacSha256(kService, "request");
const signatureBuffer = await hmacSha256(kSigning, stringToSign);
const signature = arrayBufferToHex(signatureBuffer);
signResult.Authorization = `HMAC-SHA256 Credential=${credential.access_key_id}/${credentialScope}, SignedHeaders=${signedHeadersStr}, Signature=${signature}`;
return signResult;
}
/**
* 发送API请求
*/
export async function request(
method: string,
query: Record<string, any>,
header: Record<string, string>,
ak: string,
sk: string,
action: string,
body: any
): Promise<any> {
const signResult = await getSignature(method, query, header, ak, sk, action, body);
const finalHeader = { ...header, ...signResult };
const requestParam: RequestParam = {
body: method === "POST" ? JSON.stringify(body) : "",
host: Host,
path: "/",
method: method,
content_type: "application/json",
date: new Date(),
query: { Action: action, Version: Version, ...query },
};
const url = new URL(`https://${requestParam.host}${requestParam.path}`);
Object.keys(requestParam.query).forEach(key => {
url.searchParams.append(key, requestParam.query[key]);
});
const fetchOptions: RequestInit = {
method: method,
headers: finalHeader,
};
if (method === "POST") {
fetchOptions.body = requestParam.body;
}
try {
const response = await fetch(url.toString(), fetchOptions);
return await response.json();
} catch (error) {
throw new Error(`Request failed: ${error}`);
}
}
/**
* 使用示例
*/
export async function example(ak: string, sk: string) {
try {
// POST 请求示例 - 调用 UpdateZone API
const requestBody = {
ZID: 100,
Remark: "example",
};
const updateZoneResult = await request("POST", {}, {}, ak, sk, "UpdateZone", requestBody);
console.log(updateZoneResult);
// GET 请求示例 - 调用 CheckZone API
const requestQuery = { ZoneName: "example.com" };
const checkZoneResult = await request("GET", requestQuery, {}, ak, sk, "CheckZone", {});
console.log(checkZoneResult);
} catch (error) {
console.error('API request failed:', error);
}
}