feat(授权): 添加后端服务支持自动获取授权码
- 新增 server 目录包含后端服务,支持通过 AK/SK 或用户名密码认证获取授权码 - 添加前端 API 模块实现授权码获取功能 - 更新 UI 增加获取授权码按钮和输入框布局 - 完善 README 文档说明后端配置和启动流程
This commit is contained in:
149
README.md
149
README.md
@@ -11,8 +11,12 @@ antiwear-web/
|
|||||||
│ ├── HwICSUiSdk.d.ts # TypeScript 类型定义
|
│ ├── HwICSUiSdk.d.ts # TypeScript 类型定义
|
||||||
│ ├── HwICSUiSdk.css # SDK 样式
|
│ ├── HwICSUiSdk.css # SDK 样式
|
||||||
│ └── ...
|
│ └── ...
|
||||||
|
├── server/ # 后端服务
|
||||||
|
│ ├── index.js # 后端主入口
|
||||||
|
│ └── package.json # 后端依赖
|
||||||
├── scripts/
|
├── scripts/
|
||||||
│ └── main.js # 主逻辑代码
|
│ ├── main.js # 主逻辑代码
|
||||||
|
│ └── api.js # 前端 API 调用模块
|
||||||
├── styles/
|
├── styles/
|
||||||
│ └── main.css # 自定义样式
|
│ └── main.css # 自定义样式
|
||||||
├── index.html # 主页面
|
├── index.html # 主页面
|
||||||
@@ -23,28 +27,63 @@ antiwear-web/
|
|||||||
|
|
||||||
### 1. 环境要求
|
### 1. 环境要求
|
||||||
|
|
||||||
|
- Node.js 14+ (后端服务)
|
||||||
- 现代浏览器(Chrome、Firefox、Edge、Safari 最新版本)
|
- 现代浏览器(Chrome、Firefox、Edge、Safari 最新版本)
|
||||||
- 支持 WebRTC 和 WebSocket
|
- 支持 WebRTC 和 WebSocket
|
||||||
- 需要麦克风权限(用于语音交互)
|
- 需要麦克风权限(用于语音交互)
|
||||||
|
|
||||||
### 2. 配置参数
|
### 2. 后端服务配置
|
||||||
|
|
||||||
#### 默认参数(已预填)
|
#### 方式一:使用 AK/SK 认证(推荐)
|
||||||
| 参数 | 默认值 |
|
|
||||||
|------|--------|
|
|
||||||
| 服务器地址 | `metastudio-api.cn-north-4.myhuaweicloud.com` |
|
|
||||||
| 机器人ID | `2c9084bf9c9cc1c0019db2c4ca2a5c89` |
|
|
||||||
|
|
||||||
#### 需要填写的参数
|
设置环境变量:
|
||||||
| 参数 | 说明 | 必填 |
|
```bash
|
||||||
|------|------|------|
|
# Windows
|
||||||
| 一次性授权码 | 用于身份验证的临时授权码 | 是 |
|
set HUAWEI_AK=your_access_key
|
||||||
|
set HUAWEI_SK=your_secret_key
|
||||||
|
set HUAWEI_PROJECT_ID=your_project_id
|
||||||
|
```
|
||||||
|
|
||||||
> **如何获取授权码**:通过华为云 MetaStudio 控制台或 API 获取一次性授权码(有效期5分钟)
|
```bash
|
||||||
|
# Linux/Mac
|
||||||
|
export HUAWEI_AK=your_access_key
|
||||||
|
export HUAWEI_SK=your_secret_key
|
||||||
|
export HUAWEI_PROJECT_ID=your_project_id
|
||||||
|
```
|
||||||
|
|
||||||
### 3. 启动项目
|
#### 方式二:使用用户名密码认证
|
||||||
|
|
||||||
由于使用了 ES Modules,需要通过本地服务器运行:
|
```bash
|
||||||
|
# Windows
|
||||||
|
set HUAWEI_USERNAME=your_username
|
||||||
|
set HUAWEI_PASSWORD=your_password
|
||||||
|
set HUAWEI_DOMAIN_NAME=your_domain_name
|
||||||
|
set HUAWEI_PROJECT_ID=your_project_id
|
||||||
|
```
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Linux/Mac
|
||||||
|
export HUAWEI_USERNAME=your_username
|
||||||
|
export HUAWEI_PASSWORD=your_password
|
||||||
|
export HUAWEI_DOMAIN_NAME=your_domain_name
|
||||||
|
export HUAWEI_PROJECT_ID=your_project_id
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 启动服务
|
||||||
|
|
||||||
|
#### 启动后端服务
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd server
|
||||||
|
npm install
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
后端服务将在 `http://localhost:3001` 启动。
|
||||||
|
|
||||||
|
#### 启动前端项目
|
||||||
|
|
||||||
|
在另一个终端窗口:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# 使用 Python 3
|
# 使用 Python 3
|
||||||
@@ -58,11 +97,21 @@ npx http-server -p 8080
|
|||||||
|
|
||||||
然后在浏览器中访问 `http://localhost:8080`
|
然后在浏览器中访问 `http://localhost:8080`
|
||||||
|
|
||||||
### 4. 使用流程
|
### 4. 配置参数
|
||||||
|
|
||||||
1. **获取授权码**:通过华为云控制台获取一次性授权码
|
#### 默认参数(已预填)
|
||||||
2. **填写配置**:在页面中输入授权码
|
| 参数 | 默认值 |
|
||||||
3. **初始化 SDK**:点击 "初始化 SDK" 开始使用
|
|------|--------|
|
||||||
|
| 服务器地址 | `metastudio-api.cn-north-4.myhuaweicloud.com` |
|
||||||
|
| 机器人ID | `2c9084bf9c9cc1c0019db2c4ca2a5c89` |
|
||||||
|
| 项目ID | `c67bf5e7d1f84d20a6eefff2c1ee9cd4` |
|
||||||
|
|
||||||
|
### 5. 使用流程
|
||||||
|
|
||||||
|
1. **启动后端服务**:`cd server && npm start`
|
||||||
|
2. **启动前端服务器**:`python -m http.server 8080`
|
||||||
|
3. **获取授权码**:点击页面上的 "🔑 获取授权码" 按钮
|
||||||
|
4. **初始化 SDK**:点击 "初始化 SDK" 开始使用
|
||||||
|
|
||||||
## 📖 功能说明
|
## 📖 功能说明
|
||||||
|
|
||||||
@@ -105,55 +154,36 @@ SDK 提供了丰富的事件回调:
|
|||||||
|
|
||||||
## 🔧 开发指南
|
## 🔧 开发指南
|
||||||
|
|
||||||
### 引入 SDK
|
### 前端 API 调用
|
||||||
|
|
||||||
```javascript
|
后端服务提供了以下接口:
|
||||||
import { HwICSUiSdk, ICSEventKey, InteractionMode, ConfigKey } from './hwICSUiSdk/HwICSUiSdk.esm.js';
|
|
||||||
|
#### 获取一次性授权码
|
||||||
|
```
|
||||||
|
POST /api/once-code
|
||||||
```
|
```
|
||||||
|
|
||||||
### 初始化 SDK
|
响应示例:
|
||||||
|
```json
|
||||||
```javascript
|
{
|
||||||
await HwICSUiSdk.create({
|
"once_code": "OATAXWpTNDPGjVyzxNLKvumcv297Q7uUz5CCltLtFDE1uqnPAPmS4jm61XnMQGuV"
|
||||||
serverAddress: 'your-server-address',
|
}
|
||||||
containerId: 'digitalHumanBox',
|
|
||||||
robotId: 'your-robot-id',
|
|
||||||
onceCode: 'your-once-code',
|
|
||||||
config: {
|
|
||||||
[ConfigKey.ENABLE_CAPTION]: true,
|
|
||||||
[ConfigKey.ENABLE_CHAT_BTN]: true
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 注册事件监听
|
错误响应:
|
||||||
|
```json
|
||||||
```javascript
|
{
|
||||||
HwICSUiSdk.addEventListeners({
|
"error_code": "CONFIG_MISSING",
|
||||||
[ICSEventKey.SPEAKING_START]: () => {
|
"error_msg": "请配置环境变量 (AK/SK 或用户名密码)"
|
||||||
console.log('数字人开始说话');
|
}
|
||||||
},
|
|
||||||
[ICSEventKey.SPEECH_RECOGNIZED]: (data) => {
|
|
||||||
console.log('识别结果:', data);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### 开始对话
|
### 后端接口
|
||||||
|
|
||||||
```javascript
|
| 接口 | 方法 | 说明 |
|
||||||
await HwICSUiSdk.startChat({
|
|------|------|------|
|
||||||
interactionMode: InteractionMode.AUDIO
|
| /api/once-code | POST | 获取一次性授权码 |
|
||||||
});
|
| /health | GET | 健康检查 |
|
||||||
```
|
|
||||||
|
|
||||||
### 发送文本消息
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
await HwICSUiSdk.sendTextQuestion({
|
|
||||||
text: '你好,数字人!'
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
## ⚙️ 配置选项
|
## ⚙️ 配置选项
|
||||||
|
|
||||||
@@ -176,6 +206,7 @@ SDK 支持多种配置项:
|
|||||||
2. **浏览器权限**:首次使用需要授权麦克风和摄像头权限
|
2. **浏览器权限**:首次使用需要授权麦克风和摄像头权限
|
||||||
3. **资源释放**:页面卸载时务必调用 `destroy()` 方法释放资源
|
3. **资源释放**:页面卸载时务必调用 `destroy()` 方法释放资源
|
||||||
4. **错误处理**:建议对所有异步操作添加错误处理
|
4. **错误处理**:建议对所有异步操作添加错误处理
|
||||||
|
5. **后端服务**:获取授权码需要启动后端服务并配置正确的环境变量
|
||||||
|
|
||||||
## 🔍 调试技巧
|
## 🔍 调试技巧
|
||||||
|
|
||||||
|
|||||||
@@ -44,8 +44,11 @@
|
|||||||
<h3>授权码</h3>
|
<h3>授权码</h3>
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="onceCode">一次性授权码</label>
|
<label for="onceCode">一次性授权码</label>
|
||||||
<input type="text" id="onceCode" placeholder="请输入一次性授权码" value="">
|
<div class="input-with-button">
|
||||||
<small class="field-hint">通过华为云控制台获取,有效期5分钟,只能使用一次</small>
|
<input type="text" id="onceCode" placeholder="请输入一次性授权码或点击获取" value="" readonly>
|
||||||
|
<button id="getOnceCodeBtn" class="btn btn-secondary">🔑 获取授权码</button>
|
||||||
|
</div>
|
||||||
|
<small class="field-hint">有效期5分钟,只能使用一次</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
18
scripts/api.js
Normal file
18
scripts/api.js
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
const API_BASE = 'http://localhost:3001';
|
||||||
|
|
||||||
|
export async function fetchOnceCode() {
|
||||||
|
const response = await fetch(`${API_BASE}/api/once-code`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const error = await response.json();
|
||||||
|
throw new Error(error.error_msg || '获取授权码失败');
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
return data.once_code;
|
||||||
|
}
|
||||||
@@ -10,6 +10,7 @@
|
|||||||
|
|
||||||
// 导入 HwICSUiSdk
|
// 导入 HwICSUiSdk
|
||||||
import { HwICSUiSdk, ICSEventKey, InteractionMode, ConfigKey } from '../hwICSUiSdk/HwICSUiSdk.esm.js';
|
import { HwICSUiSdk, ICSEventKey, InteractionMode, ConfigKey } from '../hwICSUiSdk/HwICSUiSdk.esm.js';
|
||||||
|
import { fetchOnceCode } from './api.js';
|
||||||
|
|
||||||
// ===== 全局状态 =====
|
// ===== 全局状态 =====
|
||||||
const state = {
|
const state = {
|
||||||
@@ -31,6 +32,7 @@ const elements = {
|
|||||||
// 按钮
|
// 按钮
|
||||||
initBtn: document.getElementById('initBtn'),
|
initBtn: document.getElementById('initBtn'),
|
||||||
destroyBtn: document.getElementById('destroyBtn'),
|
destroyBtn: document.getElementById('destroyBtn'),
|
||||||
|
getOnceCodeBtn: document.getElementById('getOnceCodeBtn'),
|
||||||
startChatBtn: document.getElementById('startChatBtn'),
|
startChatBtn: document.getElementById('startChatBtn'),
|
||||||
stopChatBtn: document.getElementById('stopChatBtn'),
|
stopChatBtn: document.getElementById('stopChatBtn'),
|
||||||
startSpeakBtn: document.getElementById('startSpeakBtn'),
|
startSpeakBtn: document.getElementById('startSpeakBtn'),
|
||||||
@@ -408,6 +410,24 @@ async function toggleAudio() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function handleGetOnceCode() {
|
||||||
|
try {
|
||||||
|
elements.getOnceCodeBtn.disabled = true;
|
||||||
|
elements.getOnceCodeBtn.textContent = '🔄 获取中...';
|
||||||
|
logger.info('正在获取授权码...');
|
||||||
|
|
||||||
|
const onceCode = await fetchOnceCode();
|
||||||
|
elements.onceCode.value = onceCode;
|
||||||
|
logger.info('授权码获取成功');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('获取授权码失败', error.message);
|
||||||
|
} finally {
|
||||||
|
elements.getOnceCodeBtn.disabled = false;
|
||||||
|
elements.getOnceCodeBtn.textContent = '🔑 获取授权码';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function clearLogs() {
|
function clearLogs() {
|
||||||
elements.logContainer.innerHTML = '';
|
elements.logContainer.innerHTML = '';
|
||||||
logger.info('日志已清空');
|
logger.info('日志已清空');
|
||||||
@@ -417,6 +437,7 @@ function clearLogs() {
|
|||||||
function bindEvents() {
|
function bindEvents() {
|
||||||
elements.initBtn.addEventListener('click', initializeSDK);
|
elements.initBtn.addEventListener('click', initializeSDK);
|
||||||
elements.destroyBtn.addEventListener('click', destroySDK);
|
elements.destroyBtn.addEventListener('click', destroySDK);
|
||||||
|
elements.getOnceCodeBtn.addEventListener('click', handleGetOnceCode);
|
||||||
|
|
||||||
elements.startChatBtn.addEventListener('click', startChat);
|
elements.startChatBtn.addEventListener('click', startChat);
|
||||||
elements.stopChatBtn.addEventListener('click', stopChat);
|
elements.stopChatBtn.addEventListener('click', stopChat);
|
||||||
|
|||||||
197
server/index.js
Normal file
197
server/index.js
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
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');
|
||||||
|
});
|
||||||
13
server/package.json
Normal file
13
server/package.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"name": "antiwear-server",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "华为交互数字人后端服务",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node index.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"cors": "^2.8.5"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -248,6 +248,15 @@ section h2 {
|
|||||||
padding-right: 80px;
|
padding-right: 80px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.input-with-button {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.input-with-button input {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
.field-status {
|
.field-status {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
right: var(--spacing-sm);
|
right: var(--spacing-sm);
|
||||||
|
|||||||
Reference in New Issue
Block a user