Files
bigwo/test2/server/app.js

163 lines
5.3 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

require('dotenv').config();
const http = require('http');
const express = require('express');
const cors = require('cors');
const path = require('path');
const db = require('./db');
const voiceRoutes = require('./routes/voice');
const chatRoutes = require('./routes/chat');
const sessionRoutes = require('./routes/session');
const { setupNativeVoiceGateway } = require('./services/nativeVoiceGateway');
// ========== 环境变量校验 ==========
function validateEnv() {
const required = [
{ key: 'VOLC_S2S_APP_ID', desc: 'S2S 端到端语音 AppID' },
{ key: 'VOLC_S2S_TOKEN', desc: 'S2S 端到端语音 Token' },
{ key: 'VOLC_ARK_ENDPOINT_ID', desc: '方舟 LLM 推理接入点 ID' },
];
const missing = [];
const placeholder = [];
for (const { key, desc } of required) {
const val = process.env[key];
if (!val) {
missing.push({ key, desc });
} else if (val.startsWith('your_')) {
placeholder.push({ key, desc });
}
}
if (missing.length > 0) {
console.warn('\n⚠ 以下必需环境变量未设置:');
missing.forEach(({ key, desc }) => console.warn(`${key}${desc}`));
}
if (placeholder.length > 0) {
console.warn('\n⚠ 以下环境变量仍为占位符Mock 模式):');
placeholder.forEach(({ key, desc }) => console.warn(` 🔶 ${key}${desc}`));
}
const ready = missing.length === 0 && placeholder.length === 0;
if (ready) {
console.log('✅ 所有环境变量已配置');
} else {
console.warn('\n💡 请编辑 server/.env 填入真实凭证,当前将以 Mock 模式运行');
}
// 可选变量提示
const optional = [
{ key: 'COZE_API_TOKEN', desc: 'Coze 智能体(文字对话)' },
{ key: 'COZE_BOT_ID', desc: 'Coze Bot ID' },
{ key: 'VOLC_WEBSEARCH_API_KEY', desc: '联网搜索' },
{ key: 'VOLC_S2S_SPEAKER_ID', desc: '自定义音色' },
{ key: 'VOLC_ARK_KNOWLEDGE_BASE_IDS', desc: '方舟私域知识库(语音)' },
];
const configuredOptional = optional.filter(({ key }) => {
const v = process.env[key];
return v && !v.startsWith('your_');
});
if (configuredOptional.length > 0) {
console.log(`📦 可选功能已启用:${configuredOptional.map(o => o.desc).join('、')}`);
}
return ready;
}
// ========== Express 应用 ==========
const app = express();
const server = http.createServer(app);
const PORT = process.env.PORT || 3001;
app.use(cors());
app.use(express.json({ limit: '1mb' }));
app.use(express.urlencoded({ extended: true, limit: '1mb' }));
// 请求日志中间件
app.use((req, res, next) => {
const start = Date.now();
res.on('finish', () => {
const ms = Date.now() - start;
if (req.path.startsWith('/api/')) {
console.log(`[${req.method}] ${req.path}${res.statusCode} (${ms}ms)`);
}
});
next();
});
app.use('/api/voice', voiceRoutes);
app.use('/api/chat', chatRoutes);
app.use('/api/session', sessionRoutes);
app.get('/api/health', (req, res) => {
const envReady = !process.env.VOLC_S2S_APP_ID?.startsWith('your_');
res.json({
status: 'ok',
mode: 's2s-hybrid',
apiVersion: '2024-12-01',
configured: envReady,
features: {
voiceChat: true,
textChat: !!process.env.COZE_API_TOKEN && !process.env.COZE_API_TOKEN.startsWith('your_') && !!process.env.COZE_BOT_ID && !process.env.COZE_BOT_ID.startsWith('your_'),
textChatProvider: 'coze',
webSearch: !!process.env.VOLC_WEBSEARCH_API_KEY && !process.env.VOLC_WEBSEARCH_API_KEY.startsWith('your_'),
customSpeaker: !!process.env.VOLC_S2S_SPEAKER_ID && !process.env.VOLC_S2S_SPEAKER_ID.startsWith('your_'),
arkKnowledgeBase: !!process.env.VOLC_ARK_KNOWLEDGE_BASE_IDS && !process.env.VOLC_ARK_KNOWLEDGE_BASE_IDS.startsWith('your_'),
},
});
});
// 静态文件服务
app.use(express.static('../client/dist'));
// 处理单页应用路由
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../client/dist/index.html'));
});
// 统一错误处理中间件
app.use((err, req, res, _next) => {
console.error(`[Error] ${req.method} ${req.path}:`, err.message);
const status = err.status || 500;
res.status(status).json({
success: false,
error: err.message || 'Internal Server Error',
...(process.env.NODE_ENV !== 'production' && { stack: err.stack }),
});
});
// 404 处理
app.use((req, res) => {
res.status(404).json({ success: false, error: `Route not found: ${req.method} ${req.path}` });
});
// 启动服务(先初始化数据库)
async function start() {
try {
await db.initialize();
} catch (err) {
console.error('[DB] MySQL initialization failed:', err.message);
console.warn('[DB] Continuing without database — context switching will use in-memory fallback');
}
if (process.env.ENABLE_NATIVE_VOICE_GATEWAY !== 'false') {
setupNativeVoiceGateway(server);
console.log('[NativeVoice] Gateway enabled at /ws/realtime-dialog');
} else {
console.log('[NativeVoice] Gateway disabled (ENABLE_NATIVE_VOICE_GATEWAY=false)');
}
server.listen(PORT, () => {
console.log('\n========================================');
console.log(` 🚀 Voice Chat Backend`);
console.log(` 📡 http://localhost:${PORT}`);
console.log(` 🔧 Mode: S2S端到端 + LLM混合 (API v2024-12-01)`);
console.log('========================================\n');
validateEnv();
console.log('');
});
}
start();