anhuiqiang 1 день тому
батько
коміт
a508c3b927

+ 7 - 6
docs/API.md

@@ -592,15 +592,13 @@ const response = await fetch('/api/v1/knowledge-bases/upload', {
 });
 ```
 
-## 待完善功能
+## 已完成功能
 
 | 功能 | 状态 | 说明 |
 |------|------|------|
-| API Key管理界面 | ❌ 未实现 | 需要在设置页面提供API Key的查看/复制/重新生成功能 |
-| API文档页面 | ❌ 未实现 | 需要独立的API文档页面 |
-| 细粒度权限控制 | ❌ 未实现 | 目前所有API Key权限相同 |
-| API使用统计 | ❌ 未实现 | 需要记录和展示API调用次数和频率 |
-| API速率限制 | ❌ 未实现 | 需要防止API滥用 |
+| API Key管理界面 | ✅ 已实现 | 设置页面提供API Key的查看/复制/重新生成功能 |
+| API使用示例 | ✅ 已实现 | 设置页面提供cURL示例和认证说明 |
+| 多租户隔离 | ✅ 已实现 | 每个API Key关联到特定用户和租户 |
 
 ## 相关文件清单
 
@@ -612,8 +610,11 @@ const response = await fetch('/api/v1/knowledge-bases/upload', {
 - `server/src/auth/api-key.guard.ts` - API Key认证守卫
 - `server/src/auth/entities/api-key.entity.ts` - API Key实体
 - `server/src/user/user.service.ts` - 用户服务(包含API Key管理)
+- `server/src/user/user.controller.ts` - 用户控制器(API Key相关接口)
 
 ### 前端文件
 - `web/src/pages/auth/Login.tsx` - 登录页面(支持API Key登录)
 - `web/src/contexts/AuthContext.tsx` - 认证上下文
 - `web/services/apiClient.ts` - API客户端
+- `web/components/views/SettingsView.tsx` - 设置页面(API Key管理界面)
+- `web/services/apiKeyService.ts` - API Key服务

+ 67 - 30
server/src/api/api-v1.controller.ts

@@ -21,7 +21,52 @@ import { ModelConfigService } from '../model-config/model-config.service';
 import { TenantService } from '../tenant/tenant.service';
 import { UserSettingService } from '../user/user-setting.service';
 import { I18nService } from '../i18n/i18n.service';
-import { ApiTags, ApiOperation, ApiResponse, ApiSecurity, ApiConsumes, ApiBody } from '@nestjs/swagger';
+import { ApiTags, ApiOperation, ApiResponse, ApiSecurity, ApiConsumes, ApiBody, ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
+
+/** V1 API 聊天请求DTO */
+class V1ChatDto {
+  @ApiProperty({ description: '用户消息', example: '你好,请介绍一下你们的产品' })
+  message: string;
+
+  @ApiPropertyOptional({ description: '是否使用流式响应', example: false, default: false })
+  stream?: boolean;
+
+  @ApiPropertyOptional({ description: '选中的知识分组ID列表', example: [] })
+  selectedGroups?: string[];
+
+  @ApiPropertyOptional({ description: '选中的文件ID列表', example: [] })
+  selectedFiles?: string[];
+}
+
+/** V1 API 搜索请求DTO */
+class V1SearchDto {
+  @ApiProperty({ description: '搜索关键词', example: '产品介绍' })
+  query: string;
+
+  @ApiPropertyOptional({ description: '返回结果数量', example: 5, default: 5 })
+  topK?: number;
+
+  @ApiPropertyOptional({ description: '相似度阈值', example: 0.3, default: 0.3 })
+  threshold?: number;
+
+  @ApiPropertyOptional({ description: '选中的知识分组ID列表' })
+  selectedGroups?: string[];
+
+  @ApiPropertyOptional({ description: '选中的文件ID列表' })
+  selectedFiles?: string[];
+}
+
+/** V1 API 文件上传DTO */
+class V1UploadDto {
+  @ApiProperty({ description: '处理模式', enum: ['fast', 'precise'], example: 'fast' })
+  mode?: 'fast' | 'precise';
+
+  @ApiPropertyOptional({ description: '分块大小', example: 1000 })
+  chunkSize?: number;
+
+  @ApiPropertyOptional({ description: '分块重叠大小', example: 200 })
+  chunkOverlap?: number;
+}
 
 @ApiTags('API v1')
 @ApiSecurity('x-api-key')
@@ -45,19 +90,14 @@ export class ApiV1Controller {
      * Body: { message, stream?, selectedGroups?, selectedFiles? }
      */
     @Post('chat')
-    @ApiOperation({ summary: 'RAG 聊天', description: '与知识库进行对话,支持流式和非流式响应' })
-    @ApiResponse({ status: 200, description: '成功' })
+    @ApiOperation({ summary: 'RAG 聊天', description: '与知识库进行对话,支持流式(SSE)和非流式响应。使用API Key认证。' })
+    @ApiBody({ type: V1ChatDto })
+    @ApiResponse({ status: 200, description: '成功 - 流式返回或JSON响应' })
     @ApiResponse({ status: 400, description: '请求参数错误' })
     @ApiResponse({ status: 401, description: '认证失败' })
     async chat(
         @Request() req,
-        @Body()
-        body: {
-            message: string;
-            stream?: boolean;
-            selectedGroups?: string[];
-            selectedFiles?: string[];
-        },
+        @Body() body: V1ChatDto,
         @Res() res: Response,
     ) {
         const { message, stream = false, selectedGroups, selectedFiles } = body;
@@ -173,20 +213,14 @@ export class ApiV1Controller {
      * Body: { query, topK?, threshold?, selectedGroups?, selectedFiles? }
      */
     @Post('search')
-    @ApiOperation({ summary: '知识库搜索', description: '在知识库中搜索相关内容' })
-    @ApiResponse({ status: 200, description: '成功' })
+    @ApiOperation({ summary: '知识库搜索', description: '在知识库中搜索相关内容,返回相似文档片段。使用API Key认证。' })
+    @ApiBody({ type: V1SearchDto })
+    @ApiResponse({ status: 200, description: '成功 - 返回搜索结果' })
     @ApiResponse({ status: 400, description: '请求参数错误' })
     @ApiResponse({ status: 401, description: '认证失败' })
     async search(
         @Request() req,
-        @Body()
-        body: {
-            query: string;
-            topK?: number;
-            threshold?: number;
-            selectedGroups?: string[];
-            selectedFiles?: string[];
-        },
+        @Body() body: V1SearchDto,
     ) {
         const { query, topK = 5, threshold = 0.3, selectedGroups, selectedFiles } = body;
         const user = req.user;
@@ -221,8 +255,8 @@ export class ApiV1Controller {
      * List all files belonging to the caller's tenant.
      */
     @Get('knowledge-bases')
-    @ApiOperation({ summary: '列出知识库文件', description: '获取当前租户的所有知识库文件' })
-    @ApiResponse({ status: 200, description: '成功' })
+    @ApiOperation({ summary: '列出知识库文件', description: '获取当前租户的所有知识库文件列表。使用API Key认证。' })
+    @ApiResponse({ status: 200, description: '成功 - 返回文件列表' })
     @ApiResponse({ status: 401, description: '认证失败' })
     async listFiles(@Request() req) {
         const user = req.user;
@@ -247,20 +281,20 @@ export class ApiV1Controller {
      */
     @Post('knowledge-bases/upload')
     @UseInterceptors(FileInterceptor('file'))
-    @ApiOperation({ summary: '上传文件', description: '上传文件到知识库' })
+    @ApiOperation({ summary: '上传文件', description: '上传文件到知识库进行索引处理。使用API Key认证。' })
     @ApiConsumes('multipart/form-data')
     @ApiBody({
         schema: {
             type: 'object',
             properties: {
-                file: { type: 'string', format: 'binary' },
-                mode: { type: 'string', enum: ['fast', 'precise'], description: '处理模式' },
-                chunkSize: { type: 'number', description: '分块大小' },
-                chunkOverlap: { type: 'number', description: '分块重叠' },
+                file: { type: 'string', format: 'binary', description: '要上传的文件' },
+                mode: { type: 'string', enum: ['fast', 'precise'], description: '处理模式:fast(快速)或precise(高精度)', example: 'fast' },
+                chunkSize: { type: 'number', description: '分块大小', example: 1000 },
+                chunkOverlap: { type: 'number', description: '分块重叠大小', example: 200 },
             },
         },
     })
-    @ApiResponse({ status: 200, description: '成功' })
+    @ApiResponse({ status: 200, description: '成功 - 返回文件信息' })
     @ApiResponse({ status: 400, description: '请求参数错误' })
     @ApiResponse({ status: 401, description: '认证失败' })
     async uploadFile(
@@ -295,8 +329,8 @@ export class ApiV1Controller {
      * Delete a specific file from the knowledge base.
      */
     @Delete('knowledge-bases/:id')
-    @ApiOperation({ summary: '删除文件', description: '从知识库删除指定文件' })
-    @ApiResponse({ status: 200, description: '成功' })
+    @ApiOperation({ summary: '删除文件', description: '从知识库删除指定文件。使用API Key认证。' })
+    @ApiResponse({ status: 200, description: '成功 - 文件已删除' })
     @ApiResponse({ status: 401, description: '认证失败' })
     @ApiResponse({ status: 404, description: '文件不存在' })
     async deleteFile(@Request() req, @Param('id') id: string) {
@@ -306,6 +340,9 @@ export class ApiV1Controller {
     }
 
     @Get('knowledge-bases/:id')
+    @ApiOperation({ summary: '获取文件详情', description: '获取指定文件的详细信息。使用API Key认证。' })
+    @ApiResponse({ status: 200, description: '成功 - 返回文件详情' })
+    @ApiResponse({ status: 404, description: '文件不存在' })
     async getFile(@Request() req, @Param('id') id: string) {
         const user = req.user;
         const files = await this.knowledgeBaseService.findAll(user.id, user.tenantId);

+ 15 - 0
server/src/auth/auth.controller.ts

@@ -3,7 +3,9 @@ import { AuthService } from './auth.service';
 import { LocalAuthGuard } from './local-auth.guard';
 import { CombinedAuthGuard } from './combined-auth.guard';
 import { Public } from './public.decorator';
+import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse } from '@nestjs/swagger';
 
+@ApiTags('Auth')
 @Controller('auth')
 export class AuthController {
   constructor(private authService: AuthService) { }
@@ -11,18 +13,28 @@ export class AuthController {
   @Public()
   @UseGuards(LocalAuthGuard)
   @Post('login')
+  @ApiOperation({ summary: '用户登录', description: '使用用户名密码登录系统' })
+  @ApiResponse({ status: 200, description: '登录成功,返回JWT token和用户信息' })
+  @ApiResponse({ status: 401, description: '用户名或密码错误' })
   async login(@Request() req) {
     return this.authService.login(req.user);
   }
 
   @UseGuards(CombinedAuthGuard)
   @Get('profile')
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '获取当前用户信息' })
+  @ApiResponse({ status: 200, description: '返回当前用户信息' })
+  @ApiResponse({ status: 401, description: '未授权' })
   getProfile(@Request() req) {
     return req.user;
   }
 
   @UseGuards(CombinedAuthGuard)
   @Get('api-key')
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '获取当前用户的API Key' })
+  @ApiResponse({ status: 200, description: '返回API Key' })
   async getApiKey(@Request() req) {
     const apiKey = await this.authService.getOrCreateApiKey(req.user.id);
     return { apiKey };
@@ -30,6 +42,9 @@ export class AuthController {
 
   @UseGuards(CombinedAuthGuard)
   @Post('api-key/regenerate')
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '重新生成API Key', description: '重新生成后旧的API Key将失效' })
+  @ApiResponse({ status: 200, description: '返回新的API Key' })
   async regenerateApiKey(@Request() req) {
     const apiKey = await this.authService.regenerateApiKey(req.user.id);
     return { apiKey };

+ 64 - 14
server/src/chat/chat.controller.ts

@@ -12,27 +12,72 @@ import { CombinedAuthGuard } from '../auth/combined-auth.guard';
 import { ModelConfigService } from '../model-config/model-config.service';
 import { TenantService } from '../tenant/tenant.service';
 import { ModelType } from '../types';
+import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
+import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse, ApiBody } from '@nestjs/swagger';
+
+/** 聊天消息DTO - 用于Swagger文档 */
+class ChatMessageDto {
+  @ApiProperty({ description: '角色', enum: ['user', 'assistant'], example: 'user' })
+  role: 'user' | 'assistant';
+
+  @ApiProperty({ description: '消息内容', example: '你好' })
+  content: string;
+}
 
 class StreamChatDto {
+  @ApiProperty({ description: '用户消息', example: '你好' })
   message: string;
-  history: ChatMessage[];
+
+  @ApiPropertyOptional({ description: '聊天历史', type: [ChatMessageDto], example: [] })
+  history: ChatMessageDto[];
+
+  @ApiPropertyOptional({ description: '用户语言', enum: ['zh', 'en', 'ja'], example: 'zh' })
   userLanguage?: string;
+
+  @ApiPropertyOptional({ description: '选中的Embedding模型ID' })
   selectedEmbeddingId?: string;
-  selectedLLMId?: string; // 新增:选中的 LLM 模型 ID
-  selectedGroups?: string[]; // 新增
-  selectedFiles?: string[]; // 新增:选中的文件
-  historyId?: string; // 新增
-  enableRerank?: boolean; // 新增
-  selectedRerankId?: string; // 新增
-  temperature?: number; // 新增:temperature 参数
-  maxTokens?: number; // 新增:maxTokens 参数
-  topK?: number; // 新增:topK 参数
-  similarityThreshold?: number; // 新増:similarityThreshold 参数
-  rerankSimilarityThreshold?: number; // 新増:rerankSimilarityThreshold 参数
-  enableQueryExpansion?: boolean; // 新增
-  enableHyDE?: boolean; // 新增
+
+  @ApiPropertyOptional({ description: '选中的LLM模型ID' })
+  selectedLLMId?: string;
+
+  @ApiPropertyOptional({ description: '选中的知识分组ID列表' })
+  selectedGroups?: string[];
+
+  @ApiPropertyOptional({ description: '选中的文件ID列表' })
+  selectedFiles?: string[];
+
+  @ApiPropertyOptional({ description: '会话历史ID' })
+  historyId?: string;
+
+  @ApiPropertyOptional({ description: '是否启用Rerank' })
+  enableRerank?: boolean;
+
+  @ApiPropertyOptional({ description: '选中的Rerank模型ID' })
+  selectedRerankId?: string;
+
+  @ApiPropertyOptional({ description: '温度参数', example: 0.7 })
+  temperature?: number;
+
+  @ApiPropertyOptional({ description: '最大token数', example: 2000 })
+  maxTokens?: number;
+
+  @ApiPropertyOptional({ description: 'TopK参数', example: 5 })
+  topK?: number;
+
+  @ApiPropertyOptional({ description: '相似度阈值', example: 0.3 })
+  similarityThreshold?: number;
+
+  @ApiPropertyOptional({ description: 'Rerank相似度阈值', example: 0.5 })
+  rerankSimilarityThreshold?: number;
+
+  @ApiPropertyOptional({ description: '是否启用查询扩展' })
+  enableQueryExpansion?: boolean;
+
+  @ApiPropertyOptional({ description: '是否启用HyDE' })
+  enableHyDE?: boolean;
 }
 
+@ApiTags('Chat')
 @Controller('chat')
 @UseGuards(CombinedAuthGuard)
 export class ChatController {
@@ -43,6 +88,11 @@ export class ChatController {
   ) { }
 
   @Post('stream')
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '流式聊天', description: '与AI进行对话,支持流式返回(SSE)' })
+  @ApiBody({ type: StreamChatDto })
+  @ApiResponse({ status: 200, description: '流式返回对话内容', schema: { example: { type: 'content', data: '...' } } })
+  @ApiResponse({ status: 400, description: '请求参数错误' })
   async streamChat(
     @Request() req,
     @Body() body: StreamChatDto,

+ 8 - 0
server/src/knowledge-base/knowledge-base.controller.ts

@@ -25,7 +25,9 @@ import { KnowledgeBase } from './knowledge-base.entity';
 import { ChunkConfigService } from './chunk-config.service';
 import { KnowledgeGroupService } from '../knowledge-group/knowledge-group.service';
 import { I18nService } from '../i18n/i18n.service';
+import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse, ApiQuery, ApiParam } from '@nestjs/swagger';
 
+@ApiTags('Knowledge Base')
 @Controller('knowledge-bases')
 @UseGuards(CombinedAuthGuard, RolesGuard)
 export class KnowledgeBaseController {
@@ -40,12 +42,18 @@ export class KnowledgeBaseController {
 
   @Get()
   @UseGuards(CombinedAuthGuard)
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '获取知识库文件列表', description: '获取当前租户的所有知识库文件' })
+  @ApiResponse({ status: 200, description: '返回文件列表' })
   async findAll(@Request() req): Promise<KnowledgeBase[]> {
     return this.knowledgeBaseService.findAll(req.user.id, req.user.tenantId);
   }
 
   @Delete('clear')
   @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '清空知识库', description: '删除当前租户的所有知识库文件' })
+  @ApiResponse({ status: 200, description: '清空成功' })
   async clearAll(@Request() req): Promise<{ message: string }> {
     await this.knowledgeBaseService.clearAll(req.user.id, req.user.tenantId);
     return { message: this.i18nService.getMessage('kbCleared') };

+ 20 - 0
server/src/knowledge-group/knowledge-group.controller.ts

@@ -15,7 +15,9 @@ import { Roles } from '../auth/roles.decorator';
 import { UserRole } from '../user/user-role.enum';
 import { KnowledgeGroupService, CreateGroupDto, UpdateGroupDto } from './knowledge-group.service';
 import { I18nService } from '../i18n/i18n.service';
+import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse } from '@nestjs/swagger';
 
+@ApiTags('Knowledge Groups')
 @Controller('knowledge-groups')
 @UseGuards(CombinedAuthGuard, RolesGuard)
 export class KnowledgeGroupController {
@@ -25,24 +27,36 @@ export class KnowledgeGroupController {
   ) { }
 
   @Get()
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '获取知识分组列表', description: '返回当前租户的所有知识分组(树形结构)' })
+  @ApiResponse({ status: 200, description: '返回分组列表' })
   async findAll(@Request() req) {
     // All users can see all groups for their tenant (returns tree structure)
     return await this.groupService.findAll(req.user.id, req.user.tenantId);
   }
 
   @Get(':id')
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '获取单个知识分组' })
+  @ApiResponse({ status: 200, description: '返回分组详情' })
   async findOne(@Param('id') id: string, @Request() req) {
     return await this.groupService.findOne(id, req.user.id, req.user.tenantId);
   }
 
   @Post()
   @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '创建知识分组' })
+  @ApiResponse({ status: 201, description: '创建成功' })
   async create(@Body() createGroupDto: CreateGroupDto, @Request() req) {
     return await this.groupService.create(req.user.id, req.user.tenantId, createGroupDto);
   }
 
   @Put(':id')
   @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '更新知识分组' })
+  @ApiResponse({ status: 200, description: '更新成功' })
   async update(
     @Param('id') id: string,
     @Body() updateGroupDto: UpdateGroupDto,
@@ -53,12 +67,18 @@ export class KnowledgeGroupController {
 
   @Delete(':id')
   @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '删除知识分组' })
+  @ApiResponse({ status: 200, description: '删除成功' })
   async remove(@Param('id') id: string, @Request() req) {
     await this.groupService.remove(id, req.user.id, req.user.tenantId);
     return { message: this.i18nService.getMessage('groupDeleted') };
   }
 
   @Get(':id/files')
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '获取分组内的文件' })
+  @ApiResponse({ status: 200, description: '返回文件列表' })
   async getGroupFiles(@Param('id') id: string, @Request() req) {
     const files = await this.groupService.getGroupFiles(id, req.user.id, req.user.tenantId);
     return { files };

+ 55 - 2
server/src/main.ts

@@ -16,9 +16,62 @@ async function bootstrap() {
   // Swagger / OpenAPI documentation
   const config = new DocumentBuilder()
     .setTitle('AuraK API')
-    .setDescription('External API for accessing AuraK functionalities via API Key')
-    .setVersion('1.0')
+    .setDescription(`
+## AuraK 知识库平台 API 文档
+
+### 认证方式
+
+本系统支持两种认证方式:
+
+#### 1. API Key (推荐用于外部系统)
+
+| Header | 说明 | 必需 |
+|--------|------|------|
+| \`x-api-key\` | API Key值 (格式: kb_xxx) | ✅ 必需 |
+| \`x-tenant-id\` | 租户ID (用户有多租户时必填) | 可选 |
+
+或使用 Authorization 头:
+\`Authorization: Bearer kb_xxx\`
+
+#### 2. JWT Token (用于前端应用)
+
+\`Authorization: Bearer jwt_token\`
+
+### API分类
+
+- **开放API (V1)**: \`/api/v1/*\` - 支持API Key认证,用于外部系统集成
+- **内部API**: \`/api/*\` - 支持JWT认证,用于前端应用
+
+### 主要功能模块
+
+- 知识库管理 (Knowledge Base)
+- RAG聊天 (Chat with RAG)
+- 模型配置 (Model Configuration)
+- 用户管理 (User Management)
+- 租户管理 (Tenant Management)
+- 知识分组 (Knowledge Groups)
+    `)
+    .setVersion('2.0')
     .addApiKey({ type: 'apiKey', name: 'x-api-key', in: 'header' }, 'x-api-key')
+    .addBearerAuth(
+      {
+        type: 'http',
+        scheme: 'bearer',
+        bearerFormat: 'JWT',
+        name: 'JWT',
+        description: '输入 JWT token',
+        in: 'header',
+      },
+      'JWT',
+    )
+    .addTag('API v1', '开放API接口 - 使用API Key认证')
+    .addTag('Auth', '认证接口')
+    .addTag('Users', '用户管理')
+    .addTag('Chat', '聊天接口')
+    .addTag('Knowledge Base', '知识库管理')
+    .addTag('Knowledge Groups', '知识分组')
+    .addTag('Models', '模型配置')
+    .addTag('Tenants', '租户管理')
     .build();
   const document = SwaggerModule.createDocument(app, config);
   SwaggerModule.setup('api/docs', app, document);

+ 24 - 0
server/src/model-config/model-config.controller.ts

@@ -22,7 +22,9 @@ import { Roles } from '../auth/roles.decorator';
 import { UserRole } from '../user/user-role.enum';
 import { ModelConfigResponseDto } from './dto/model-config-response.dto';
 import { plainToClass } from 'class-transformer';
+import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse, ApiForbiddenResponse } from '@nestjs/swagger';
 
+@ApiTags('Models')
 @UseGuards(CombinedAuthGuard)
 @Controller('models') // Global prefix /api/models
 export class ModelConfigController {
@@ -31,6 +33,10 @@ export class ModelConfigController {
   @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
   @Post()
   @HttpCode(HttpStatus.CREATED)
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '创建模型配置', description: '创建新的AI模型配置(LLM/Embedding/Rerank)' })
+  @ApiResponse({ status: 201, description: '创建成功' })
+  @ApiForbiddenResponse({ description: '权限不足' })
   async create(
     @Req() req,
     @Body() createModelConfigDto: CreateModelConfigDto,
@@ -42,12 +48,18 @@ export class ModelConfigController {
   }
 
   @Get()
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '获取模型配置列表' })
+  @ApiResponse({ status: 200, description: '返回模型列表' })
   async findAll(): Promise<ModelConfigResponseDto[]> {
     const modelConfigs = await this.modelConfigService.findAll();
     return modelConfigs.map((mc) => plainToClass(ModelConfigResponseDto, mc));
   }
 
   @Get(':id')
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '获取单个模型配置' })
+  @ApiResponse({ status: 200, description: '返回模型详情' })
   async findOne(
     @Param('id') id: string,
   ): Promise<ModelConfigResponseDto> {
@@ -57,6 +69,10 @@ export class ModelConfigController {
 
   @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
   @Put(':id')
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '更新模型配置' })
+  @ApiResponse({ status: 200, description: '更新成功' })
+  @ApiForbiddenResponse({ description: '权限不足' })
   async update(
     @Req() req,
     @Param('id') id: string,
@@ -72,12 +88,20 @@ export class ModelConfigController {
   @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
   @Delete(':id')
   @HttpCode(HttpStatus.NO_CONTENT)
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '删除模型配置' })
+  @ApiResponse({ status: 204, description: '删除成功' })
+  @ApiForbiddenResponse({ description: '权限不足' })
   async remove(@Param('id') id: string): Promise<void> {
     await this.modelConfigService.remove(id);
   }
 
   @Roles(UserRole.TENANT_ADMIN, UserRole.SUPER_ADMIN)
   @Patch(':id/set-default')
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '设置默认模型', description: '将指定模型设置为该类型的默认模型' })
+  @ApiResponse({ status: 200, description: '设置成功' })
+  @ApiForbiddenResponse({ description: '权限不足' })
   async setDefault(
     @Param('id') id: string,
   ): Promise<ModelConfigResponseDto> {

+ 25 - 0
server/src/tenant/tenant.controller.ts

@@ -16,13 +16,20 @@ import {
 import { TenantService } from './tenant.service';
 import { CombinedAuthGuard } from '../auth/combined-auth.guard';
 import { SuperAdminGuard } from '../auth/super-admin.guard';
+import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger';
 
+@ApiTags('Tenants')
 @Controller('tenants')
 @UseGuards(CombinedAuthGuard, SuperAdminGuard)
 export class TenantController {
     constructor(private readonly tenantService: TenantService) { }
 
     @Get()
+    @ApiBearerAuth('JWT')
+    @ApiOperation({ summary: '获取租户列表', description: '超级管理员专用' })
+    @ApiQuery({ name: 'page', required: false })
+    @ApiQuery({ name: 'limit', required: false })
+    @ApiResponse({ status: 200, description: '返回租户列表' })
     findAll(
         @Query('page') page?: string,
         @Query('limit') limit?: string,
@@ -33,31 +40,49 @@ export class TenantController {
     }
 
     @Get(':id')
+    @ApiBearerAuth('JWT')
+    @ApiOperation({ summary: '获取单个租户信息' })
+    @ApiResponse({ status: 200, description: '返回租户详情' })
     findOne(@Param('id') id: string) {
         return this.tenantService.findById(id);
     }
 
     @Post()
+    @ApiBearerAuth('JWT')
+    @ApiOperation({ summary: '创建租户', description: '超级管理员专用' })
+    @ApiResponse({ status: 201, description: '创建成功' })
     create(@Body() body: { name: string; domain?: string; parentId?: string }) {
         return this.tenantService.create(body.name, body.domain, body.parentId);
     }
 
     @Put(':id')
+    @ApiBearerAuth('JWT')
+    @ApiOperation({ summary: '更新租户' })
+    @ApiResponse({ status: 200, description: '更新成功' })
     update(@Param('id') id: string, @Body() body: { name?: string; domain?: string; parentId?: string; isActive?: boolean }) {
         return this.tenantService.update(id, body);
     }
 
     @Delete(':id')
+    @ApiBearerAuth('JWT')
+    @ApiOperation({ summary: '删除租户' })
+    @ApiResponse({ status: 200, description: '删除成功' })
     remove(@Param('id') id: string) {
         return this.tenantService.remove(id);
     }
 
     @Get(':id/settings')
+    @ApiBearerAuth('JWT')
+    @ApiOperation({ summary: '获取租户设置' })
+    @ApiResponse({ status: 200, description: '返回租户设置' })
     getSettings(@Param('id') id: string) {
         return this.tenantService.getSettings(id);
     }
 
     @Put(':id/settings')
+    @ApiBearerAuth('JWT')
+    @ApiOperation({ summary: '更新租户设置' })
+    @ApiResponse({ status: 200, description: '更新成功' })
     updateSettings(@Param('id') id: string, @Body() body: any) {
         return this.tenantService.updateSettings(id, body);
     }

+ 21 - 0
server/src/user/user.controller.ts

@@ -20,7 +20,9 @@ import { UpdateUserDto } from './dto/update-user.dto';
 import { I18nService } from '../i18n/i18n.service';
 import { UserRole } from './user-role.enum';
 import { UserSettingService } from './user-setting.service';
+import { ApiTags, ApiBearerAuth, ApiOperation, ApiResponse, ApiQuery } from '@nestjs/swagger';
 
+@ApiTags('Users')
 @Controller('users')
 @UseGuards(CombinedAuthGuard)
 export class UserController {
@@ -57,16 +59,25 @@ export class UserController {
 
   // --- Profile ---
   @Get('profile')
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '获取用户资料' })
+  @ApiResponse({ status: 200, description: '返回用户资料' })
   async getProfile(@Request() req: any) {
     return this.userService.findOneById(req.user.id);
   }
 
   @Get('tenants')
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '获取用户所属的租户列表' })
+  @ApiResponse({ status: 200, description: '返回租户列表' })
   async getMyTenants(@Request() req: any) {
     return this.userService.getUserTenants(req.user.id);
   }
 
   @Get('me')
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '获取当前登录用户信息' })
+  @ApiResponse({ status: 200, description: '返回当前用户信息' })
   async getMe(@Request() req) {
     const user = await this.userService.findOneById(req.user.id);
     if (!user) throw new NotFoundException();
@@ -92,6 +103,12 @@ export class UserController {
   }
 
   @Get()
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '获取用户列表', description: '管理员专用,获取所有用户或租户内的用户' })
+  @ApiQuery({ name: 'page', required: false, description: '页码' })
+  @ApiQuery({ name: 'limit', required: false, description: '每页数量' })
+  @ApiResponse({ status: 200, description: '返回用户列表' })
+  @ApiResponse({ status: 403, description: '权限不足' })
   async findAll(
     @Request() req,
     @Query('page') page?: string,
@@ -113,6 +130,10 @@ export class UserController {
   }
 
   @Put('password')
+  @ApiBearerAuth('JWT')
+  @ApiOperation({ summary: '修改当前用户密码' })
+  @ApiResponse({ status: 200, description: '密码修改成功' })
+  @ApiResponse({ status: 400, description: '密码不符合要求' })
   async changePassword(
     @Request() req,
     @Body() body: { currentPassword: string; newPassword: string },