- 在应用中引入ImageCompositionController和ImageCompositionService - 实现图片拼接功能,支持多种排列方式和输出格式 - 更新Swagger文档,添加图片拼接API描述和示例 - 修改模板执行API描述,简化审核流程说明
216 lines
5.7 KiB
TypeScript
216 lines
5.7 KiB
TypeScript
import {
|
|
Controller,
|
|
Post,
|
|
Body,
|
|
HttpException,
|
|
HttpStatus,
|
|
UseGuards,
|
|
} from '@nestjs/common';
|
|
import {
|
|
ApiTags,
|
|
ApiOperation,
|
|
ApiResponse as SwaggerApiResponse,
|
|
ApiBody,
|
|
} from '@nestjs/swagger';
|
|
import { ImageCompositionService } from '../services/image-composition.service';
|
|
import {
|
|
ImageCompositionDto,
|
|
ImageCompositionResponseDto,
|
|
ArrangementDirection,
|
|
} from '../dto/image-composition.dto';
|
|
import { ApiCommonResponses } from '../decorators/api-common-responses.decorator';
|
|
import { PlatformAuthGuard } from '../platform/guards/platform-auth.guard';
|
|
import { ResponseUtil, ApiResponse } from '../utils/response.util';
|
|
|
|
@ApiTags('图片处理')
|
|
@Controller('image-composition')
|
|
@ApiCommonResponses()
|
|
export class ImageCompositionController {
|
|
constructor(
|
|
private readonly imageCompositionService: ImageCompositionService,
|
|
) {}
|
|
|
|
@Post('compose')
|
|
@UseGuards(PlatformAuthGuard)
|
|
@ApiOperation({
|
|
summary: '图片拼接',
|
|
description: '将多张图片按指定位置拼接到一个画布上,支持层级、透明度等设置',
|
|
})
|
|
@ApiBody({
|
|
type: ImageCompositionDto,
|
|
description: '图片拼接配置 - 简化版本',
|
|
examples: {
|
|
horizontal: {
|
|
summary: '水平排列示例',
|
|
value: {
|
|
images: [
|
|
'https://picsum.photos/300/200',
|
|
'https://picsum.photos/300/200',
|
|
'https://picsum.photos/300/200'
|
|
],
|
|
direction: 'horizontal',
|
|
outputFormat: 'jpeg',
|
|
maxWidth: 1920,
|
|
maxHeight: 1080,
|
|
quality: 80
|
|
},
|
|
},
|
|
vertical: {
|
|
summary: '垂直排列示例',
|
|
value: {
|
|
images: [
|
|
'https://picsum.photos/200/300',
|
|
'https://picsum.photos/200/300',
|
|
'https://picsum.photos/200/300'
|
|
],
|
|
direction: 'vertical',
|
|
outputFormat: 'webp',
|
|
maxWidth: 800,
|
|
maxHeight: 1200,
|
|
quality: 85
|
|
},
|
|
},
|
|
grid: {
|
|
summary: '网格排列示例',
|
|
value: {
|
|
images: [
|
|
'https://picsum.photos/200/200',
|
|
'https://picsum.photos/200/200',
|
|
'https://picsum.photos/200/200',
|
|
'https://picsum.photos/200/200',
|
|
'https://picsum.photos/200/200'
|
|
],
|
|
direction: 'grid',
|
|
outputFormat: 'jpeg',
|
|
maxWidth: 1024,
|
|
maxHeight: 1024,
|
|
quality: 75
|
|
},
|
|
},
|
|
},
|
|
})
|
|
@SwaggerApiResponse({
|
|
status: 200,
|
|
description: '图片拼接成功',
|
|
type: ImageCompositionResponseDto,
|
|
})
|
|
@SwaggerApiResponse({
|
|
status: 400,
|
|
description: '请求参数错误',
|
|
})
|
|
@SwaggerApiResponse({
|
|
status: 401,
|
|
description: '未授权访问',
|
|
})
|
|
@SwaggerApiResponse({
|
|
status: 500,
|
|
description: '服务器内部错误',
|
|
})
|
|
async composeImages(
|
|
@Body() compositionDto: ImageCompositionDto,
|
|
): Promise<ApiResponse<ImageCompositionResponseDto>> {
|
|
try {
|
|
// 基础参数验证
|
|
this.validateCompositionRequest(compositionDto);
|
|
|
|
const result = await this.imageCompositionService.composeImages(compositionDto);
|
|
|
|
return ResponseUtil.success(result, '图片拼接成功');
|
|
} catch (error) {
|
|
if (error instanceof HttpException) {
|
|
throw error;
|
|
}
|
|
|
|
throw new HttpException(
|
|
`图片拼接失败: ${error.message}`,
|
|
HttpStatus.INTERNAL_SERVER_ERROR,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 验证拼接请求参数 - 简化版本
|
|
*/
|
|
private validateCompositionRequest(dto: ImageCompositionDto): void {
|
|
// 验证图片数量
|
|
if (!dto.images || dto.images.length === 0) {
|
|
throw new HttpException(
|
|
'至少需要提供一张图片',
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
|
|
if (dto.images.length > 20) {
|
|
throw new HttpException(
|
|
'图片数量不能超过20张',
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
|
|
// 验证排列方向
|
|
const validDirections = Object.values(ArrangementDirection);
|
|
if (!validDirections.includes(dto.direction)) {
|
|
throw new HttpException(
|
|
`排列方向必须是以下值之一: ${validDirections.join(', ')}`,
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
|
|
// 验证图片URL格式
|
|
for (const imageUrl of dto.images) {
|
|
if (!imageUrl || typeof imageUrl !== 'string') {
|
|
throw new HttpException(
|
|
'图片URL不能为空',
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
|
|
// 检查是否为有效的URL或base64格式
|
|
const isValidUrl = this.isValidImageUrl(imageUrl);
|
|
const isValidBase64 = imageUrl.startsWith('data:image/');
|
|
|
|
if (!isValidUrl && !isValidBase64) {
|
|
throw new HttpException(
|
|
'图片必须是有效的HTTP(S) URL或base64格式',
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
}
|
|
|
|
// 验证尺寸参数
|
|
if (dto.maxWidth !== undefined && (dto.maxWidth < 100 || dto.maxWidth > 4096)) {
|
|
throw new HttpException(
|
|
'最大宽度必须在100-4096像素之间',
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
|
|
if (dto.maxHeight !== undefined && (dto.maxHeight < 100 || dto.maxHeight > 4096)) {
|
|
throw new HttpException(
|
|
'最大高度必须在100-4096像素之间',
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
|
|
// 验证质量参数
|
|
if (dto.quality !== undefined && (dto.quality < 1 || dto.quality > 100)) {
|
|
throw new HttpException(
|
|
'图片质量必须在1-100之间',
|
|
HttpStatus.BAD_REQUEST,
|
|
);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 验证图片URL格式
|
|
*/
|
|
private isValidImageUrl(url: string): boolean {
|
|
try {
|
|
const parsedUrl = new URL(url);
|
|
return parsedUrl.protocol === 'http:' || parsedUrl.protocol === 'https:';
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
}
|