feat(client): 短信登录、JWT、Redis 与 Spug 短信及流式 Chat

- 新增客户端认证:短信发送/登录、access/refresh JWT、Guard/Strategy\n- Redis 存验证码;可配置 SMS_CODE_TTL_SECONDS;失败时回滚与明确错误\n- 短信改为 Spug 推送助手(code/targets/number/name),移除 UniSMS\n- Chat SSE 接口与 DTO;AppModule 挂载 RedisModule\n- 更新 README 与 project-solution 环境变量说明

Made-with: Cursor
This commit is contained in:
2026-04-21 06:30:50 +08:00
parent 61ac181b83
commit 6cc89062e1
15 changed files with 750 additions and 8 deletions

View File

@@ -1,7 +1,17 @@
import { Body, Controller, Inject, Post, Res } from '@nestjs/common';
import { Body, Controller, Inject, Post, Res, UseGuards } from '@nestjs/common';
import { FastifyReply } from 'fastify';
import { StreamChatRequest } from '@shared/ai-gateway/types/chat.types';
import { ProviderRouterService } from '@shared/ai-gateway/router/provider-router.service';
import { ClientJwtAuthGuard } from '../../auth/client-jwt-auth.guard';
import {
ApiBearerAuth,
ApiBody,
ApiConsumes,
ApiOkResponse,
ApiOperation,
ApiProduces,
ApiTags,
} from '@nestjs/swagger';
function formatSse(event: string, data: unknown) {
return `event: ${event}\ndata: ${JSON.stringify(data)}\n\n`;
@@ -11,6 +21,8 @@ function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
@ApiTags('Client Chat')
@ApiBearerAuth('access-token')
@Controller('client/v1/chat')
export class ChatController {
constructor(
@@ -19,6 +31,62 @@ export class ChatController {
) {}
@Post('completions/stream')
@UseGuards(ClientJwtAuthGuard)
@ApiOperation({ summary: '统一流式 Chat 接口SSE' })
@ApiConsumes('application/json')
@ApiProduces('text/event-stream')
@ApiBody({
schema: {
type: 'object',
description: '统一 chat 请求体,支持指定平台或自动路由',
properties: {
model: {
type: 'string',
description: '模型名(不传时 provider 使用默认模型)',
example: 'qwen-plus',
},
platform: {
type: 'string',
description: '目标平台auto 或不传表示自动路由)',
enum: ['auto', 'qwen', 'deepseek', 'volc'],
example: 'qwen',
},
messages: {
type: 'array',
description: '聊天消息列表',
items: {
type: 'object',
properties: {
role: {
type: 'string',
description: '消息角色',
enum: ['system', 'user', 'assistant'],
example: 'user',
},
content: {
type: 'string',
description: '消息文本内容',
example: '你是谁',
},
},
required: ['role', 'content'],
},
},
},
required: ['messages'],
},
})
@ApiOkResponse({
description: 'SSE 流式响应meta -> delta -> usage -> done',
schema: {
type: 'string',
example:
'event: meta\\ndata: {"requestId":"chatcmpl_xxx","platform":"qwen","model":"qwen-plus"}\\n\\n' +
'event: delta\\ndata: {"delta":"你好"}\\n\\n' +
'event: usage\\ndata: {"promptTokens":10,"completionTokens":20,"totalTokens":30}\\n\\n' +
'event: done\\ndata: {"finishReason":"stop"}\\n\\n',
},
})
async streamChat(
@Body() body: StreamChatRequest,
@Res() reply: FastifyReply,