Files
antiwear-web/server/index.js
ZF sun 4f72519c9a feat(授权): 添加后端服务支持自动获取授权码
- 新增 server 目录包含后端服务,支持通过 AK/SK 或用户名密码认证获取授权码
- 添加前端 API 模块实现授权码获取功能
- 更新 UI 增加获取授权码按钮和输入框布局
- 完善 README 文档说明后端配置和启动流程
2026-04-29 17:21:24 +08:00

198 lines
6.0 KiB
JavaScript

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');
});