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; } 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 { 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 { 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 { const hashBuffer = await crypto.subtle.digest('SHA-256', stringToArrayBuffer(content)); return arrayBufferToHex(hashBuffer); } /** * 获取签名 */ export async function getSignature( method: string, query: Record, header: Record, ak: string, sk: string, action: string, body: any ): Promise { 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 = {}; 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, header: Record, ak: string, sk: string, action: string, body: any ): Promise { 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); } }