1028 lines
26 KiB
Markdown
1028 lines
26 KiB
Markdown
# Swagger API 文档配置指南
|
||
|
||
## 1. Swagger 基础配置
|
||
|
||
### 1.1 安装依赖
|
||
```bash
|
||
pnpm add @nestjs/swagger swagger-ui-express
|
||
pnpm add -D @types/swagger-ui-express
|
||
```
|
||
|
||
### 1.2 基础配置文件
|
||
```typescript
|
||
// src/config/swagger.config.ts
|
||
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
|
||
import { INestApplication } from '@nestjs/common';
|
||
|
||
export function setupSwagger(app: INestApplication): void {
|
||
const config = new DocumentBuilder()
|
||
.setTitle('多平台小程序统一后台API')
|
||
.setDescription(`
|
||
## 功能特性
|
||
- 🔐 统一用户认证 (微信/支付宝/百度/字节跳动等)
|
||
- 🔄 平台数据同步 (数据库异步处理)
|
||
- 🧩 可扩展架构 (支持后续添加支付、推送等功能)
|
||
- 📊 灵活数据存储 (MySQL + JSON)
|
||
|
||
## 认证方式
|
||
使用JWT Bearer Token进行API认证
|
||
|
||
## AI模板系统
|
||
- 🎨 动态模板管理 (数据库配置 + 代码执行)
|
||
- 🚀 N8n工作流集成 (图片/视频生成)
|
||
- 📊 使用统计分析 (性能监控 + 用户行为)
|
||
- 🎛️ 运营管理后台 (A/B测试 + 个性化推荐)
|
||
|
||
## 响应格式
|
||
所有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('🧩 扩展服务', '预留的扩展功能接口')
|
||
.addTag('📊 数据统计', '业务数据统计和分析')
|
||
.addServer('http://localhost:3000', '开发环境')
|
||
.addServer('https://api-dev.example.com', '测试环境')
|
||
.addServer('https://api.example.com', '生产环境')
|
||
.build();
|
||
|
||
const document = SwaggerModule.createDocument(app, config, {
|
||
operationIdFactory: (controllerKey: string, methodKey: string) => methodKey,
|
||
});
|
||
|
||
SwaggerModule.setup('api/docs', app, 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',
|
||
],
|
||
customCssUrl: [
|
||
'https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.15.5/swagger-ui.min.css',
|
||
],
|
||
});
|
||
}
|
||
```
|
||
|
||
## 2. 通用DTO定义
|
||
|
||
### 2.1 统一响应格式
|
||
```typescript
|
||
// src/dto/common-response.dto.ts
|
||
import { ApiProperty } from '@nestjs/swagger';
|
||
|
||
export class CommonResponseDto<T = any> {
|
||
@ApiProperty({ description: '响应状态码', example: 200 })
|
||
code: number;
|
||
|
||
@ApiProperty({ description: '响应消息', example: 'success' })
|
||
message: string;
|
||
|
||
@ApiProperty({ description: '响应数据' })
|
||
data: T;
|
||
|
||
@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<T = any> {
|
||
@ApiProperty({ description: '数据列表' })
|
||
items: T[];
|
||
|
||
@ApiProperty({ description: '总数量', example: 100 })
|
||
total: number;
|
||
|
||
@ApiProperty({ description: '当前页码', example: 1 })
|
||
page: number;
|
||
|
||
@ApiProperty({ description: '每页数量', example: 10 })
|
||
limit: number;
|
||
|
||
@ApiProperty({ description: '总页数', example: 10 })
|
||
totalPages: number;
|
||
}
|
||
```
|
||
|
||
### 2.2 平台枚举定义
|
||
```typescript
|
||
// src/dto/platform.dto.ts
|
||
import { ApiProperty } from '@nestjs/swagger';
|
||
|
||
export enum PlatformType {
|
||
WECHAT = 'wechat',
|
||
ALIPAY = 'alipay',
|
||
BAIDU = 'baidu',
|
||
BYTEDANCE = 'bytedance',
|
||
JD = 'jd',
|
||
QQ = 'qq',
|
||
FEISHU = 'feishu',
|
||
KUAISHOU = 'kuaishou',
|
||
H5 = 'h5',
|
||
RN = 'rn'
|
||
}
|
||
|
||
export const PlatformDescriptions = {
|
||
[PlatformType.WECHAT]: '微信小程序',
|
||
[PlatformType.ALIPAY]: '支付宝小程序',
|
||
[PlatformType.BAIDU]: '百度智能小程序',
|
||
[PlatformType.BYTEDANCE]: '字节跳动小程序',
|
||
[PlatformType.JD]: '京东小程序',
|
||
[PlatformType.QQ]: 'QQ小程序',
|
||
[PlatformType.FEISHU]: '飞书小程序',
|
||
[PlatformType.KUAISHOU]: '快手小程序',
|
||
[PlatformType.H5]: 'H5应用',
|
||
[PlatformType.RN]: 'React Native应用',
|
||
};
|
||
```
|
||
|
||
## 3. 用户管理API文档
|
||
|
||
### 3.1 用户登录DTO
|
||
```typescript
|
||
// src/dto/user.dto.ts
|
||
import { ApiProperty } from '@nestjs/swagger';
|
||
import { IsString, IsEnum, IsOptional, IsObject } from 'class-validator';
|
||
import { PlatformType } from './platform.dto';
|
||
|
||
export class UserLoginDto {
|
||
@ApiProperty({
|
||
description: '平台类型',
|
||
enum: PlatformType,
|
||
example: PlatformType.WECHAT,
|
||
enumName: 'PlatformType',
|
||
})
|
||
@IsEnum(PlatformType)
|
||
platform: PlatformType;
|
||
|
||
@ApiProperty({
|
||
description: '平台授权码/临时登录凭证',
|
||
example: '081234567890abcdef',
|
||
minLength: 1,
|
||
maxLength: 200,
|
||
})
|
||
@IsString()
|
||
code: string;
|
||
|
||
@ApiProperty({
|
||
description: '加密用户数据 (微信小程序专用)',
|
||
required: false,
|
||
example: 'encrypted_user_data_string',
|
||
})
|
||
@IsOptional()
|
||
@IsString()
|
||
encryptedData?: string;
|
||
|
||
@ApiProperty({
|
||
description: '加密向量 (微信小程序专用)',
|
||
required: false,
|
||
example: 'iv_string',
|
||
})
|
||
@IsOptional()
|
||
@IsString()
|
||
iv?: string;
|
||
|
||
@ApiProperty({
|
||
description: '额外的平台特定数据',
|
||
required: false,
|
||
type: 'object',
|
||
example: { sessionKey: 'session_key_value' },
|
||
})
|
||
@IsOptional()
|
||
@IsObject()
|
||
extra?: Record<string, any>;
|
||
}
|
||
|
||
export class UserInfoDto {
|
||
@ApiProperty({ description: '用户ID', example: 'user-uuid-123' })
|
||
id: string;
|
||
|
||
@ApiProperty({ description: '统一用户ID', example: 'unified-user-123' })
|
||
unifiedUserId: string;
|
||
|
||
@ApiProperty({ description: '用户昵称', example: '张三' })
|
||
nickname: string;
|
||
|
||
@ApiProperty({
|
||
description: '头像URL',
|
||
example: 'https://example.com/avatar.jpg'
|
||
})
|
||
avatarUrl: string;
|
||
|
||
@ApiProperty({
|
||
description: '手机号',
|
||
example: '13800138000',
|
||
required: false
|
||
})
|
||
phone?: string;
|
||
|
||
@ApiProperty({
|
||
description: '邮箱',
|
||
example: 'user@example.com',
|
||
required: false
|
||
})
|
||
email?: string;
|
||
|
||
@ApiProperty({ description: '用户状态', example: 1 })
|
||
status: number;
|
||
|
||
@ApiProperty({ description: '创建时间', example: '2023-12-01T10:00:00Z' })
|
||
createdAt: Date;
|
||
|
||
@ApiProperty({ description: '更新时间', example: '2023-12-01T10:00:00Z' })
|
||
updatedAt: Date;
|
||
}
|
||
|
||
export class UserLoginResponseDto {
|
||
@ApiProperty({
|
||
description: 'JWT访问令牌',
|
||
example: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
|
||
})
|
||
token: string;
|
||
|
||
@ApiProperty({
|
||
description: '刷新令牌',
|
||
example: 'refresh_token_string_here',
|
||
})
|
||
refreshToken: string;
|
||
|
||
@ApiProperty({
|
||
description: '用户信息',
|
||
type: UserInfoDto,
|
||
})
|
||
userInfo: UserInfoDto;
|
||
|
||
@ApiProperty({
|
||
description: '平台特定数据',
|
||
type: 'object',
|
||
required: false,
|
||
example: { openid: 'wx_openid_123' },
|
||
})
|
||
platformSpecific?: Record<string, any>;
|
||
}
|
||
```
|
||
|
||
## 4. 扩展服务API文档
|
||
|
||
### 4.1 扩展数据相关DTO
|
||
```typescript
|
||
// src/dto/extension.dto.ts
|
||
import { ApiProperty } from '@nestjs/swagger';
|
||
import { IsString, IsEnum, IsOptional, IsObject } from 'class-validator';
|
||
import { PlatformType } from './platform.dto';
|
||
|
||
export class CreateExtensionDataDto {
|
||
@ApiProperty({
|
||
description: '用户ID',
|
||
example: 'user-uuid-123',
|
||
})
|
||
@IsString()
|
||
userId: string;
|
||
|
||
@ApiProperty({
|
||
description: '平台类型',
|
||
enum: PlatformType,
|
||
example: PlatformType.WECHAT,
|
||
})
|
||
@IsEnum(PlatformType)
|
||
platform: PlatformType;
|
||
|
||
@ApiProperty({
|
||
description: '数据类型',
|
||
example: 'custom',
|
||
maxLength: 50,
|
||
})
|
||
@IsString()
|
||
dataType: string;
|
||
|
||
@ApiProperty({
|
||
description: '外部引用ID',
|
||
required: false,
|
||
example: 'ref-123456',
|
||
maxLength: 100,
|
||
})
|
||
@IsOptional()
|
||
@IsString()
|
||
referenceId?: string;
|
||
|
||
@ApiProperty({
|
||
description: '扩展数据内容',
|
||
type: 'object',
|
||
example: {
|
||
customField1: 'value1',
|
||
customField2: 'value2',
|
||
settings: { enabled: true }
|
||
},
|
||
})
|
||
@IsObject()
|
||
data: Record<string, any>;
|
||
|
||
@ApiProperty({
|
||
description: '元数据',
|
||
required: false,
|
||
type: 'object',
|
||
example: { source: 'api', version: '1.0' },
|
||
})
|
||
@IsOptional()
|
||
@IsObject()
|
||
metadata?: Record<string, any>;
|
||
}
|
||
|
||
export class ExtensionDataResponseDto {
|
||
@ApiProperty({ description: '扩展数据ID', example: 'ext-uuid-123' })
|
||
id: string;
|
||
|
||
@ApiProperty({ description: '用户ID', example: 'user-uuid-123' })
|
||
userId: string;
|
||
|
||
@ApiProperty({
|
||
description: '平台类型',
|
||
enum: PlatformType,
|
||
example: PlatformType.WECHAT,
|
||
})
|
||
platform: PlatformType;
|
||
|
||
@ApiProperty({ description: '数据类型', example: 'custom' })
|
||
dataType: string;
|
||
|
||
@ApiProperty({ description: '外部引用ID', example: 'ref-123456' })
|
||
referenceId: string;
|
||
|
||
@ApiProperty({
|
||
description: '扩展数据内容',
|
||
type: 'object',
|
||
})
|
||
data: Record<string, any>;
|
||
|
||
@ApiProperty({
|
||
description: '元数据',
|
||
type: 'object',
|
||
})
|
||
metadata: Record<string, any>;
|
||
|
||
@ApiProperty({ description: '状态', example: 'active' })
|
||
status: string;
|
||
|
||
@ApiProperty({ description: '创建时间', example: '2023-12-01T10:00:00Z' })
|
||
createdAt: Date;
|
||
|
||
@ApiProperty({ description: '更新时间', example: '2023-12-01T10:00:00Z' })
|
||
updatedAt: Date;
|
||
}
|
||
```
|
||
|
||
## 5. 错误响应文档
|
||
|
||
### 5.1 通用错误响应
|
||
```typescript
|
||
// src/dto/error-response.dto.ts
|
||
import { ApiProperty } from '@nestjs/swagger';
|
||
|
||
export class ErrorResponseDto {
|
||
@ApiProperty({ description: '错误状态码', example: 400 })
|
||
code: number;
|
||
|
||
@ApiProperty({ description: '错误消息', example: '请求参数错误' })
|
||
message: string;
|
||
|
||
@ApiProperty({ description: '错误数据', example: null })
|
||
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,
|
||
},
|
||
};
|
||
```
|
||
|
||
## 6. 装饰器使用示例
|
||
|
||
### 6.1 Controller装饰器
|
||
```typescript
|
||
// src/decorators/api-common-responses.decorator.ts
|
||
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),
|
||
);
|
||
}
|
||
```
|
||
|
||
### 6.2 使用示例
|
||
```typescript
|
||
// src/controllers/user.controller.ts
|
||
import { Controller, Post, Body, Get, UseGuards } from '@nestjs/common';
|
||
import {
|
||
ApiTags,
|
||
ApiOperation,
|
||
ApiResponse,
|
||
ApiBearerAuth,
|
||
} from '@nestjs/swagger';
|
||
import { ApiCommonResponses, ApiAuthResponses } from '../decorators/api-common-responses.decorator';
|
||
|
||
@ApiTags('🔐 用户管理')
|
||
@Controller('users')
|
||
@ApiCommonResponses()
|
||
export class UserController {
|
||
@Post('login')
|
||
@ApiOperation({
|
||
summary: '用户登录',
|
||
description: '支持多平台用户登录,返回JWT令牌和用户信息',
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '登录成功',
|
||
type: UserLoginResponseDto,
|
||
})
|
||
async login(@Body() loginDto: UserLoginDto) {
|
||
return this.userService.login(loginDto);
|
||
}
|
||
|
||
@Get('profile')
|
||
@UseGuards(JwtAuthGuard)
|
||
@ApiBearerAuth('JWT-auth')
|
||
@ApiAuthResponses()
|
||
@ApiOperation({
|
||
summary: '获取用户信息',
|
||
description: '获取当前登录用户的详细信息',
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '获取成功',
|
||
type: UserInfoDto,
|
||
})
|
||
async getProfile(@CurrentUser() user: any) {
|
||
return this.userService.getProfile(user.id);
|
||
}
|
||
}
|
||
```
|
||
|
||
## 7. AI模板管理 API
|
||
|
||
### 7.1 模板数据传输对象
|
||
|
||
```typescript
|
||
// src/dto/template.dto.ts
|
||
import { ApiProperty } from '@nestjs/swagger';
|
||
import { IsString, IsNumber, IsEnum, IsOptional, IsArray, IsBoolean } from 'class-validator';
|
||
|
||
export enum TemplateType {
|
||
IMAGE = 'image',
|
||
VIDEO = 'video'
|
||
}
|
||
|
||
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 TemplateExecuteDto {
|
||
@ApiProperty({
|
||
description: '输入图片URL',
|
||
example: 'https://cdn.roasmax.cn/upload/3d590851eb584e92aa415a964e93260e.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 TemplateStatsDto {
|
||
@ApiProperty({ description: '总模板数', example: 8 })
|
||
total: number;
|
||
|
||
@ApiProperty({ description: '启用模板数', example: 8 })
|
||
enabled: number;
|
||
|
||
@ApiProperty({ description: '图片模板数', example: 3 })
|
||
imageTemplates: number;
|
||
|
||
@ApiProperty({ description: '视频模板数', example: 5 })
|
||
videoTemplates: number;
|
||
}
|
||
```
|
||
|
||
### 7.2 模板管理控制器
|
||
|
||
```typescript
|
||
// src/controllers/template.controller.ts
|
||
import {
|
||
Controller, Get, Post, Param, Body, Query, UseGuards, ParseIntPipe
|
||
} from '@nestjs/common';
|
||
import {
|
||
ApiTags,
|
||
ApiOperation,
|
||
ApiResponse,
|
||
ApiBearerAuth,
|
||
ApiParam,
|
||
ApiQuery
|
||
} from '@nestjs/swagger';
|
||
import { JwtAuthGuard } from '../guards/jwt-auth.guard';
|
||
import { CurrentUser } from '../decorators/current-user.decorator';
|
||
import { ApiCommonResponses, ApiAuthResponses } from '../decorators/api-common-responses.decorator';
|
||
import { N8nTemplateFactoryService } from '../services/n8n-template-factory.service';
|
||
import { TemplateListDto, TemplateExecuteDto, TemplateExecuteResponseDto, TemplateStatsDto } from '../dto/template.dto';
|
||
|
||
@ApiTags('🎨 AI模板系统')
|
||
@Controller('templates')
|
||
@ApiCommonResponses()
|
||
export class TemplateController {
|
||
constructor(
|
||
private readonly templateFactory: N8nTemplateFactoryService,
|
||
) {}
|
||
|
||
@Get()
|
||
@ApiOperation({
|
||
summary: '获取所有模板列表',
|
||
description: '获取所有可用的AI生成模板,支持按类型筛选'
|
||
})
|
||
@ApiQuery({
|
||
name: 'type',
|
||
required: false,
|
||
enum: ['image', 'video'],
|
||
description: '模板类型筛选'
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '获取成功',
|
||
type: [TemplateListDto]
|
||
})
|
||
async getTemplates(@Query('type') type?: 'image' | 'video') {
|
||
if (type === 'image') {
|
||
return this.templateFactory.getTemplatesByType('image');
|
||
} else if (type === 'video') {
|
||
return this.templateFactory.getTemplatesByType('video');
|
||
}
|
||
return this.templateFactory.getAllTemplates();
|
||
}
|
||
|
||
@Get('stats')
|
||
@ApiOperation({
|
||
summary: '获取模板统计信息',
|
||
description: '获取模板总数、类型分布等统计信息'
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '获取成功',
|
||
type: TemplateStatsDto
|
||
})
|
||
async getTemplateStats() {
|
||
const allTemplates = await this.templateFactory.getAllTemplates();
|
||
const imageTemplates = allTemplates.filter(t => t.templateType === 'image');
|
||
const videoTemplates = allTemplates.filter(t => t.templateType === 'video');
|
||
|
||
return {
|
||
total: allTemplates.length,
|
||
enabled: allTemplates.filter(t => t.isActive).length,
|
||
imageTemplates: imageTemplates.length,
|
||
videoTemplates: videoTemplates.length,
|
||
};
|
||
}
|
||
|
||
@Get(':templateId')
|
||
@ApiOperation({
|
||
summary: '获取模板详情',
|
||
description: '根据模板ID获取详细信息'
|
||
})
|
||
@ApiParam({
|
||
name: 'templateId',
|
||
description: '模板ID',
|
||
example: 1
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '获取成功',
|
||
type: TemplateListDto
|
||
})
|
||
async getTemplateById(@Param('templateId', ParseIntPipe) templateId: number) {
|
||
return this.templateFactory.getTemplateById(templateId);
|
||
}
|
||
|
||
@Get('code/:templateCode')
|
||
@ApiOperation({
|
||
summary: '通过代码获取模板详情',
|
||
description: '根据模板代码获取详细信息'
|
||
})
|
||
@ApiParam({
|
||
name: 'templateCode',
|
||
description: '模板代码',
|
||
example: 'character_figurine_v1'
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '获取成功',
|
||
type: TemplateListDto
|
||
})
|
||
async getTemplateByCode(@Param('templateCode') templateCode: string) {
|
||
return this.templateFactory.getTemplateByCode(templateCode);
|
||
}
|
||
|
||
@Post(':templateId/execute')
|
||
@UseGuards(JwtAuthGuard)
|
||
@ApiBearerAuth('JWT-auth')
|
||
@ApiAuthResponses()
|
||
@ApiOperation({
|
||
summary: '执行模板生成',
|
||
description: '根据模板ID执行AI生成任务,支持图片和视频生成'
|
||
})
|
||
@ApiParam({
|
||
name: 'templateId',
|
||
description: '模板ID',
|
||
example: 1
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '执行成功',
|
||
type: TemplateExecuteResponseDto
|
||
})
|
||
@ApiResponse({
|
||
status: 400,
|
||
description: '参数错误',
|
||
schema: {
|
||
example: {
|
||
code: 400,
|
||
message: '输入图片URL不能为空',
|
||
data: null
|
||
}
|
||
}
|
||
})
|
||
@ApiResponse({
|
||
status: 402,
|
||
description: '积分不足',
|
||
schema: {
|
||
example: {
|
||
code: 402,
|
||
message: '积分不足,当前余额: 10,需要: 28',
|
||
data: null
|
||
}
|
||
}
|
||
})
|
||
async executeTemplate(
|
||
@Param('templateId', ParseIntPipe) templateId: number,
|
||
@Body() executeDto: TemplateExecuteDto,
|
||
@CurrentUser() user: any
|
||
) {
|
||
const startTime = Date.now();
|
||
|
||
// 获取模板配置
|
||
const templateConfig = await this.templateFactory.getTemplateById(templateId);
|
||
|
||
// 创建动态模板实例并执行
|
||
let template;
|
||
if (templateConfig.templateType === 'image') {
|
||
template = await this.templateFactory.createImageTemplate(templateId);
|
||
} else {
|
||
template = await this.templateFactory.createVideoTemplate(templateId);
|
||
}
|
||
|
||
const result = await template.execute(executeDto.imageUrl);
|
||
const executionTime = Date.now() - startTime;
|
||
|
||
return {
|
||
success: true,
|
||
imageUrl: templateConfig.templateType === 'image' ? result : undefined,
|
||
videoUrl: templateConfig.templateType === 'video' ? result : undefined,
|
||
taskId: `req_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
|
||
executionTime,
|
||
creditCost: templateConfig.creditCost
|
||
};
|
||
}
|
||
|
||
@Post('code/:templateCode/execute')
|
||
@UseGuards(JwtAuthGuard)
|
||
@ApiBearerAuth('JWT-auth')
|
||
@ApiAuthResponses()
|
||
@ApiOperation({
|
||
summary: '通过代码执行模板',
|
||
description: '根据模板代码执行AI生成任务,支持图片和视频生成'
|
||
})
|
||
@ApiParam({
|
||
name: 'templateCode',
|
||
description: '模板代码',
|
||
example: 'character_figurine_v1'
|
||
})
|
||
@ApiResponse({
|
||
status: 200,
|
||
description: '执行成功',
|
||
type: TemplateExecuteResponseDto
|
||
})
|
||
async executeTemplateByCode(
|
||
@Param('templateCode') templateCode: string,
|
||
@Body() executeDto: TemplateExecuteDto,
|
||
@CurrentUser() user: any
|
||
) {
|
||
const startTime = Date.now();
|
||
|
||
// 创建动态模板实例并执行
|
||
const template = await this.templateFactory.createTemplateByCode(templateCode);
|
||
const result = await template.execute(executeDto.imageUrl);
|
||
const executionTime = Date.now() - startTime;
|
||
|
||
return {
|
||
success: true,
|
||
imageUrl: template.templateType === 'image' ? result : undefined,
|
||
videoUrl: template.templateType === 'video' ? result : undefined,
|
||
taskId: `req_${Date.now()}_${Math.random().toString(36).substr(2, 6)}`,
|
||
executionTime,
|
||
creditCost: template.creditCost
|
||
};
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.3 API使用示例
|
||
|
||
#### 获取所有模板
|
||
```bash
|
||
curl -X GET "http://localhost:3000/api/templates" \
|
||
-H "accept: application/json"
|
||
```
|
||
|
||
响应示例:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": [
|
||
{
|
||
"id": 1,
|
||
"code": "character_figurine_v1",
|
||
"name": "人物手办",
|
||
"description": "将人物照片制作成精细的角色手办模型,展示在收藏家房间中,并生成抚摸手办的视频",
|
||
"templateType": "video",
|
||
"creditCost": 28,
|
||
"version": "1.0.0",
|
||
"inputExampleUrl": "https://cdn.roasmax.cn/upload/3d590851eb584e92aa415a964e93260e.jpg",
|
||
"outputExampleUrl": "https://file.302.ai/gpt/imgs/20250828/2283106b31faf2066e1a72d955f65bca.jpg",
|
||
"tags": ["人物", "手办", "模型", "收藏", "PVC", "角色模型", "视频生成"],
|
||
"isActive": true,
|
||
"createdAt": "2024-01-01T00:00:00Z"
|
||
}
|
||
]
|
||
}
|
||
```
|
||
|
||
#### 执行模板生成
|
||
```bash
|
||
curl -X POST "http://localhost:3000/api/templates/1/execute" \
|
||
-H "accept: application/json" \
|
||
-H "Authorization: Bearer YOUR_JWT_TOKEN" \
|
||
-H "Content-Type: application/json" \
|
||
-d '{
|
||
"imageUrl": "https://cdn.roasmax.cn/upload/3d590851eb584e92aa415a964e93260e.jpg"
|
||
}'
|
||
```
|
||
|
||
响应示例:
|
||
```json
|
||
{
|
||
"code": 200,
|
||
"message": "success",
|
||
"data": {
|
||
"success": true,
|
||
"videoUrl": "https://n8n.bowongai.com/files/generated_video_abc123.mp4",
|
||
"taskId": "req_1704067200_abc123",
|
||
"executionTime": 5000,
|
||
"creditCost": 28
|
||
}
|
||
}
|
||
```
|
||
|
||
### 7.4 错误处理
|
||
|
||
```typescript
|
||
// 常见错误响应
|
||
export const TemplateApiErrors = {
|
||
TEMPLATE_NOT_FOUND: {
|
||
code: 404,
|
||
message: '模板不存在',
|
||
data: null
|
||
},
|
||
INSUFFICIENT_CREDITS: {
|
||
code: 402,
|
||
message: '积分不足',
|
||
data: { required: 28, current: 10 }
|
||
},
|
||
INVALID_IMAGE_URL: {
|
||
code: 400,
|
||
message: '无效的图片URL',
|
||
data: null
|
||
},
|
||
TEMPLATE_EXECUTION_FAILED: {
|
||
code: 500,
|
||
message: 'AI生成失败,请稍后重试',
|
||
data: null
|
||
},
|
||
TEMPLATE_DISABLED: {
|
||
code: 403,
|
||
message: '模板已禁用',
|
||
data: null
|
||
}
|
||
};
|
||
```
|
||
|
||
## 8. 环境配置
|
||
|
||
### 8.1 开发环境配置
|
||
```typescript
|
||
// src/main.ts
|
||
import { NestFactory } from '@nestjs/core';
|
||
import { ValidationPipe } from '@nestjs/common';
|
||
import { AppModule } from './app.module';
|
||
import { setupSwagger } from './config/swagger.config';
|
||
|
||
async function bootstrap() {
|
||
const app = await NestFactory.create(AppModule);
|
||
|
||
// 全局验证管道
|
||
app.useGlobalPipes(new ValidationPipe({
|
||
whitelist: true,
|
||
forbidNonWhitelisted: true,
|
||
transform: true,
|
||
}));
|
||
|
||
// API前缀
|
||
app.setGlobalPrefix('api/v1');
|
||
|
||
// CORS配置
|
||
app.enableCors({
|
||
origin: process.env.NODE_ENV === 'production'
|
||
? ['https://example.com']
|
||
: true,
|
||
credentials: true,
|
||
});
|
||
|
||
// 仅在非生产环境启用Swagger
|
||
if (process.env.NODE_ENV !== 'production') {
|
||
setupSwagger(app);
|
||
}
|
||
|
||
const port = process.env.PORT || 3000;
|
||
await app.listen(port);
|
||
|
||
console.log('🚀 应用启动成功!');
|
||
console.log(`📖 API文档地址: http://localhost:${port}/api/docs`);
|
||
}
|
||
bootstrap();
|
||
```
|
||
|
||
## 9. 访问API文档
|
||
|
||
启动应用后,访问以下地址查看API文档:
|
||
|
||
- **开发环境**: http://localhost:3000/api/docs
|
||
- **JSON格式**: http://localhost:3000/api/docs-json
|
||
- **YAML格式**: http://localhost:3000/api/docs-yaml
|
||
|
||
API文档包含:
|
||
- 📋 完整的接口列表和参数说明
|
||
- 🔐 JWT认证测试功能
|
||
- 📝 请求/响应示例
|
||
- 🧪 在线接口测试功能
|
||
- 📊 数据模型定义
|
||
- 🎨 AI模板系统接口 (支持图片/视频生成)
|
||
- 💾 动态模板配置管理
|
||
- 📈 模板使用统计和监控
|