feat: 完善模板系统排序功能和代码格式优化

- N8nTemplateEntity已包含sortOrder排序字段
- 所有模板列表接口均按sortOrder倒序排列
- 修复ESLint代码格式问题
This commit is contained in:
imeepos 2025-09-04 18:22:49 +08:00
parent 0bc27df560
commit 42fa667d3c
12 changed files with 217 additions and 92 deletions

View File

@ -4,7 +4,8 @@ import { INestApplication } from '@nestjs/common';
export function setupSwagger(app: INestApplication): void {
const config = new DocumentBuilder()
.setTitle('多平台小程序统一后台API')
.setDescription(`
.setDescription(
`
##
- 🔐 (///)
- 🎨 AI模板系统 (/)
@ -26,7 +27,8 @@ export function setupSwagger(app: INestApplication): void {
"traceId": "trace-uuid"
}
\`\`\`
`)
`,
)
.setVersion('1.0.0')
.setContact('开发团队', 'https://example.com', 'dev@example.com')
.setLicense('MIT', 'https://opensource.org/licenses/MIT')
@ -76,4 +78,4 @@ export function setupSwagger(app: INestApplication): void {
],
});
console.log('✅ Swagger documentation setup completed at /api/docs');
}
}

View File

@ -68,9 +68,9 @@ export class TemplateController {
example: {
code: 400,
message: 'imageUrl is required',
data: null
}
}
data: null,
},
},
})
async executeTemplateById(
@Param('templateId', ParseIntPipe) templateId: number,
@ -111,7 +111,11 @@ export class TemplateController {
summary: '通过代码执行模板',
description: '根据模板代码执行AI生成任务支持图片和视频生成',
})
@ApiParam({ name: 'code', description: '模板代码', example: 'character_figurine_v1' })
@ApiParam({
name: 'code',
description: '模板代码',
example: 'character_figurine_v1',
})
@ApiBody({ type: TemplateExecuteDto })
@ApiResponse({
status: 200,
@ -169,11 +173,11 @@ export class TemplateController {
totalCount: 2,
results: [
{ inputUrl: 'image1.jpg', outputUrl: 'output1.jpg' },
{ inputUrl: 'image2.jpg', outputUrl: 'output2.jpg' }
]
}
}
}
{ inputUrl: 'image2.jpg', outputUrl: 'output2.jpg' },
],
},
},
},
})
async batchExecuteTemplate(
@Param('templateId', ParseIntPipe) templateId: number,
@ -309,8 +313,18 @@ export class TemplateController {
summary: '推荐模板',
description: '根据用户偏好标签推荐适合的模板',
})
@ApiQuery({ name: 'tags', required: false, description: '用户偏好标签,逗号分隔', example: '人物,手办' })
@ApiQuery({ name: 'limit', required: false, description: '推荐数量', example: 5 })
@ApiQuery({
name: 'tags',
required: false,
description: '用户偏好标签,逗号分隔',
example: '人物,手办',
})
@ApiQuery({
name: 'limit',
required: false,
description: '推荐数量',
example: 5,
})
@ApiResponse({
status: 200,
description: '推荐成功',
@ -319,10 +333,10 @@ export class TemplateController {
success: true,
data: {
userTags: ['人物', '手办'],
recommendedTemplates: []
}
}
}
recommendedTemplates: [],
},
},
},
})
async recommendTemplates(
@Query('tags') tags?: string,
@ -515,10 +529,10 @@ export class TemplateController {
data: {
id: 1,
code: 'new_template_v1',
name: '新模板'
}
}
}
name: '新模板',
},
},
},
})
@ApiResponse({
status: 409,

View File

@ -15,4 +15,4 @@ export function ApiAuthResponses() {
ApiResponse(CommonErrorResponses.Unauthorized),
ApiResponse(CommonErrorResponses.Forbidden),
);
}
}

View File

@ -21,7 +21,12 @@ export class PaginationDto {
@ApiProperty({ description: '页码', example: 1, minimum: 1 })
page: number;
@ApiProperty({ description: '每页数量', example: 10, minimum: 1, maximum: 100 })
@ApiProperty({
description: '每页数量',
example: 10,
minimum: 1,
maximum: 100,
})
limit: number;
}
@ -40,4 +45,4 @@ export class PaginationResponseDto {
@ApiProperty({ description: '总页数', example: 10 })
totalPages: number;
}
}

View File

@ -50,4 +50,4 @@ export const CommonErrorResponses = {
description: '服务器内部错误',
type: ErrorResponseDto,
},
};
};

View File

@ -20,7 +20,10 @@ export class CreateTemplateDto {
@IsString()
name: string;
@ApiProperty({ description: '模板描述', example: '将人物照片制作成精细的角色手办模型' })
@ApiProperty({
description: '模板描述',
example: '将人物照片制作成精细的角色手办模型',
})
@IsString()
description: string;
@ -33,30 +36,53 @@ export class CreateTemplateDto {
@IsString()
version: string;
@ApiProperty({ description: '输入示例URL', required: false, example: 'https://example.com/input.jpg' })
@ApiProperty({
description: '输入示例URL',
required: false,
example: 'https://example.com/input.jpg',
})
@IsString()
@IsOptional()
inputExampleUrl?: string;
@ApiProperty({ description: '输出示例URL', required: false, example: 'https://example.com/output.jpg' })
@ApiProperty({
description: '输出示例URL',
required: false,
example: 'https://example.com/output.jpg',
})
@IsString()
@IsOptional()
outputExampleUrl?: string;
@ApiProperty({ description: '标签数组', required: false, example: ['人物', '手办', '模型'] })
@ApiProperty({
description: '标签数组',
required: false,
example: ['人物', '手办', '模型'],
})
@IsArray()
@IsOptional()
tags?: string[];
@ApiProperty({ description: '模板类型', enum: TemplateType, example: 'image' })
@ApiProperty({
description: '模板类型',
enum: TemplateType,
example: 'image',
})
@IsEnum(TemplateType)
templateType: TemplateType;
@ApiProperty({ description: '模板类名', example: 'CharacterFigurineTemplate' })
@ApiProperty({
description: '模板类名',
example: 'CharacterFigurineTemplate',
})
@IsString()
templateClass: string;
@ApiProperty({ description: '图片模型', required: false, example: 'flux-dev' })
@ApiProperty({
description: '图片模型',
required: false,
example: 'flux-dev',
})
@IsString()
@IsOptional()
imageModel?: string;
@ -66,7 +92,11 @@ export class CreateTemplateDto {
@IsOptional()
imagePrompt?: string;
@ApiProperty({ description: '视频模型', required: false, example: 'runway-gen3' })
@ApiProperty({
description: '视频模型',
required: false,
example: 'runway-gen3',
})
@IsString()
@IsOptional()
videoModel?: string;
@ -76,7 +106,13 @@ export class CreateTemplateDto {
@IsOptional()
videoPrompt?: string;
@ApiProperty({ description: '视频时长(秒)', required: false, example: 10, minimum: 1, maximum: 300 })
@ApiProperty({
description: '视频时长(秒)',
required: false,
example: 10,
minimum: 1,
maximum: 300,
})
@IsNumber()
@IsOptional()
@Min(1)
@ -100,22 +136,39 @@ export class CreateTemplateDto {
}
export class UpdateTemplateDto {
@ApiProperty({ description: '模板代码', required: false, example: 'character_figurine_v1' })
@ApiProperty({
description: '模板代码',
required: false,
example: 'character_figurine_v1',
})
@IsString()
@IsOptional()
code?: string;
@ApiProperty({ description: '模板名称', required: false, example: '人物手办' })
@ApiProperty({
description: '模板名称',
required: false,
example: '人物手办',
})
@IsString()
@IsOptional()
name?: string;
@ApiProperty({ description: '模板描述', required: false, example: '将人物照片制作成精细的角色手办模型' })
@ApiProperty({
description: '模板描述',
required: false,
example: '将人物照片制作成精细的角色手办模型',
})
@IsString()
@IsOptional()
description?: string;
@ApiProperty({ description: '积分消耗', required: false, example: 28, minimum: 0 })
@ApiProperty({
description: '积分消耗',
required: false,
example: 28,
minimum: 0,
})
@IsNumber()
@Min(0)
@IsOptional()
@ -126,17 +179,29 @@ export class UpdateTemplateDto {
@IsOptional()
version?: string;
@ApiProperty({ description: '输入示例URL', required: false, example: 'https://example.com/input.jpg' })
@ApiProperty({
description: '输入示例URL',
required: false,
example: 'https://example.com/input.jpg',
})
@IsString()
@IsOptional()
inputExampleUrl?: string;
@ApiProperty({ description: '输出示例URL', required: false, example: 'https://example.com/output.jpg' })
@ApiProperty({
description: '输出示例URL',
required: false,
example: 'https://example.com/output.jpg',
})
@IsString()
@IsOptional()
outputExampleUrl?: string;
@ApiProperty({ description: '标签数组', required: false, example: ['人物', '手办', '模型'] })
@ApiProperty({
description: '标签数组',
required: false,
example: ['人物', '手办', '模型'],
})
@IsArray()
@IsOptional()
tags?: string[];
@ -146,12 +211,20 @@ export class UpdateTemplateDto {
@IsOptional()
templateType?: TemplateType;
@ApiProperty({ description: '模板类名', required: false, example: 'CharacterFigurineTemplate' })
@ApiProperty({
description: '模板类名',
required: false,
example: 'CharacterFigurineTemplate',
})
@IsString()
@IsOptional()
templateClass?: string;
@ApiProperty({ description: '图片模型', required: false, example: 'flux-dev' })
@ApiProperty({
description: '图片模型',
required: false,
example: 'flux-dev',
})
@IsString()
@IsOptional()
imageModel?: string;
@ -161,7 +234,11 @@ export class UpdateTemplateDto {
@IsOptional()
imagePrompt?: string;
@ApiProperty({ description: '视频模型', required: false, example: 'runway-gen3' })
@ApiProperty({
description: '视频模型',
required: false,
example: 'runway-gen3',
})
@IsString()
@IsOptional()
videoModel?: string;
@ -171,7 +248,13 @@ export class UpdateTemplateDto {
@IsOptional()
videoPrompt?: string;
@ApiProperty({ description: '视频时长(秒)', required: false, example: 10, minimum: 1, maximum: 300 })
@ApiProperty({
description: '视频时长(秒)',
required: false,
example: 10,
minimum: 1,
maximum: 300,
})
@IsNumber()
@IsOptional()
@Min(1)
@ -197,7 +280,7 @@ export class UpdateTemplateDto {
export class TemplateExecuteDto {
@ApiProperty({
description: '输入图片URL',
example: 'https://cdn.example.com/upload/image.jpg'
example: 'https://cdn.example.com/upload/image.jpg',
})
@IsString()
imageUrl: string;
@ -236,10 +319,17 @@ export class TemplateListDto {
@ApiProperty({ description: '模板名称', example: '人物手办' })
name: string;
@ApiProperty({ description: '模板描述', example: '将人物照片制作成精细的角色手办模型' })
@ApiProperty({
description: '模板描述',
example: '将人物照片制作成精细的角色手办模型',
})
description: string;
@ApiProperty({ enum: TemplateType, description: '模板类型', example: 'video' })
@ApiProperty({
enum: TemplateType,
description: '模板类型',
example: 'video',
})
templateType: TemplateType;
@ApiProperty({ description: '积分消耗', example: 28 })
@ -254,7 +344,11 @@ export class TemplateListDto {
@ApiProperty({ description: '输出示例URL', required: false })
outputExampleUrl?: string;
@ApiProperty({ type: [String], description: '标签数组', example: ['人物', '手办', '模型'] })
@ApiProperty({
type: [String],
description: '标签数组',
example: ['人物', '手办', '模型'],
})
tags: string[];
@ApiProperty({ description: '是否启用', example: true })
@ -267,7 +361,10 @@ export class TemplateListDto {
export class BatchExecuteDto {
@ApiProperty({
description: '输入图片URL数组',
example: ['https://cdn.example.com/upload/image1.jpg', 'https://cdn.example.com/upload/image2.jpg']
example: [
'https://cdn.example.com/upload/image1.jpg',
'https://cdn.example.com/upload/image2.jpg',
],
})
@IsArray()
imageUrls: string[];

View File

@ -59,7 +59,7 @@ export class TemplateExecutionEntity {
userId: string;
/** 执行平台 - 任务发起的平台来源 */
@Column({ type: 'enum', enum: PlatformType, enumName: 'PlatformType', })
@Column({ type: 'enum', enum: PlatformType, enumName: 'PlatformType' })
platform: PlatformType;
/** 执行类型 - 标识生成内容的类型(图片或视频) */

View File

@ -40,4 +40,4 @@ async function generateSwaggerJson() {
await app.close();
}
generateSwaggerJson().catch(console.error);
generateSwaggerJson().catch(console.error);

View File

@ -7,11 +7,13 @@ async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 启用全局验证管道
app.useGlobalPipes(new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}));
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
// 设置Swagger文档在全局前缀之前
try {
@ -23,14 +25,13 @@ async function bootstrap() {
// 设置API前缀排除docs路径
app.setGlobalPrefix('api/v1', {
exclude: ['docs', 'docs/(.*)']
exclude: ['docs', 'docs/(.*)'],
});
// 启用 CORS 支持多平台访问
app.enableCors({
origin: process.env.NODE_ENV === 'production'
? ['https://example.com']
: true,
origin:
process.env.NODE_ENV === 'production' ? ['https://example.com'] : true,
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
credentials: true,

View File

@ -6,7 +6,10 @@ import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { BaseAdapter } from './base.adapter';
import { UserEntity } from '../../entities/user.entity';
import { PlatformUserEntity, PlatformType } from '../../entities/platform-user.entity';
import {
PlatformUserEntity,
PlatformType,
} from '../../entities/platform-user.entity';
import {
PlatformLoginData,
PlatformRegisterData,

View File

@ -6,7 +6,10 @@ import { ConfigService } from '@nestjs/config';
import { JwtService } from '@nestjs/jwt';
import { BaseAdapter } from './base.adapter';
import { UserEntity } from '../../entities/user.entity';
import { PlatformUserEntity, PlatformType } from '../../entities/platform-user.entity';
import {
PlatformUserEntity,
PlatformType,
} from '../../entities/platform-user.entity';
import {
PlatformLoginData,
PlatformRegisterData,

View File

@ -47,11 +47,11 @@ export class UnifiedUserController {
userInfo: {
id: 'user-uuid',
nickname: '用户昵称',
avatarUrl: 'https://example.com/avatar.jpg'
}
}
}
}
avatarUrl: 'https://example.com/avatar.jpg',
},
},
},
},
})
@ApiResponse({ status: 400, description: '请求参数错误' })
@ApiResponse({ status: 401, description: '平台授权失败' })
@ -81,10 +81,10 @@ export class UnifiedUserController {
message: '注册成功',
data: {
userId: 'user-uuid',
unifiedUserId: 'unified-user-123'
}
}
}
unifiedUserId: 'unified-user-123',
},
},
},
})
async register(@Body() registerDto: PlatformLoginDto) {
const result = await this.unifiedUserService.register(
@ -119,10 +119,10 @@ export class UnifiedUserController {
avatarUrl: 'https://example.com/avatar.jpg',
phone: '13800138000',
email: 'user@example.com',
platformUsers: []
}
}
}
platformUsers: [],
},
},
},
})
@ApiResponse({ status: 401, description: '未授权访问' })
async getProfile(@Request() req) {
@ -147,9 +147,9 @@ export class UnifiedUserController {
schema: {
example: {
code: 200,
message: '更新成功'
}
}
message: '更新成功',
},
},
})
@ApiResponse({ status: 401, description: '未授权访问' })
async updateProfile(@Request() req, @Body() updateDto: UserInfoUpdateDto) {
@ -177,9 +177,9 @@ export class UnifiedUserController {
schema: {
example: {
code: 200,
message: '绑定成功'
}
}
message: '绑定成功',
},
},
})
@ApiResponse({ status: 401, description: '未授权访问' })
@ApiResponse({ status: 409, description: '平台账号已绑定其他用户' })
@ -208,9 +208,9 @@ export class UnifiedUserController {
schema: {
example: {
code: 200,
message: '解绑成功'
}
}
message: '解绑成功',
},
},
})
@ApiResponse({ status: 401, description: '未授权访问' })
@ApiResponse({ status: 404, description: '平台绑定不存在' })
@ -241,16 +241,16 @@ export class UnifiedUserController {
{
type: 'wechat',
name: '微信小程序',
supported: true
supported: true,
},
{
type: 'alipay',
name: '支付宝小程序',
supported: true
}
]
}
}
supported: true,
},
],
},
},
})
async getSupportedPlatforms() {
const platforms = this.unifiedUserService.getSupportedPlatforms();