/** * mock-upstream.mjs — 本地模拟 DashScope 上游端点(开发测试用,保留以供后续测试) * * 用途:在无真实 DashScope API Key 的情况下验证网关全链路。 * * 启动方式: * node apps/api/scripts/mock-upstream.mjs * 监听端口: 19090 * * 端点: * POST /v1/chat/completions * - 非流式:返回 OpenAI 格式 JSON,含 usage {prompt_tokens: 50000, completion_tokens: 80000} * - 流式 (stream: true):返回 SSE 格式,最后一条包含 usage */ import { createServer } from 'node:http'; const PORT = 19090; function randomId(prefix) { const rand = Math.random().toString(36).slice(2, 10); return `${prefix}_${rand}`; } function nowUnix() { return Math.floor(Date.now() / 1000); } function handleChatCompletions(body, res) { const isStream = body && body.stream === true; const model = (body && body.model) || 'unknown'; const id = randomId('chatcmpl'); const created = nowUnix(); if (isStream) { // SSE 流式响应 res.writeHead(200, { 'Content-Type': 'text/event-stream', 'Cache-Control': 'no-cache', Connection: 'keep-alive', }); const chunks = [ { id, object: 'chat.completion.chunk', created, model, choices: [ { index: 0, delta: { role: 'assistant', content: '' }, finish_reason: null, }, ], usage: null, }, { id, object: 'chat.completion.chunk', created, model, choices: [ { index: 0, delta: { content: '你好!我是由 DoTouch.AI 提供的 AI 助手,请问有什么可以帮助你的?', }, finish_reason: null, }, ], usage: null, }, { id, object: 'chat.completion.chunk', created, model, choices: [{ index: 0, delta: {}, finish_reason: 'stop' }], usage: { prompt_tokens: 50000, completion_tokens: 80000, total_tokens: 130, }, }, ]; for (const chunk of chunks) { res.write(`data: ${JSON.stringify(chunk)}\n\n`); } res.write('data: [DONE]\n\n'); res.end(); } else { // 非流式 JSON 响应 const response = { id, object: 'chat.completion', created, model, choices: [ { index: 0, message: { role: 'assistant', content: '你好!我是由 DoTouch.AI 提供的 AI 助手,请问有什么可以帮助你的?', }, finish_reason: 'stop', }, ], usage: { prompt_tokens: 50000, completion_tokens: 80000, total_tokens: 130, }, }; res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(response)); } } function parseBody(req) { return new Promise((resolve) => { let data = ''; req.on('data', (chunk) => { data += chunk; }); req.on('end', () => { try { resolve(JSON.parse(data)); } catch { resolve(null); } }); }); } const server = createServer(async (req, res) => { const parsedUrl = new URL(req.url || '/', `http://localhost:${PORT}`); if ( req.method === 'POST' && parsedUrl.pathname === '/v1/chat/completions' ) { const body = await parseBody(req); handleChatCompletions(body, res); } else if (req.method === 'GET' && parsedUrl.pathname === '/health') { res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ status: 'ok' })); } else { res.writeHead(404, { 'Content-Type': 'application/json' }); res.end( JSON.stringify({ error: { message: 'Not found', type: '404', code: '404' } }), ); } }); server.listen(PORT, () => { console.log(`🔧 Mock DashScope upstream running on http://localhost:${PORT}`); console.log(` POST /v1/chat/completions`); });