From d43d732c030c6005ca17139ccba3a827a2d92f7a Mon Sep 17 00:00:00 2001 From: ZF sun <34314687@qq.com> Date: Wed, 29 Apr 2026 18:14:32 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E5=8D=8E=E4=B8=BA?= =?UTF-8?q?=E4=BA=91API=E7=BD=91=E5=85=B3SDK=E5=B9=B6=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E8=AE=A4=E8=AF=81=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构服务器端认证逻辑,使用华为云API网关SDK替代原有实现 新增自动获取授权码功能,当用户未输入时自动获取 添加SDK相关测试文件和许可证 --- scripts/main.js | 14 +- .../licenses/license-crypto-js | 24 ++ .../licenses/license-node | 20 + .../APIGW-javascript-sdk-2.0.6/node_demo.js | 37 ++ server/APIGW-javascript-sdk-2.0.6/signer.js | 374 ++++++++++++++++++ server/APIGW-javascript-sdk-2.0.6/test.js | 10 + server/index.js | 205 +++------- server/sdk.zip | Bin 0 -> 7672 bytes 8 files changed, 527 insertions(+), 157 deletions(-) create mode 100644 server/APIGW-javascript-sdk-2.0.6/licenses/license-crypto-js create mode 100644 server/APIGW-javascript-sdk-2.0.6/licenses/license-node create mode 100644 server/APIGW-javascript-sdk-2.0.6/node_demo.js create mode 100644 server/APIGW-javascript-sdk-2.0.6/signer.js create mode 100644 server/APIGW-javascript-sdk-2.0.6/test.js create mode 100644 server/sdk.zip diff --git a/scripts/main.js b/scripts/main.js index 244bfc9..d413f46 100644 --- a/scripts/main.js +++ b/scripts/main.js @@ -181,13 +181,25 @@ async function initializeSDK() { try { const serverAddress = elements.serverAddress.value.trim(); const robotId = elements.robotId.value.trim(); - const onceCode = elements.onceCode.value.trim(); + let onceCode = elements.onceCode.value.trim(); if (!serverAddress) { logger.error('请输入服务器地址'); return; } + if (!onceCode) { + logger.info('授权码为空,正在自动获取...'); + try { + onceCode = await fetchOnceCode(); + elements.onceCode.value = onceCode; + logger.info('授权码自动获取成功'); + } catch (err) { + logger.error('授权码自动获取失败', err.message); + return; + } + } + logger.info('正在初始化 SDK...'); const isSupported = await HwICSUiSdk.checkBrowserSupport(true); diff --git a/server/APIGW-javascript-sdk-2.0.6/licenses/license-crypto-js b/server/APIGW-javascript-sdk-2.0.6/licenses/license-crypto-js new file mode 100644 index 0000000..6a7c063 --- /dev/null +++ b/server/APIGW-javascript-sdk-2.0.6/licenses/license-crypto-js @@ -0,0 +1,24 @@ +# License + +[The MIT License (MIT)](http://opensource.org/licenses/MIT) + +Copyright (c) 2009-2013 Jeff Mott +Copyright (c) 2013-2016 Evan Vosberg + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. \ No newline at end of file diff --git a/server/APIGW-javascript-sdk-2.0.6/licenses/license-node b/server/APIGW-javascript-sdk-2.0.6/licenses/license-node new file mode 100644 index 0000000..807debc --- /dev/null +++ b/server/APIGW-javascript-sdk-2.0.6/licenses/license-node @@ -0,0 +1,20 @@ +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. \ No newline at end of file diff --git a/server/APIGW-javascript-sdk-2.0.6/node_demo.js b/server/APIGW-javascript-sdk-2.0.6/node_demo.js new file mode 100644 index 0000000..afd6621 --- /dev/null +++ b/server/APIGW-javascript-sdk-2.0.6/node_demo.js @@ -0,0 +1,37 @@ +var signer = require('./signer'); +var https = require("https"); +var sig = new signer.Signer(); +//Set the AK/SK to sign and authenticate the request. +// 认证用的ak和sk硬编码到代码中或者明文存储都有很大的安全风险,建议在配置文件或者环境变量中密文存放,使用时解密,确保安全; +// 本示例以ak和sk保存在环境变量中为例,运行本示例前请先在本地环境中设置环境变量HUAWEICLOUD_SDK_AK和HUAWEICLOUD_SDK_SK。 +sig.Key = process.env.HUAWEICLOUD_SDK_AK; +sig.Secret = process.env.HUAWEICLOUD_SDK_SK; + +//The following example shows how to set the request URL and parameters to query a VPC list. +//Specify a request method, such as GET, PUT, POST, DELETE, HEAD, and PATCH. +//Set request host. +//Set request URI. +//Set parameters for the request URL. +var r = new signer.HttpRequest("GET", "endpoint.example.com/v1/77b6a44cba5143ab91d13ab9a8ff44fd/vpcs?limie=1"); +//Add header parameters, for example, x-domain-id for invoking a global service and x-project-id for invoking a project-level service. +r.headers = {"Content-Type": "application/json"}; +//Add a body if you have specified the PUT or POST method. Special characters, such as the double quotation mark ("), contained in the body must be escaped. +r.body = ''; + +var opt = sig.Sign(r); +console.log(opt.headers["X-Sdk-Date"]); +console.log(opt.headers["Authorization"]); + +var req = https.request(opt, function (res) { + console.log(res.statusCode); + console.log('headers:', JSON.stringify(res.headers)); + res.on("data", function (chunk) { + console.log(chunk.toString()) + }) +}); + +req.on("error", function (err) { + console.log(err.message) +}); +req.write(r.body); +req.end(); diff --git a/server/APIGW-javascript-sdk-2.0.6/signer.js b/server/APIGW-javascript-sdk-2.0.6/signer.js new file mode 100644 index 0000000..33deb68 --- /dev/null +++ b/server/APIGW-javascript-sdk-2.0.6/signer.js @@ -0,0 +1,374 @@ +// HWS API Gateway Signature +(function (root, factory) { + "use strict"; + + /*global define*/ + if (typeof define === 'function' && define.amd) { + // AMD + define(['CryptoJS'], function (CryptoJS) { + var crypto_wrapper = { + hmacsha256: function (keyByte, message) { + return CryptoJS.HmacSHA256(message, keyByte).toString(CryptoJS.enc.Hex) + }, + HexEncodeSHA256Hash: function (body) { + return CryptoJS.SHA256(body) + } + }; + return factory(crypto_wrapper) + }); + } else if (typeof wx === 'object') { + // wechat + var CryptoJS = require('./js/hmac-sha256.js'); + var crypto_wrapper = { + hmacsha256: function (keyByte, message) { + return CryptoJS.HmacSHA256(message, keyByte).toString(CryptoJS.enc.Hex) + }, + HexEncodeSHA256Hash: function (body) { + return CryptoJS.SHA256(body) + } + }; + module.exports = factory(crypto_wrapper); + } else if (typeof module === 'object' && module.exports) { + // Node + var crypto = require('crypto'); + var crypto_wrapper = { + hmacsha256: function (keyByte, message) { + return crypto.createHmac('SHA256', keyByte).update(message).digest().toString('hex') + }, + HexEncodeSHA256Hash: function (body) { + return crypto.createHash('SHA256').update(body).digest().toString('hex') + } + }; + module.exports = factory(crypto_wrapper); + } else { + // Browser + var CryptoJS = root.CryptoJS + var crypto_wrapper = { + hmacsha256: function (keyByte, message) { + return CryptoJS.HmacSHA256(message, keyByte).toString(CryptoJS.enc.Hex) + }, + HexEncodeSHA256Hash: function (body) { + return CryptoJS.SHA256(body) + } + }; + root.signer = factory(crypto_wrapper); + } +}(this, function (crypto_wrapper) { + 'use strict'; + + var Algorithm = "SDK-HMAC-SHA256"; + var HeaderXDate = "X-Sdk-Date"; + var HeaderAuthorization = "Authorization"; + var HeaderContentSha256 = "x-sdk-content-sha256"; + + const hexTable = new Array(256); + for (var i = 0; i < 256; ++i) + hexTable[i] = '%' + ((i < 16 ? '0' : '') + i.toString(16)).toUpperCase(); + + const noEscape = [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0 - 15 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 16 - 31 + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, // 32 - 47 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, // 48 - 63 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 64 - 79 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, // 80 - 95 + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 96 - 111 + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0 // 112 - 127 + ]; + + // function urlEncode is based on https://github.com/nodejs/node/blob/master/lib/querystring.js + // Copyright Joyent, Inc. and other Node contributors. + function urlEncode(str) { + if (typeof str !== 'string') { + if (typeof str === 'object') + str = String(str); + else + str += ''; + } + var out = ''; + var lastPos = 0; + + for (var i = 0; i < str.length; ++i) { + var c = str.charCodeAt(i); + + // ASCII + if (c < 0x80) { + if (noEscape[c] === 1) + continue; + if (lastPos < i) + out += str.slice(lastPos, i); + lastPos = i + 1; + out += hexTable[c]; + continue; + } + + if (lastPos < i) + out += str.slice(lastPos, i); + + // Multi-byte characters ... + if (c < 0x800) { + lastPos = i + 1; + out += hexTable[0xC0 | (c >> 6)] + hexTable[0x80 | (c & 0x3F)]; + continue; + } + if (c < 0xD800 || c >= 0xE000) { + lastPos = i + 1; + out += hexTable[0xE0 | (c >> 12)] + + hexTable[0x80 | ((c >> 6) & 0x3F)] + + hexTable[0x80 | (c & 0x3F)]; + continue; + } + // Surrogate pair + ++i; + + if (i >= str.length) + throw new errors.URIError('ERR_INVALID_URI'); + + var c2 = str.charCodeAt(i) & 0x3FF; + + lastPos = i + 1; + c = 0x10000 + (((c & 0x3FF) << 10) | c2); + out += hexTable[0xF0 | (c >> 18)] + + hexTable[0x80 | ((c >> 12) & 0x3F)] + + hexTable[0x80 | ((c >> 6) & 0x3F)] + + hexTable[0x80 | (c & 0x3F)]; + } + if (lastPos === 0) + return str; + if (lastPos < str.length) + return out + str.slice(lastPos); + return out; + } + + function HttpRequest(method, url, headers, body) { + if (method === undefined) { + this.method = ""; + } else { + this.method = method; + } + if (url === undefined) { + this.host = ""; + this.uri = ""; + this.query = {}; + } else { + this.query = {}; + var host, path; + var i = url.indexOf("://"); + if (i !== -1) { + url = url.substr(i + 3) + } + var i = url.indexOf("?"); + if (i !== -1) { + var query_str = url.substr(i + 1); + url = url.substr(0, i); + var spl = query_str.split("&"); + for (var i in spl) { + var kv = spl[i]; + var index = kv.indexOf('='); + var key, value; + if (index >= 0) { + key = kv.substr(0, index); + value = kv.substr(index + 1); + } else { + key = kv; + value = ""; + } + if (key !== "") { + key = decodeURI(key); + value = decodeURI(value); + if (this.query[key] === undefined) { + this.query[key] = [value] + } else { + this.query[key].push(value) + } + } + } + } + var i = url.indexOf("/"); + if (i === -1) { + host = url; + path = "/"; + } else { + host = url.substr(0, i); + path = url.substr(i); + } + this.host = host; + this.uri = decodeURI(path); + } + if (headers === undefined) { + this.headers = {}; + } else { + this.headers = headers; + } + if (body === undefined) { + this.body = ""; + } else { + this.body = body; + } + } + + function findHeader(r, header) { + for (var k in r.headers) { + if (k.toLowerCase() === header.toLowerCase()) { + return r.headers[k] + } + } + return null; + } + +// Build a CanonicalRequest from a regular request string +// +// CanonicalRequest = +// HTTPRequestMethod + '\n' + +// CanonicalURI + '\n' + +// CanonicalQueryString + '\n' + +// CanonicalHeaders + '\n' + +// SignedHeaders + '\n' + +// HexEncode(Hash(RequestPayload)) + function CanonicalRequest(r, signedHeaders) { + var hexencode = findHeader(r, HeaderContentSha256); + if (hexencode === null) { + var data = RequestPayload(r); + hexencode = crypto_wrapper.HexEncodeSHA256Hash(data); + } + return r.method + "\n" + CanonicalURI(r) + "\n" + CanonicalQueryString(r) + "\n" + CanonicalHeaders(r, signedHeaders) + "\n" + signedHeaders.join(';') + "\n" + hexencode + } + + function CanonicalURI(r) { + var pattens = r.uri.split('/'); + var uri = []; + for (var k in pattens) { + var v = pattens[k]; + uri.push(urlEncode(v)) + } + var urlpath = uri.join('/'); + if (urlpath[urlpath.length - 1] !== '/') { + urlpath = urlpath + '/' + } + //r.uri = urlpath + return urlpath; + } + + function CanonicalQueryString(r) { + var keys = []; + for (var key in r.query) { + keys.push(key) + } + keys.sort(); + var a = []; + for (var i in keys) { + var key = urlEncode(keys[i]); + var value = r.query[keys[i]]; + if (Array.isArray(value)) { + value.sort(); + for (var iv in value) { + a.push(key + '=' + urlEncode(value[iv])) + } + } else { + a.push(key + '=' + urlEncode(value)) + } + } + return a.join('&'); + } + + function CanonicalHeaders(r, signedHeaders) { + var headers = {}; + for (var key in r.headers) { + headers[key.toLowerCase()] = r.headers[key]; + } + var a = []; + for (var i in signedHeaders) { + var value = headers[signedHeaders[i]]; + a.push(signedHeaders[i] + ':' + value.trim()) + } + return a.join('\n') + "\n" + } + + function SignedHeaders(r) { + var a = []; + for (var key in r.headers) { + a.push(key.toLowerCase()) + } + a.sort(); + return a + } + + function RequestPayload(r) { + return r.body + } + +// Create a "String to Sign". + function StringToSign(canonicalRequest, t) { + var bytes = crypto_wrapper.HexEncodeSHA256Hash(canonicalRequest); + return Algorithm + "\n" + t + "\n" + bytes + } + +// Create the HWS Signature. + function SignStringToSign(stringToSign, signingKey) { + return crypto_wrapper.hmacsha256(signingKey, stringToSign) + } + +// Get the finalized value for the "Authorization" header. The signature +// parameter is the output from SignStringToSign + function AuthHeaderValue(signature, Key, signedHeaders) { + return Algorithm + " Access=" + Key + ", SignedHeaders=" + signedHeaders.join(';') + ", Signature=" + signature + } + + function twoChar(s) { + if (s >= 10) { + return "" + s + } else { + return "0" + s + } + } + + function getTime() { + var date = new Date(); + return "" + date.getUTCFullYear() + twoChar(date.getUTCMonth() + 1) + twoChar(date.getUTCDate()) + "T" + + twoChar(date.getUTCHours()) + twoChar(date.getUTCMinutes()) + twoChar(date.getUTCSeconds()) + "Z" + } + + function Signer() { + this.Key = ""; + this.Secret = ""; + } + + Signer.prototype.Sign = function (r) { + var headerTime = findHeader(r, HeaderXDate); + if (headerTime === null) { + headerTime = getTime(); + r.headers[HeaderXDate] = headerTime + } + if (r.method !== "PUT" && r.method !== "PATCH" && r.method !== "POST") { + r.body = "" + } + var queryString = CanonicalQueryString(r); + if (queryString !== "") { + queryString = "?" + queryString + } + var options = { + hostname: r.host, + path: encodeURI(r.uri) + queryString, + method: r.method, + headers: r.headers + }; + if (findHeader(r, 'host') === null) { + r.headers.host = r.host; + } + var signedHeaders = SignedHeaders(r); + var canonicalRequest = CanonicalRequest(r, signedHeaders); + var stringToSign = StringToSign(canonicalRequest, headerTime); + var signature = SignStringToSign(stringToSign, this.Secret); + options.headers[HeaderAuthorization] = AuthHeaderValue(signature, this.Key, signedHeaders); + return options + }; + return { + HttpRequest: HttpRequest, + Signer: Signer, + urlEncode: urlEncode, + findHeader: findHeader, + SignedHeaders: SignedHeaders, + CanonicalRequest: CanonicalRequest, + StringToSign: StringToSign, + } +})); diff --git a/server/APIGW-javascript-sdk-2.0.6/test.js b/server/APIGW-javascript-sdk-2.0.6/test.js new file mode 100644 index 0000000..5956815 --- /dev/null +++ b/server/APIGW-javascript-sdk-2.0.6/test.js @@ -0,0 +1,10 @@ +var assert = require("assert"); +var signer = require('./signer') + +var s = "" +for (i = 0; i < 0x80; i++) { + s = s + signer.urlEncode(String.fromCharCode(i)) +} +console.log(s) +assert.equal(s, "%00%01%02%03%04%05%06%07%08%09%0A%0B%0C%0D%0E%0F%10%11%12%13%14%15%16%17%18%19%1A%1B%1C%1D%1E%1F%20%21%22%23%24%25%26%27%28%29%2A%2B%2C-.%2F0123456789%3A%3B%3C%3D%3E%3F%40ABCDEFGHIJKLMNOPQRSTUVWXYZ%5B%5C%5D%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F") + diff --git a/server/index.js b/server/index.js index df3ab0d..a4bf546 100644 --- a/server/index.js +++ b/server/index.js @@ -2,7 +2,8 @@ require('dotenv').config(); const express = require('express'); const cors = require('cors'); -const crypto = require('crypto'); +const https = require('https'); +const signer = require('./APIGW-javascript-sdk-2.0.6/signer'); const app = express(); const PORT = 3001; @@ -11,172 +12,66 @@ app.use(cors()); app.use(express.json()); const METASTUDIO_API_BASE = 'https://metastudio.cn-north-4.myhuaweicloud.com'; -const PROJECT_ID = process.env.HUAWEI_PROJECT_ID || 'c67bf5e7d1f84d20a6eefff2c1ee9cd4'; -const IAM_TOKEN_URL = 'https://iam.cn-north-4.myhuaweicloud.com/v3/auth/tokens'; - -async function getIamToken() { - const username = process.env.HUAWEI_USERNAME; - const password = process.env.HUAWEI_PASSWORD; - const domainName = process.env.HUAWEI_DOMAIN_NAME || domainName; - const region = 'cn-north-4'; - - if (!username || !password || !domainName) { - throw new Error('请配置环境变量: HUAWEI_USERNAME, HUAWEI_PASSWORD, HUAWEI_DOMAIN_NAME'); - } - - const requestBody = { - "auth": { - "identity": { - "methods": ["password"], - "password": { - "user": { - "name": username, - "password": password, - "domain": { - "name": domainName - } - } - } - }, - "scope": { - "project": { - "name": region - } - } - } - }; - - const response = await fetch(IAM_TOKEN_URL, { - method: 'POST', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(requestBody) - }); - - if (!response.ok) { - throw new Error(`IAM认证失败: ${response.status}`); - } - - const token = response.headers.get('X-Subject-Token'); - return token; -} - -function buildAKSKSignature(method, host, uri, query, date, headers) { - const secretKey = process.env.HUAWEI_SECRET_KEY; - if (!secretKey) { - throw new Error('请配置环境变量: HUAWEI_SECRET_KEY'); - } - - const signingKey = 'sdk-hmac-sha256'; - const dateShort = date.toISOString().slice(0, 10).replace(/-/g, ''); - - const canonicalRequestParts = [ - method.toUpperCase(), - uri, - query || '', - `${headers.toLowerCase()}\n${host.toLowerCase()}` - ]; - const canonicalRequest = canonicalRequestParts.join('\n'); - - const stringToSign = [ - signingKey, - date, - `${dateShort}/${region}/metastudio/request`, - crypto.createHash('sha256').update(canonicalRequest).digest('hex') - ].join('\n'); - - const signature = crypto - .createHmac('sha256', secretKey) - .update(stringToSign) - .digest('base64'); - - return { - signature, - stringToSign, - dateShort - }; -} async function createOnceCodeWithAKSK() { const accessKey = process.env.HUAWEI_AK; const secretKey = process.env.HUAWEI_SK; - const region = 'cn-north-4'; + const projectId = process.env.HUAWEI_PROJECT_ID; if (!accessKey || !secretKey) { throw new Error('请配置环境变量: HUAWEI_AK 和 HUAWEI_SK'); } - - const host = 'metastudio.cn-north-4.myhuaweicloud.com'; - const uri = `/v1/${PROJECT_ID}/digital-human-chat/once-code`; - const method = 'POST'; - const date = new Date().toUTCString(); - const query = ''; - - const { signature, stringToSign, dateShort } = buildAKSKSignature( - method, host, uri, query, date, '' - ); - - const authHeader = `SDK-HMAC-SHA256 Access=${accessKey}, SignedHeaders=host;x-sdk-date, Signature=${signature}`; - - const response = await fetch(`${METASTUDIO_API_BASE}${uri}`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Sdk-Date': date, - 'Authorization': authHeader, - 'X-Project-Id': PROJECT_ID - }, - body: JSON.stringify({}) - }); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`API调用失败: ${response.status} - ${errorText}`); + if (!projectId) { + throw new Error('请配置环境变量: HUAWEI_PROJECT_ID'); } - const data = await response.json(); - return data.once_code; + const uri = `/v1/${projectId}/digital-human-chat/once-code`; + + const sig = new signer.Signer(); + sig.Key = accessKey; + sig.Secret = secretKey; + + const req = new signer.HttpRequest( + 'POST', + `${METASTUDIO_API_BASE}${uri}`, + { + 'content-type': 'application/json', + 'x-project-id': projectId + }, + '{}' + ); + + const options = sig.Sign(req); + + return new Promise((resolve, reject) => { + const request = https.request(options, (res) => { + let data = ''; + res.on('data', (chunk) => { + data += chunk; + }); + res.on('end', () => { + if (res.statusCode !== 200) { + reject(new Error(`API调用失败: ${res.statusCode} - ${data}`)); + return; + } + const response = JSON.parse(data); + resolve(response.once_code); + }); + }); + + request.on('error', (e) => { + reject(e); + }); + + request.write('{}'); + request.end(); + }); } app.post('/api/once-code', async (req, res) => { try { - let onceCode; - - if (process.env.HUAWEI_AK && process.env.HUAWEI_SK) { - console.log('使用 AK/SK 认证获取授权码...'); - onceCode = await createOnceCodeWithAKSK(); - } else if (process.env.HUAWEI_USERNAME && process.env.HUAWEI_PASSWORD) { - console.log('使用 Token 认证获取授权码...'); - const token = await getIamToken(); - - const response = await fetch( - `${METASTUDIO_API_BASE}/v1/${PROJECT_ID}/digital-human-chat/once-code`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-Auth-Token': token, - 'X-Project-Id': PROJECT_ID - }, - body: JSON.stringify({}) - } - ); - - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`API调用失败: ${response.status} - ${errorText}`); - } - - const data = await response.json(); - onceCode = data.once_code; - } else { - return res.status(400).json({ - error_code: 'CONFIG_MISSING', - error_msg: '请配置环境变量 (AK/SK 或用户名密码)' - }); - } - + console.log('使用 AK/SK 认证获取授权码...'); + const onceCode = await createOnceCodeWithAKSK(); res.json({ once_code: onceCode }); } catch (error) { console.error('获取授权码失败:', error); @@ -193,7 +88,5 @@ app.get('/health', (req, res) => { app.listen(PORT, () => { console.log(`后端服务已启动: http://localhost:${PORT}`); - console.log('支持的认证方式:'); - console.log(' 1. AK/SK 认证: 设置 HUAWEI_AK 和 HUAWEI_SK'); - console.log(' 2. Token 认证: 设置 HUAWEI_USERNAME, HUAWEI_PASSWORD, HUAWEI_DOMAIN_NAME'); + console.log('使用 AK/SK 认证方式'); }); diff --git a/server/sdk.zip b/server/sdk.zip new file mode 100644 index 0000000000000000000000000000000000000000..e5db0fed72ebc81452c78537ab3d8b9a1ede45fb GIT binary patch literal 7672 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 Y|Bi_Eb0Yq*OEHlge+I%!%0I6D3oSYixc~qF literal 0 HcmV?d00001