| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162 |
- /**
- * 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`);
- });
|