feat: 完善API文档系统和Swagger集成
- 集成Swagger文档生成和UI界面 - 为所有API端点添加详细的文档注解 - 添加统一的响应装饰器和DTO类型定义 - 优化API路由结构和全局验证配置 - 新增文档生成和服务脚本命令
This commit is contained in:
198
docs/API_DOCUMENTATION_SETUP.md
Normal file
198
docs/API_DOCUMENTATION_SETUP.md
Normal file
@@ -0,0 +1,198 @@
|
|||||||
|
# API 文档配置指南
|
||||||
|
|
||||||
|
## 概览
|
||||||
|
|
||||||
|
本项目使用 Swagger/OpenAPI 3.0 规范生成完整的 API 文档,支持在线测试和接口调试。
|
||||||
|
|
||||||
|
## 访问文档
|
||||||
|
|
||||||
|
启动项目后,可通过以下地址访问API文档:
|
||||||
|
|
||||||
|
- **交互式文档**: http://localhost:3000/api/docs
|
||||||
|
- **JSON格式**: http://localhost:3000/api/docs-json
|
||||||
|
- **YAML格式**: http://localhost:3000/api/docs-yaml
|
||||||
|
|
||||||
|
## 文档配置
|
||||||
|
|
||||||
|
### 1. 主配置文件
|
||||||
|
|
||||||
|
文件位置: `src/config/swagger.config.ts`
|
||||||
|
|
||||||
|
包含以下配置:
|
||||||
|
- API基础信息(标题、描述、版本)
|
||||||
|
- JWT认证配置
|
||||||
|
- 标签分类
|
||||||
|
- 多环境服务器配置
|
||||||
|
- UI自定义选项
|
||||||
|
|
||||||
|
### 2. DTO文档化
|
||||||
|
|
||||||
|
#### 通用响应格式
|
||||||
|
- `src/dto/common-response.dto.ts` - 统一响应格式
|
||||||
|
- `src/dto/error-response.dto.ts` - 错误响应格式
|
||||||
|
|
||||||
|
#### 模板系统DTO
|
||||||
|
- `src/dto/template.dto.ts` - 模板相关数据传输对象
|
||||||
|
- `CreateTemplateDto` - 创建模板
|
||||||
|
- `UpdateTemplateDto` - 更新模板
|
||||||
|
- `TemplateExecuteDto` - 执行模板
|
||||||
|
- `TemplateListDto` - 模板列表
|
||||||
|
- `BatchExecuteDto` - 批量执行
|
||||||
|
|
||||||
|
#### 平台用户DTO
|
||||||
|
- `src/platform/dto/platform-login.dto.ts` - 平台登录相关
|
||||||
|
|
||||||
|
### 3. Controller文档化
|
||||||
|
|
||||||
|
#### 装饰器使用
|
||||||
|
```typescript
|
||||||
|
@ApiTags('功能模块') // 分组标签
|
||||||
|
@ApiOperation({ summary: '接口摘要', description: '详细描述' })
|
||||||
|
@ApiResponse({ status: 200, description: '成功', type: ResponseDto })
|
||||||
|
@ApiParam({ name: 'id', description: '参数描述' })
|
||||||
|
@ApiQuery({ name: 'filter', required: false })
|
||||||
|
@ApiBody({ type: RequestDto })
|
||||||
|
@ApiBearerAuth('JWT-auth') // JWT认证
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 通用装饰器
|
||||||
|
- `src/decorators/api-common-responses.decorator.ts`
|
||||||
|
- `@ApiCommonResponses()` - 通用错误响应
|
||||||
|
- `@ApiAuthResponses()` - 认证相关错误
|
||||||
|
|
||||||
|
## 支持的功能模块
|
||||||
|
|
||||||
|
### 1. 用户管理 (`用户管理`)
|
||||||
|
- 多平台统一登录
|
||||||
|
- 用户信息获取与更新
|
||||||
|
- 平台账号绑定/解绑
|
||||||
|
- 支持的平台列表查询
|
||||||
|
|
||||||
|
### 2. AI模板系统 (`AI模板系统`)
|
||||||
|
- 模板列表查询(支持类型筛选)
|
||||||
|
- 模板详情获取
|
||||||
|
- 模板执行(单个/批量)
|
||||||
|
- 模板推荐
|
||||||
|
- 模板管理(创建/更新/删除)
|
||||||
|
|
||||||
|
### 3. 平台适配 (`平台适配`)
|
||||||
|
- 各平台特定接口
|
||||||
|
- 数据同步功能
|
||||||
|
|
||||||
|
### 4. 积分系统 (`积分系统`)
|
||||||
|
- 积分获取、消耗、查询管理
|
||||||
|
|
||||||
|
### 5. 扩展服务 (`扩展服务`)
|
||||||
|
- 预留的扩展功能接口
|
||||||
|
|
||||||
|
## API 规范
|
||||||
|
|
||||||
|
### 统一响应格式
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": {},
|
||||||
|
"timestamp": 1703001000000,
|
||||||
|
"traceId": "trace-uuid"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 错误码规范
|
||||||
|
|
||||||
|
- **1000-1999**: 系统级错误
|
||||||
|
- **2000-2999**: 业务级错误
|
||||||
|
- **3000-3999**: 平台特定错误
|
||||||
|
- **4000-4999**: 客户端错误
|
||||||
|
- **5000-5999**: 服务端错误
|
||||||
|
|
||||||
|
### 认证方式
|
||||||
|
|
||||||
|
使用 JWT Bearer Token 进行接口认证:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||||
|
```
|
||||||
|
|
||||||
|
## 开发指南
|
||||||
|
|
||||||
|
### 1. 添加新接口文档
|
||||||
|
|
||||||
|
1. **创建DTO类**:
|
||||||
|
```typescript
|
||||||
|
export class NewFeatureDto {
|
||||||
|
@ApiProperty({ description: '字段描述', example: '示例值' })
|
||||||
|
@IsString()
|
||||||
|
field: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Controller添加装饰器**:
|
||||||
|
```typescript
|
||||||
|
@ApiTags('功能模块')
|
||||||
|
@ApiCommonResponses()
|
||||||
|
export class NewController {
|
||||||
|
@Post()
|
||||||
|
@ApiOperation({ summary: '接口摘要', description: '详细描述' })
|
||||||
|
@ApiBody({ type: NewFeatureDto })
|
||||||
|
@ApiResponse({ status: 200, description: '成功' })
|
||||||
|
async newEndpoint() {}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. 生成静态文档
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 生成 Swagger JSON 文档
|
||||||
|
npm run docs:generate
|
||||||
|
|
||||||
|
# 启动文档服务
|
||||||
|
npm run docs:serve
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. 文档验证
|
||||||
|
|
||||||
|
启动项目后访问 http://localhost:3000/api/docs 验证:
|
||||||
|
- 接口分组是否正确
|
||||||
|
- 参数描述是否完整
|
||||||
|
- 响应格式是否标准
|
||||||
|
- 认证配置是否生效
|
||||||
|
|
||||||
|
## 部署注意事项
|
||||||
|
|
||||||
|
### 生产环境配置
|
||||||
|
|
||||||
|
生产环境默认禁用 Swagger 文档,确保安全性:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// 仅在非生产环境启用 Swagger
|
||||||
|
if (process.env.NODE_ENV !== 'production') {
|
||||||
|
setupSwagger(app);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 自定义域名配置
|
||||||
|
|
||||||
|
需要在 `swagger.config.ts` 中更新服务器配置:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
.addServer('https://your-domain.com', '生产环境')
|
||||||
|
```
|
||||||
|
|
||||||
|
## 维护指南
|
||||||
|
|
||||||
|
### 1. 定期更新
|
||||||
|
- 新增接口后及时添加文档
|
||||||
|
- 保持DTO和实际接口同步
|
||||||
|
- 更新示例数据和错误信息
|
||||||
|
|
||||||
|
### 2. 版本管理
|
||||||
|
- API版本变更时更新文档版本号
|
||||||
|
- 重要变更添加changelog说明
|
||||||
|
- 保持向后兼容性文档说明
|
||||||
|
|
||||||
|
### 3. 团队协作
|
||||||
|
- 代码审查包含文档完整性检查
|
||||||
|
- 新团队成员参考文档快速上手
|
||||||
|
- 前端开发基于文档进行接口对接
|
||||||
1972
docs/api-spec.json
Normal file
1972
docs/api-spec.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -25,7 +25,9 @@
|
|||||||
"migration:run": "npm run typeorm -- migration:run",
|
"migration:run": "npm run typeorm -- migration:run",
|
||||||
"migration:revert": "npm run typeorm -- migration:revert",
|
"migration:revert": "npm run typeorm -- migration:revert",
|
||||||
"schema:sync": "npm run typeorm -- schema:sync",
|
"schema:sync": "npm run typeorm -- schema:sync",
|
||||||
"schema:drop": "npm run typeorm -- schema:drop"
|
"schema:drop": "npm run typeorm -- schema:drop",
|
||||||
|
"docs:generate": "nest build && node dist/generate-swagger-json.js",
|
||||||
|
"docs:serve": "nest start --watch"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@nestjs/axios": "^4.0.1",
|
"@nestjs/axios": "^4.0.1",
|
||||||
|
|||||||
79
src/config/swagger.config.ts
Normal file
79
src/config/swagger.config.ts
Normal file
@@ -0,0 +1,79 @@
|
|||||||
|
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||||||
|
import { INestApplication } from '@nestjs/common';
|
||||||
|
|
||||||
|
export function setupSwagger(app: INestApplication): void {
|
||||||
|
const config = new DocumentBuilder()
|
||||||
|
.setTitle('多平台小程序统一后台API')
|
||||||
|
.setDescription(`
|
||||||
|
## 功能特性
|
||||||
|
- 🔐 统一用户认证 (微信/支付宝/百度/字节跳动等)
|
||||||
|
- 🎨 AI模板系统 (图片/视频生成)
|
||||||
|
- 💰 积分系统 (积分获取、消耗、管理)
|
||||||
|
- 📱 多平台适配 (8大平台支持)
|
||||||
|
- 🧩 扩展数据存储 (灵活的JSON数据)
|
||||||
|
|
||||||
|
## 认证方式
|
||||||
|
使用JWT Bearer Token进行API认证
|
||||||
|
|
||||||
|
## 响应格式
|
||||||
|
所有API响应都遵循统一格式:
|
||||||
|
\`\`\`json
|
||||||
|
{
|
||||||
|
"code": 200,
|
||||||
|
"message": "success",
|
||||||
|
"data": {},
|
||||||
|
"timestamp": 1703001000000,
|
||||||
|
"traceId": "trace-uuid"
|
||||||
|
}
|
||||||
|
\`\`\`
|
||||||
|
`)
|
||||||
|
.setVersion('1.0.0')
|
||||||
|
.setContact('开发团队', 'https://example.com', 'dev@example.com')
|
||||||
|
.setLicense('MIT', 'https://opensource.org/licenses/MIT')
|
||||||
|
.addBearerAuth(
|
||||||
|
{
|
||||||
|
type: 'http',
|
||||||
|
scheme: 'bearer',
|
||||||
|
bearerFormat: 'JWT',
|
||||||
|
name: 'JWT',
|
||||||
|
description: 'Enter JWT token',
|
||||||
|
in: 'header',
|
||||||
|
},
|
||||||
|
'JWT-auth',
|
||||||
|
)
|
||||||
|
.addTag('用户管理', '用户注册、登录、信息管理')
|
||||||
|
.addTag('平台适配', '各平台特定接口和数据同步')
|
||||||
|
.addTag('AI模板系统', 'AI图片/视频生成模板管理')
|
||||||
|
.addTag('积分系统', '积分获取、消耗、查询管理')
|
||||||
|
.addTag('扩展服务', '预留的扩展功能接口')
|
||||||
|
.addServer('http://localhost:3000', '开发环境')
|
||||||
|
.addServer('https://api-dev.example.com', '测试环境')
|
||||||
|
.addServer('https://api.example.com', '生产环境')
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const document = SwaggerModule.createDocument(app as any, config, {
|
||||||
|
operationIdFactory: (controllerKey: string, methodKey: string) => methodKey,
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('🔧 Setting up Swagger documentation...');
|
||||||
|
SwaggerModule.setup('docs', app as any, document, {
|
||||||
|
swaggerOptions: {
|
||||||
|
persistAuthorization: true,
|
||||||
|
tagsSorter: 'alpha',
|
||||||
|
operationsSorter: 'alpha',
|
||||||
|
docExpansion: 'none',
|
||||||
|
filter: true,
|
||||||
|
showRequestDuration: true,
|
||||||
|
},
|
||||||
|
customSiteTitle: '多平台API文档',
|
||||||
|
customfavIcon: '/favicon.ico',
|
||||||
|
customJs: [
|
||||||
|
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-bundle.min.js',
|
||||||
|
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui-standalone-preset.min.js',
|
||||||
|
],
|
||||||
|
customCssUrl: [
|
||||||
|
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui.min.css',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
console.log('✅ Swagger documentation setup completed at /api/docs');
|
||||||
|
}
|
||||||
@@ -11,21 +11,35 @@ import {
|
|||||||
HttpException,
|
HttpException,
|
||||||
HttpStatus,
|
HttpStatus,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import {
|
||||||
|
ApiTags,
|
||||||
|
ApiOperation,
|
||||||
|
ApiResponse,
|
||||||
|
ApiParam,
|
||||||
|
ApiQuery,
|
||||||
|
ApiBody,
|
||||||
|
} from '@nestjs/swagger';
|
||||||
import { N8nTemplateFactoryService } from '../services/n8n-template-factory.service';
|
import { N8nTemplateFactoryService } from '../services/n8n-template-factory.service';
|
||||||
import {
|
import {
|
||||||
N8nTemplateEntity,
|
N8nTemplateEntity,
|
||||||
TemplateType,
|
TemplateType,
|
||||||
} from '../entities/n8n-template.entity';
|
} from '../entities/n8n-template.entity';
|
||||||
import { CreateTemplateDto, UpdateTemplateDto } from '../dto/template.dto';
|
import {
|
||||||
|
CreateTemplateDto,
|
||||||
|
UpdateTemplateDto,
|
||||||
|
TemplateExecuteDto,
|
||||||
|
TemplateExecuteResponseDto,
|
||||||
|
TemplateListDto,
|
||||||
|
BatchExecuteDto,
|
||||||
|
} from '../dto/template.dto';
|
||||||
import { TemplateExecutionEntity } from '../entities/template-execution.entity';
|
import { TemplateExecutionEntity } from '../entities/template-execution.entity';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { Repository } from 'typeorm';
|
import { Repository } from 'typeorm';
|
||||||
|
import { ApiCommonResponses } from '../decorators/api-common-responses.decorator';
|
||||||
|
|
||||||
/**
|
@ApiTags('AI模板系统')
|
||||||
* 模板控制器
|
|
||||||
* 提供模板相关的API接口,支持混合式架构的动态模板管理
|
|
||||||
*/
|
|
||||||
@Controller('templates')
|
@Controller('templates')
|
||||||
|
@ApiCommonResponses()
|
||||||
export class TemplateController {
|
export class TemplateController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly templateFactory: N8nTemplateFactoryService,
|
private readonly templateFactory: N8nTemplateFactoryService,
|
||||||
@@ -35,13 +49,29 @@ export class TemplateController {
|
|||||||
private readonly executionRepository: Repository<TemplateExecutionEntity>,
|
private readonly executionRepository: Repository<TemplateExecutionEntity>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行模板 - 通过模板ID
|
|
||||||
* @param templateId 模板ID
|
|
||||||
* @param body 请求体,包含imageUrl
|
|
||||||
* @returns 执行结果
|
|
||||||
*/
|
|
||||||
@Post(':templateId/execute')
|
@Post(':templateId/execute')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '执行模板生成',
|
||||||
|
description: '根据模板ID执行AI生成任务,支持图片和视频生成',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'templateId', description: '模板ID', example: 1 })
|
||||||
|
@ApiBody({ type: TemplateExecuteDto })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '执行成功',
|
||||||
|
type: TemplateExecuteResponseDto,
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 400,
|
||||||
|
description: '参数错误',
|
||||||
|
schema: {
|
||||||
|
example: {
|
||||||
|
code: 400,
|
||||||
|
message: 'imageUrl is required',
|
||||||
|
data: null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
async executeTemplateById(
|
async executeTemplateById(
|
||||||
@Param('templateId', ParseIntPipe) templateId: number,
|
@Param('templateId', ParseIntPipe) templateId: number,
|
||||||
@Body() body: { imageUrl: string },
|
@Body() body: { imageUrl: string },
|
||||||
@@ -76,13 +106,18 @@ export class TemplateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 执行模板 - 通过模板代码
|
|
||||||
* @param code 模板代码
|
|
||||||
* @param body 请求体,包含imageUrl
|
|
||||||
* @returns 执行结果
|
|
||||||
*/
|
|
||||||
@Post('code/:code/execute')
|
@Post('code/:code/execute')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '通过代码执行模板',
|
||||||
|
description: '根据模板代码执行AI生成任务,支持图片和视频生成',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'code', description: '模板代码', example: 'character_figurine_v1' })
|
||||||
|
@ApiBody({ type: TemplateExecuteDto })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '执行成功',
|
||||||
|
type: TemplateExecuteResponseDto,
|
||||||
|
})
|
||||||
async executeTemplateByCode(
|
async executeTemplateByCode(
|
||||||
@Param('code') code: string,
|
@Param('code') code: string,
|
||||||
@Body() body: { imageUrl: string },
|
@Body() body: { imageUrl: string },
|
||||||
@@ -116,13 +151,30 @@ export class TemplateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量执行模板
|
|
||||||
* @param templateId 模板ID
|
|
||||||
* @param body 包含多个imageUrl的数组
|
|
||||||
* @returns 批量执行结果
|
|
||||||
*/
|
|
||||||
@Post(':templateId/batch-execute')
|
@Post(':templateId/batch-execute')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '批量执行模板',
|
||||||
|
description: '批量执行模板生成任务,支持多张图片同时处理',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'templateId', description: '模板ID', example: 1 })
|
||||||
|
@ApiBody({ type: BatchExecuteDto })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '批量执行成功',
|
||||||
|
schema: {
|
||||||
|
example: {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
templateId: 1,
|
||||||
|
totalCount: 2,
|
||||||
|
results: [
|
||||||
|
{ inputUrl: 'image1.jpg', outputUrl: 'output1.jpg' },
|
||||||
|
{ inputUrl: 'image2.jpg', outputUrl: 'output2.jpg' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
async batchExecuteTemplate(
|
async batchExecuteTemplate(
|
||||||
@Param('templateId', ParseIntPipe) templateId: number,
|
@Param('templateId', ParseIntPipe) templateId: number,
|
||||||
@Body() body: { imageUrls: string[] },
|
@Body() body: { imageUrls: string[] },
|
||||||
@@ -161,11 +213,16 @@ export class TemplateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有模板列表
|
|
||||||
* @returns 模板配置列表
|
|
||||||
*/
|
|
||||||
@Get()
|
@Get()
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '获取所有模板列表',
|
||||||
|
description: '获取所有可用的AI生成模板,支持按类型筛选',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '获取成功',
|
||||||
|
type: [TemplateListDto],
|
||||||
|
})
|
||||||
async getAllTemplates() {
|
async getAllTemplates() {
|
||||||
try {
|
try {
|
||||||
const templates = await this.templateFactory.getAllTemplates();
|
const templates = await this.templateFactory.getAllTemplates();
|
||||||
@@ -197,11 +254,16 @@ export class TemplateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取图片类型模板
|
|
||||||
* @returns 图片模板列表
|
|
||||||
*/
|
|
||||||
@Get('image')
|
@Get('image')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '获取图片模板列表',
|
||||||
|
description: '获取所有图片类型的AI生成模板',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '获取成功',
|
||||||
|
type: [TemplateListDto],
|
||||||
|
})
|
||||||
async getImageTemplates() {
|
async getImageTemplates() {
|
||||||
try {
|
try {
|
||||||
const templates = await this.templateFactory.getImageTemplates();
|
const templates = await this.templateFactory.getImageTemplates();
|
||||||
@@ -217,11 +279,16 @@ export class TemplateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取视频类型模板
|
|
||||||
* @returns 视频模板列表
|
|
||||||
*/
|
|
||||||
@Get('video')
|
@Get('video')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '获取视频模板列表',
|
||||||
|
description: '获取所有视频类型的AI生成模板',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '获取成功',
|
||||||
|
type: [TemplateListDto],
|
||||||
|
})
|
||||||
async getVideoTemplates() {
|
async getVideoTemplates() {
|
||||||
try {
|
try {
|
||||||
const templates = await this.templateFactory.getVideoTemplates();
|
const templates = await this.templateFactory.getVideoTemplates();
|
||||||
@@ -237,13 +304,26 @@ export class TemplateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 根据用户偏好推荐模板
|
|
||||||
* @param tags 用户偏好标签,逗号分隔
|
|
||||||
* @param limit 推荐数量
|
|
||||||
* @returns 推荐模板列表
|
|
||||||
*/
|
|
||||||
@Get('recommend')
|
@Get('recommend')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '推荐模板',
|
||||||
|
description: '根据用户偏好标签推荐适合的模板',
|
||||||
|
})
|
||||||
|
@ApiQuery({ name: 'tags', required: false, description: '用户偏好标签,逗号分隔', example: '人物,手办' })
|
||||||
|
@ApiQuery({ name: 'limit', required: false, description: '推荐数量', example: 5 })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '推荐成功',
|
||||||
|
schema: {
|
||||||
|
example: {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
userTags: ['人物', '手办'],
|
||||||
|
recommendedTemplates: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
async recommendTemplates(
|
async recommendTemplates(
|
||||||
@Query('tags') tags?: string,
|
@Query('tags') tags?: string,
|
||||||
@Query('limit') limit: number = 5,
|
@Query('limit') limit: number = 5,
|
||||||
@@ -270,12 +350,21 @@ export class TemplateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取特定模板详情
|
|
||||||
* @param templateId 模板ID
|
|
||||||
* @returns 模板详细信息
|
|
||||||
*/
|
|
||||||
@Get(':templateId')
|
@Get(':templateId')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '获取模板详情',
|
||||||
|
description: '根据模板ID获取详细信息',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'templateId', description: '模板ID', example: 1 })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '获取成功',
|
||||||
|
type: TemplateListDto,
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 404,
|
||||||
|
description: '模板不存在',
|
||||||
|
})
|
||||||
async getTemplateById(@Param('templateId', ParseIntPipe) templateId: number) {
|
async getTemplateById(@Param('templateId', ParseIntPipe) templateId: number) {
|
||||||
try {
|
try {
|
||||||
const template = await this.templateFactory.createTemplate(templateId);
|
const template = await this.templateFactory.createTemplate(templateId);
|
||||||
@@ -411,12 +500,30 @@ export class TemplateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 创建新模板
|
|
||||||
* @param createTemplateDto 创建模板的数据传输对象
|
|
||||||
* @returns 创建的模板信息
|
|
||||||
*/
|
|
||||||
@Post()
|
@Post()
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '创建新模板',
|
||||||
|
description: '创建一个新的AI生成模板',
|
||||||
|
})
|
||||||
|
@ApiBody({ type: CreateTemplateDto })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '创建成功',
|
||||||
|
schema: {
|
||||||
|
example: {
|
||||||
|
success: true,
|
||||||
|
data: {
|
||||||
|
id: 1,
|
||||||
|
code: 'new_template_v1',
|
||||||
|
name: '新模板'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 409,
|
||||||
|
description: '模板代码已存在',
|
||||||
|
})
|
||||||
async createTemplate(@Body() createTemplateDto: CreateTemplateDto) {
|
async createTemplate(@Body() createTemplateDto: CreateTemplateDto) {
|
||||||
try {
|
try {
|
||||||
const existingTemplate = await this.templateRepository.findOne({
|
const existingTemplate = await this.templateRepository.findOne({
|
||||||
@@ -448,13 +555,26 @@ export class TemplateController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 更新模板
|
|
||||||
* @param templateId 模板ID
|
|
||||||
* @param updateTemplateDto 更新模板的数据传输对象
|
|
||||||
* @returns 更新后的模板信息
|
|
||||||
*/
|
|
||||||
@Put(':templateId')
|
@Put(':templateId')
|
||||||
|
@ApiOperation({
|
||||||
|
summary: '更新模板',
|
||||||
|
description: '更新指定模板的配置信息',
|
||||||
|
})
|
||||||
|
@ApiParam({ name: 'templateId', description: '模板ID', example: 1 })
|
||||||
|
@ApiBody({ type: UpdateTemplateDto })
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '更新成功',
|
||||||
|
type: TemplateListDto,
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 404,
|
||||||
|
description: '模板不存在',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 409,
|
||||||
|
description: '模板代码已存在',
|
||||||
|
})
|
||||||
async updateTemplate(
|
async updateTemplate(
|
||||||
@Param('templateId', ParseIntPipe) templateId: number,
|
@Param('templateId', ParseIntPipe) templateId: number,
|
||||||
@Body() updateTemplateDto: UpdateTemplateDto,
|
@Body() updateTemplateDto: UpdateTemplateDto,
|
||||||
|
|||||||
18
src/decorators/api-common-responses.decorator.ts
Normal file
18
src/decorators/api-common-responses.decorator.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
import { applyDecorators } from '@nestjs/common';
|
||||||
|
import { ApiResponse } from '@nestjs/swagger';
|
||||||
|
import { CommonErrorResponses } from '../dto/error-response.dto';
|
||||||
|
|
||||||
|
export function ApiCommonResponses() {
|
||||||
|
return applyDecorators(
|
||||||
|
ApiResponse(CommonErrorResponses.BadRequest),
|
||||||
|
ApiResponse(CommonErrorResponses.Unauthorized),
|
||||||
|
ApiResponse(CommonErrorResponses.InternalServerError),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ApiAuthResponses() {
|
||||||
|
return applyDecorators(
|
||||||
|
ApiResponse(CommonErrorResponses.Unauthorized),
|
||||||
|
ApiResponse(CommonErrorResponses.Forbidden),
|
||||||
|
);
|
||||||
|
}
|
||||||
43
src/dto/common-response.dto.ts
Normal file
43
src/dto/common-response.dto.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { ApiProperty, ApiHideProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class CommonResponseDto {
|
||||||
|
@ApiProperty({ description: '响应状态码', example: 200 })
|
||||||
|
code: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '响应消息', example: 'success' })
|
||||||
|
message: string;
|
||||||
|
|
||||||
|
@ApiHideProperty()
|
||||||
|
data: any;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '时间戳', example: 1703001000000 })
|
||||||
|
timestamp: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '追踪ID', example: 'trace-uuid-123' })
|
||||||
|
traceId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaginationDto {
|
||||||
|
@ApiProperty({ description: '页码', example: 1, minimum: 1 })
|
||||||
|
page: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '每页数量', example: 10, minimum: 1, maximum: 100 })
|
||||||
|
limit: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class PaginationResponseDto {
|
||||||
|
@ApiHideProperty()
|
||||||
|
items: any[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '总数量', example: 100 })
|
||||||
|
total: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '当前页码', example: 1 })
|
||||||
|
page: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '每页数量', example: 10 })
|
||||||
|
limit: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '总页数', example: 10 })
|
||||||
|
totalPages: number;
|
||||||
|
}
|
||||||
53
src/dto/error-response.dto.ts
Normal file
53
src/dto/error-response.dto.ts
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
import { ApiProperty, ApiHideProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
export class ErrorResponseDto {
|
||||||
|
@ApiProperty({ description: '错误状态码', example: 400 })
|
||||||
|
code: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '错误消息', example: '请求参数错误' })
|
||||||
|
message: string;
|
||||||
|
|
||||||
|
@ApiHideProperty()
|
||||||
|
data: null;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '时间戳', example: 1703001000000 })
|
||||||
|
timestamp: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '追踪ID', example: 'trace-uuid-123' })
|
||||||
|
traceId: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '详细错误信息',
|
||||||
|
required: false,
|
||||||
|
example: ['字段验证失败'],
|
||||||
|
})
|
||||||
|
details?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const CommonErrorResponses = {
|
||||||
|
BadRequest: {
|
||||||
|
status: 400,
|
||||||
|
description: '请求参数错误',
|
||||||
|
type: ErrorResponseDto,
|
||||||
|
},
|
||||||
|
Unauthorized: {
|
||||||
|
status: 401,
|
||||||
|
description: '未授权访问',
|
||||||
|
type: ErrorResponseDto,
|
||||||
|
},
|
||||||
|
Forbidden: {
|
||||||
|
status: 403,
|
||||||
|
description: '禁止访问',
|
||||||
|
type: ErrorResponseDto,
|
||||||
|
},
|
||||||
|
NotFound: {
|
||||||
|
status: 404,
|
||||||
|
description: '资源不存在',
|
||||||
|
type: ErrorResponseDto,
|
||||||
|
},
|
||||||
|
InternalServerError: {
|
||||||
|
status: 500,
|
||||||
|
description: '服务器内部错误',
|
||||||
|
type: ErrorResponseDto,
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -8,151 +8,267 @@ import {
|
|||||||
Min,
|
Min,
|
||||||
Max,
|
Max,
|
||||||
} from 'class-validator';
|
} from 'class-validator';
|
||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { TemplateType } from '../entities/n8n-template.entity';
|
import { TemplateType } from '../entities/n8n-template.entity';
|
||||||
|
|
||||||
export class CreateTemplateDto {
|
export class CreateTemplateDto {
|
||||||
|
@ApiProperty({ description: '模板代码', example: 'character_figurine_v1' })
|
||||||
@IsString()
|
@IsString()
|
||||||
code: string;
|
code: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板名称', example: '人物手办' })
|
||||||
@IsString()
|
@IsString()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板描述', example: '将人物照片制作成精细的角色手办模型' })
|
||||||
@IsString()
|
@IsString()
|
||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '积分消耗', example: 28, minimum: 0 })
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
creditCost: number;
|
creditCost: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '版本号', example: '1.0.0' })
|
||||||
@IsString()
|
@IsString()
|
||||||
version: string;
|
version: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '输入示例URL', required: false, example: 'https://example.com/input.jpg' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
inputExampleUrl?: string;
|
inputExampleUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '输出示例URL', required: false, example: 'https://example.com/output.jpg' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
outputExampleUrl?: string;
|
outputExampleUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '标签数组', required: false, example: ['人物', '手办', '模型'] })
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板类型', enum: TemplateType, example: 'image' })
|
||||||
@IsEnum(TemplateType)
|
@IsEnum(TemplateType)
|
||||||
templateType: TemplateType;
|
templateType: TemplateType;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板类名', example: 'CharacterFigurineTemplate' })
|
||||||
@IsString()
|
@IsString()
|
||||||
templateClass: string;
|
templateClass: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '图片模型', required: false, example: 'flux-dev' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
imageModel?: string;
|
imageModel?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '图片提示词模板', required: false })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
imagePrompt?: string;
|
imagePrompt?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '视频模型', required: false, example: 'runway-gen3' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
videoModel?: string;
|
videoModel?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '视频提示词模板', required: false })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
videoPrompt?: string;
|
videoPrompt?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '视频时长(秒)', required: false, example: 10, minimum: 1, maximum: 300 })
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Min(1)
|
@Min(1)
|
||||||
@Max(300)
|
@Max(300)
|
||||||
duration?: number;
|
duration?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '宽高比', required: false, example: '16:9' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
aspectRatio?: string;
|
aspectRatio?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '是否启用', required: false, example: true })
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '排序权重', required: false, example: 100 })
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
sortOrder?: number;
|
sortOrder?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class UpdateTemplateDto {
|
export class UpdateTemplateDto {
|
||||||
|
@ApiProperty({ description: '模板代码', required: false, example: 'character_figurine_v1' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
code?: string;
|
code?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板名称', required: false, example: '人物手办' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板描述', required: false, example: '将人物照片制作成精细的角色手办模型' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
description?: string;
|
description?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '积分消耗', required: false, example: 28, minimum: 0 })
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@Min(0)
|
@Min(0)
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
creditCost?: number;
|
creditCost?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '版本号', required: false, example: '1.0.1' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
version?: string;
|
version?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '输入示例URL', required: false, example: 'https://example.com/input.jpg' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
inputExampleUrl?: string;
|
inputExampleUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '输出示例URL', required: false, example: 'https://example.com/output.jpg' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
outputExampleUrl?: string;
|
outputExampleUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '标签数组', required: false, example: ['人物', '手办', '模型'] })
|
||||||
@IsArray()
|
@IsArray()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
tags?: string[];
|
tags?: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板类型', enum: TemplateType, required: false })
|
||||||
@IsEnum(TemplateType)
|
@IsEnum(TemplateType)
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
templateType?: TemplateType;
|
templateType?: TemplateType;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板类名', required: false, example: 'CharacterFigurineTemplate' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
templateClass?: string;
|
templateClass?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '图片模型', required: false, example: 'flux-dev' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
imageModel?: string;
|
imageModel?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '图片提示词模板', required: false })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
imagePrompt?: string;
|
imagePrompt?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '视频模型', required: false, example: 'runway-gen3' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
videoModel?: string;
|
videoModel?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '视频提示词模板', required: false })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
videoPrompt?: string;
|
videoPrompt?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '视频时长(秒)', required: false, example: 10, minimum: 1, maximum: 300 })
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
@Min(1)
|
@Min(1)
|
||||||
@Max(300)
|
@Max(300)
|
||||||
duration?: number;
|
duration?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '宽高比', required: false, example: '16:9' })
|
||||||
@IsString()
|
@IsString()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
aspectRatio?: string;
|
aspectRatio?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '是否启用', required: false, example: true })
|
||||||
@IsBoolean()
|
@IsBoolean()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
isActive?: boolean;
|
isActive?: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '排序权重', required: false, example: 100 })
|
||||||
@IsNumber()
|
@IsNumber()
|
||||||
@IsOptional()
|
@IsOptional()
|
||||||
sortOrder?: number;
|
sortOrder?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TemplateExecuteDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: '输入图片URL',
|
||||||
|
example: 'https://cdn.example.com/upload/image.jpg'
|
||||||
|
})
|
||||||
|
@IsString()
|
||||||
|
imageUrl: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TemplateExecuteResponseDto {
|
||||||
|
@ApiProperty({ description: '执行状态', example: true })
|
||||||
|
success: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '生成结果URL(图片模板)', required: false })
|
||||||
|
imageUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '生成结果URL(视频模板)', required: false })
|
||||||
|
videoUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '缩略图URL', required: false })
|
||||||
|
thumbnailUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '任务ID', example: 'req_1704067200_abc123' })
|
||||||
|
taskId: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '执行耗时(毫秒)', example: 5000 })
|
||||||
|
executionTime: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '消耗积分', example: 28 })
|
||||||
|
creditCost: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TemplateListDto {
|
||||||
|
@ApiProperty({ description: '模板ID', example: 1 })
|
||||||
|
id: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板代码', example: 'character_figurine_v1' })
|
||||||
|
code: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板名称', example: '人物手办' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '模板描述', example: '将人物照片制作成精细的角色手办模型' })
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
@ApiProperty({ enum: TemplateType, description: '模板类型', example: 'video' })
|
||||||
|
templateType: TemplateType;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '积分消耗', example: 28 })
|
||||||
|
creditCost: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '版本号', example: '1.0.0' })
|
||||||
|
version: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '输入示例URL', required: false })
|
||||||
|
inputExampleUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '输出示例URL', required: false })
|
||||||
|
outputExampleUrl?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ type: [String], description: '标签数组', example: ['人物', '手办', '模型'] })
|
||||||
|
tags: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '是否启用', example: true })
|
||||||
|
isActive: boolean;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建时间', example: '2024-01-01T00:00:00Z' })
|
||||||
|
createdAt: Date;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BatchExecuteDto {
|
||||||
|
@ApiProperty({
|
||||||
|
description: '输入图片URL数组',
|
||||||
|
example: ['https://cdn.example.com/upload/image1.jpg', 'https://cdn.example.com/upload/image2.jpg']
|
||||||
|
})
|
||||||
|
@IsArray()
|
||||||
|
imageUrls: string[];
|
||||||
|
}
|
||||||
|
|||||||
43
src/generate-swagger-json.ts
Normal file
43
src/generate-swagger-json.ts
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||||
|
import { AppModule } from './app.module';
|
||||||
|
import * as fs from 'fs';
|
||||||
|
|
||||||
|
async function generateSwaggerJson() {
|
||||||
|
const app = await NestFactory.create(AppModule, { logger: false });
|
||||||
|
|
||||||
|
const config = new DocumentBuilder()
|
||||||
|
.setTitle('多平台小程序统一后台API')
|
||||||
|
.setDescription('支持微信、支付宝、百度、字节跳动等多平台的统一后台服务')
|
||||||
|
.setVersion('1.0.0')
|
||||||
|
.addBearerAuth(
|
||||||
|
{
|
||||||
|
type: 'http',
|
||||||
|
scheme: 'bearer',
|
||||||
|
bearerFormat: 'JWT',
|
||||||
|
name: 'JWT',
|
||||||
|
description: 'Enter JWT token',
|
||||||
|
in: 'header',
|
||||||
|
},
|
||||||
|
'JWT-auth',
|
||||||
|
)
|
||||||
|
.addTag('用户管理', '用户注册、登录、信息管理')
|
||||||
|
.addTag('平台适配', '各平台特定接口和数据同步')
|
||||||
|
.addTag('AI模板系统', 'AI图片/视频生成模板管理')
|
||||||
|
.addTag('积分系统', '积分获取、消耗、查询管理')
|
||||||
|
.addTag('扩展服务', '预留的扩展功能接口')
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const document = SwaggerModule.createDocument(app as any, config);
|
||||||
|
|
||||||
|
fs.writeFileSync('./swagger.json', JSON.stringify(document, null, 2));
|
||||||
|
fs.writeFileSync('./docs/api-spec.json', JSON.stringify(document, null, 2));
|
||||||
|
|
||||||
|
console.log('Swagger JSON文档已生成:');
|
||||||
|
console.log('- ./swagger.json');
|
||||||
|
console.log('- ./docs/api-spec.json');
|
||||||
|
|
||||||
|
await app.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
generateSwaggerJson().catch(console.error);
|
||||||
32
src/main.ts
32
src/main.ts
@@ -1,25 +1,47 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
|
import { setupSwagger } from './config/swagger.config';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
||||||
|
// 启用全局验证管道
|
||||||
|
app.useGlobalPipes(new ValidationPipe({
|
||||||
|
whitelist: true,
|
||||||
|
forbidNonWhitelisted: true,
|
||||||
|
transform: true,
|
||||||
|
}));
|
||||||
|
|
||||||
|
// 设置Swagger文档(在全局前缀之前)
|
||||||
|
try {
|
||||||
|
setupSwagger(app);
|
||||||
|
console.log('✅ Swagger setup completed successfully');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Swagger setup failed:', error.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置API前缀(排除docs路径)
|
||||||
|
app.setGlobalPrefix('api/v1', {
|
||||||
|
exclude: ['docs', 'docs/(.*)']
|
||||||
|
});
|
||||||
|
|
||||||
// 启用 CORS 支持多平台访问
|
// 启用 CORS 支持多平台访问
|
||||||
app.enableCors({
|
app.enableCors({
|
||||||
origin: true,
|
origin: process.env.NODE_ENV === 'production'
|
||||||
|
? ['https://example.com']
|
||||||
|
: true,
|
||||||
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||||
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
|
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
|
||||||
credentials: true,
|
credentials: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 设置全局前缀
|
const port = process.env.PORT ?? 3003;
|
||||||
app.setGlobalPrefix('');
|
|
||||||
|
|
||||||
const port = process.env.PORT ?? 3000;
|
|
||||||
await app.listen(port);
|
await app.listen(port);
|
||||||
|
|
||||||
console.log(`🚀 多平台小程序后台服务启动成功!`);
|
console.log(`🚀 多平台小程序后台服务启动成功!`);
|
||||||
console.log(`📡 服务地址: http://localhost:${port}`);
|
console.log(`📡 服务地址: http://localhost:${port}`);
|
||||||
|
console.log(`📖 API文档地址: http://localhost:${port}/docs`);
|
||||||
console.log(`📋 模板管理 API: http://localhost:${port}/api/v1/templates`);
|
console.log(`📋 模板管理 API: http://localhost:${port}/api/v1/templates`);
|
||||||
}
|
}
|
||||||
bootstrap();
|
bootstrap();
|
||||||
|
|||||||
@@ -24,16 +24,37 @@ import {
|
|||||||
import { PlatformAuthGuard } from '../guards/platform-auth.guard';
|
import { PlatformAuthGuard } from '../guards/platform-auth.guard';
|
||||||
import { PlatformType } from '../../entities/platform-user.entity';
|
import { PlatformType } from '../../entities/platform-user.entity';
|
||||||
|
|
||||||
@ApiTags('统一用户管理')
|
@ApiTags('用户管理')
|
||||||
@Controller('api/v1/users')
|
@Controller('users')
|
||||||
export class UnifiedUserController {
|
export class UnifiedUserController {
|
||||||
constructor(private readonly unifiedUserService: UnifiedUserService) {}
|
constructor(private readonly unifiedUserService: UnifiedUserService) {}
|
||||||
|
|
||||||
@Post('login')
|
@Post('login')
|
||||||
@ApiOperation({ summary: '统一登录接口' })
|
@ApiOperation({
|
||||||
@ApiResponse({ status: 200, description: '登录成功' })
|
summary: '多平台统一登录',
|
||||||
|
description: '支持微信、支付宝、百度、字节跳动等8大平台的统一登录接口',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '登录成功',
|
||||||
|
schema: {
|
||||||
|
example: {
|
||||||
|
code: 200,
|
||||||
|
message: '登录成功',
|
||||||
|
data: {
|
||||||
|
token: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
||||||
|
refreshToken: 'refresh_token_string',
|
||||||
|
userInfo: {
|
||||||
|
id: 'user-uuid',
|
||||||
|
nickname: '用户昵称',
|
||||||
|
avatarUrl: 'https://example.com/avatar.jpg'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
@ApiResponse({ status: 400, description: '请求参数错误' })
|
@ApiResponse({ status: 400, description: '请求参数错误' })
|
||||||
@ApiResponse({ status: 401, description: '授权失败' })
|
@ApiResponse({ status: 401, description: '平台授权失败' })
|
||||||
async login(@Body() loginDto: PlatformLoginDto) {
|
async login(@Body() loginDto: PlatformLoginDto) {
|
||||||
const result = await this.unifiedUserService.login(
|
const result = await this.unifiedUserService.login(
|
||||||
loginDto.platform,
|
loginDto.platform,
|
||||||
@@ -47,8 +68,24 @@ export class UnifiedUserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Post('register')
|
@Post('register')
|
||||||
@ApiOperation({ summary: '统一注册接口' })
|
@ApiOperation({
|
||||||
@ApiResponse({ status: 200, description: '注册成功' })
|
summary: '多平台统一注册',
|
||||||
|
description: '支持多平台用户注册,自动创建统一用户身份',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '注册成功',
|
||||||
|
schema: {
|
||||||
|
example: {
|
||||||
|
code: 200,
|
||||||
|
message: '注册成功',
|
||||||
|
data: {
|
||||||
|
userId: 'user-uuid',
|
||||||
|
unifiedUserId: 'unified-user-123'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
async register(@Body() registerDto: PlatformLoginDto) {
|
async register(@Body() registerDto: PlatformLoginDto) {
|
||||||
const result = await this.unifiedUserService.register(
|
const result = await this.unifiedUserService.register(
|
||||||
registerDto.platform,
|
registerDto.platform,
|
||||||
@@ -63,9 +100,31 @@ export class UnifiedUserController {
|
|||||||
|
|
||||||
@Get('profile')
|
@Get('profile')
|
||||||
@UseGuards(PlatformAuthGuard)
|
@UseGuards(PlatformAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth('JWT-auth')
|
||||||
@ApiOperation({ summary: '获取用户信息' })
|
@ApiOperation({
|
||||||
@ApiResponse({ status: 200, description: '获取成功' })
|
summary: '获取用户档案',
|
||||||
|
description: '获取当前登录用户的详细信息和平台绑定状态',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '获取成功',
|
||||||
|
schema: {
|
||||||
|
example: {
|
||||||
|
code: 200,
|
||||||
|
message: '获取成功',
|
||||||
|
data: {
|
||||||
|
id: 'user-uuid',
|
||||||
|
unifiedUserId: 'unified-user-123',
|
||||||
|
nickname: '用户昵称',
|
||||||
|
avatarUrl: 'https://example.com/avatar.jpg',
|
||||||
|
phone: '13800138000',
|
||||||
|
email: 'user@example.com',
|
||||||
|
platformUsers: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 401, description: '未授权访问' })
|
||||||
async getProfile(@Request() req) {
|
async getProfile(@Request() req) {
|
||||||
const userInfo = await this.unifiedUserService.getUserInfo(req.user.userId);
|
const userInfo = await this.unifiedUserService.getUserInfo(req.user.userId);
|
||||||
return {
|
return {
|
||||||
@@ -77,9 +136,22 @@ export class UnifiedUserController {
|
|||||||
|
|
||||||
@Put('profile')
|
@Put('profile')
|
||||||
@UseGuards(PlatformAuthGuard)
|
@UseGuards(PlatformAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth('JWT-auth')
|
||||||
@ApiOperation({ summary: '更新用户信息' })
|
@ApiOperation({
|
||||||
@ApiResponse({ status: 200, description: '更新成功' })
|
summary: '更新用户信息',
|
||||||
|
description: '更新当前用户的基本信息(昵称、头像等)',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '更新成功',
|
||||||
|
schema: {
|
||||||
|
example: {
|
||||||
|
code: 200,
|
||||||
|
message: '更新成功'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 401, description: '未授权访问' })
|
||||||
async updateProfile(@Request() req, @Body() updateDto: UserInfoUpdateDto) {
|
async updateProfile(@Request() req, @Body() updateDto: UserInfoUpdateDto) {
|
||||||
await this.unifiedUserService.updateUserInfo(
|
await this.unifiedUserService.updateUserInfo(
|
||||||
req.user.platform,
|
req.user.platform,
|
||||||
@@ -94,9 +166,23 @@ export class UnifiedUserController {
|
|||||||
|
|
||||||
@Post('bind-platform')
|
@Post('bind-platform')
|
||||||
@UseGuards(PlatformAuthGuard)
|
@UseGuards(PlatformAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth('JWT-auth')
|
||||||
@ApiOperation({ summary: '绑定平台账号' })
|
@ApiOperation({
|
||||||
@ApiResponse({ status: 200, description: '绑定成功' })
|
summary: '绑定平台账号',
|
||||||
|
description: '将其他平台账号绑定到当前统一用户身份',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '绑定成功',
|
||||||
|
schema: {
|
||||||
|
example: {
|
||||||
|
code: 200,
|
||||||
|
message: '绑定成功'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 401, description: '未授权访问' })
|
||||||
|
@ApiResponse({ status: 409, description: '平台账号已绑定其他用户' })
|
||||||
async bindPlatform(@Request() req, @Body() bindDto: PlatformBindDto) {
|
async bindPlatform(@Request() req, @Body() bindDto: PlatformBindDto) {
|
||||||
await this.unifiedUserService.bindPlatform(
|
await this.unifiedUserService.bindPlatform(
|
||||||
req.user.userId,
|
req.user.userId,
|
||||||
@@ -111,9 +197,23 @@ export class UnifiedUserController {
|
|||||||
|
|
||||||
@Delete('unbind-platform/:platform')
|
@Delete('unbind-platform/:platform')
|
||||||
@UseGuards(PlatformAuthGuard)
|
@UseGuards(PlatformAuthGuard)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth('JWT-auth')
|
||||||
@ApiOperation({ summary: '解绑平台账号' })
|
@ApiOperation({
|
||||||
@ApiResponse({ status: 200, description: '解绑成功' })
|
summary: '解绑平台账号',
|
||||||
|
description: '解除指定平台账号与当前用户的绑定关系',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '解绑成功',
|
||||||
|
schema: {
|
||||||
|
example: {
|
||||||
|
code: 200,
|
||||||
|
message: '解绑成功'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
@ApiResponse({ status: 401, description: '未授权访问' })
|
||||||
|
@ApiResponse({ status: 404, description: '平台绑定不存在' })
|
||||||
async unbindPlatform(
|
async unbindPlatform(
|
||||||
@Request() req,
|
@Request() req,
|
||||||
@Param('platform') platform: PlatformType,
|
@Param('platform') platform: PlatformType,
|
||||||
@@ -126,8 +226,32 @@ export class UnifiedUserController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Get('platforms')
|
@Get('platforms')
|
||||||
@ApiOperation({ summary: '获取支持的平台列表' })
|
@ApiOperation({
|
||||||
@ApiResponse({ status: 200, description: '获取成功' })
|
summary: '获取支持的平台列表',
|
||||||
|
description: '获取系统支持的所有平台类型和相关信息',
|
||||||
|
})
|
||||||
|
@ApiResponse({
|
||||||
|
status: 200,
|
||||||
|
description: '获取成功',
|
||||||
|
schema: {
|
||||||
|
example: {
|
||||||
|
code: 200,
|
||||||
|
message: '获取成功',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
type: 'wechat',
|
||||||
|
name: '微信小程序',
|
||||||
|
supported: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'alipay',
|
||||||
|
name: '支付宝小程序',
|
||||||
|
supported: true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
async getSupportedPlatforms() {
|
async getSupportedPlatforms() {
|
||||||
const platforms = this.unifiedUserService.getSupportedPlatforms();
|
const platforms = this.unifiedUserService.getSupportedPlatforms();
|
||||||
return {
|
return {
|
||||||
|
|||||||
1972
swagger.json
Normal file
1972
swagger.json
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user