200 lines
6.0 KiB
JavaScript
200 lines
6.0 KiB
JavaScript
require('dotenv').config();
|
|
|
|
const express = require('express');
|
|
const cors = require('cors');
|
|
const crypto = require('crypto');
|
|
|
|
const app = express();
|
|
const PORT = 3001;
|
|
|
|
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';
|
|
|
|
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}`);
|
|
}
|
|
|
|
const data = await response.json();
|
|
return data.once_code;
|
|
}
|
|
|
|
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 或用户名密码)'
|
|
});
|
|
}
|
|
|
|
res.json({ once_code: onceCode });
|
|
} catch (error) {
|
|
console.error('获取授权码失败:', error);
|
|
res.status(500).json({
|
|
error_code: 'INTERNAL_ERROR',
|
|
error_msg: error.message
|
|
});
|
|
}
|
|
});
|
|
|
|
app.get('/health', (req, res) => {
|
|
res.json({ status: 'ok' });
|
|
});
|
|
|
|
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');
|
|
});
|