lovart3/app/api/resize/route.ts
Your Name bbf9768bc9 添加尺寸调整功能,统一图片处理按钮样式
- 新增尺寸调整功能:用户可以自定义画布尺寸、背景颜色、图片位置和缩放
- 创建/api/resize API端点:上传处理后的图片到存储桶并保存到数据库
- 统一图片处理按钮样式:将变清晰、抠图、图像拓展按钮底色改为70%透明黑色
- 支持透明背景和彩色背景的尺寸调整
- 图片可拖拽移动和滚轮缩放
- 处理后的图片自动保存到相册和生图记录
2025-12-29 23:02:30 +08:00

200 lines
6.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/app/(auth)/auth";
import { uploadFile } from "@/lib/storage/service";
import { db } from "@/lib/db";
import { generatedImage } from "@/lib/db/schema";
/**
* 压缩图片生成缩略图(使用 sharp)
*/
async function createThumbnail(buffer: Buffer, mimeType: string): Promise<Buffer> {
try {
const sharp = await import("sharp");
// 创建缩略图(宽度 500px,高度按比例,质量 80%)
const thumbnailBuffer = await sharp.default(buffer)
.resize(500, null, {
fit: "inside",
withoutEnlargement: true,
})
.png({ quality: 80 }) // PNG格式保持透明背景
.toBuffer();
console.log(`[Resize] Thumbnail created, size: ${thumbnailBuffer.length} bytes (original: ${buffer.length} bytes)`);
return thumbnailBuffer;
} catch (error) {
console.error("[Resize] Failed to create thumbnail:", error);
// 如果失败,返回原图
return buffer;
}
}
/**
* 将 base64 图片上传到 S3,返回缩略图和原图的 URL
*/
async function uploadBase64ToS3(base64Data: string, mimeType: string = "image/png"): Promise<{ thumbnail: string; original: string }> {
try {
// 解析 base64 数据
const buffer = Buffer.from(base64Data, "base64");
// 生成文件名
const ext = mimeType.split("/")[1] || "png";
const timestamp = Date.now();
const randomStr = Math.random().toString(36).slice(2, 8);
const thumbnailFilename = `resize-thumb-${timestamp}-${randomStr}.${ext}`;
const originalFilename = `resize-orig-${timestamp}-${randomStr}.${ext}`;
console.log(`[Resize] Original size: ${buffer.length} bytes`);
// 生成缩略图
const thumbnailBuffer = await createThumbnail(buffer, mimeType);
// 转换为 ArrayBuffer
const thumbnailArrayBuffer = thumbnailBuffer.buffer.slice(
thumbnailBuffer.byteOffset,
thumbnailBuffer.byteOffset + thumbnailBuffer.byteLength
) as ArrayBuffer;
const originalArrayBuffer = buffer.buffer.slice(
buffer.byteOffset,
buffer.byteOffset + buffer.byteLength
) as ArrayBuffer;
// 并行上传缩略图和原图到 S3
console.log(`[Resize] Uploading thumbnail and original to S3...`);
const [thumbnailResult, originalResult] = await Promise.all([
uploadFile(thumbnailFilename, thumbnailArrayBuffer),
uploadFile(originalFilename, originalArrayBuffer),
]);
console.log(`[Resize] Uploaded to S3:`);
console.log(` - Thumbnail: ${thumbnailResult.url}`);
console.log(` - Original: ${originalResult.url}`);
return {
thumbnail: thumbnailResult.url,
original: originalResult.url,
};
} catch (error) {
console.error("[Resize] Failed to upload to S3:", error);
throw new Error(`图片上传到存储桶失败: ${error instanceof Error ? error.message : "未知错误"}。请检查系统设置中的存储配置。`);
}
}
/**
* 尺寸调整API路由
* 接收前端处理后的Base64图片,上传到S3并保存到数据库
*/
export async function POST(request: NextRequest) {
try {
// 0. 获取用户会话
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json(
{ error: "未授权,请先登录" },
{ status: 401 }
);
}
const userId = session.user.id;
// 1. 获取请求体
const contentType = request.headers.get("content-type") || "";
let imageBase64: string;
let prompt = "尺寸调整处理";
let chatId: string | null = null;
let messageId: string | null = null;
let canvasWidth: number;
let canvasHeight: number;
let mimeType = "image/png";
if (contentType.includes("application/json")) {
const body = await request.json();
imageBase64 = body.image_base64;
prompt = body.prompt || "尺寸调整处理";
chatId = body.chatId || null;
messageId = body.messageId || null;
canvasWidth = body.canvasWidth;
canvasHeight = body.canvasHeight;
mimeType = body.mimeType || "image/png";
if (!imageBase64) {
return NextResponse.json(
{ error: "缺少image_base64参数" },
{ status: 400 }
);
}
if (!canvasWidth || !canvasHeight) {
return NextResponse.json(
{ error: "缺少canvasWidth或canvasHeight参数" },
{ status: 400 }
);
}
} else {
return NextResponse.json(
{ error: "不支持的Content-Type,请使用application/json" },
{ status: 400 }
);
}
console.log(`[Resize API] 图片Base64长度: ${imageBase64.length} 字符`);
console.log(`[Resize API] 画布尺寸: ${canvasWidth}x${canvasHeight}`);
// 2. 上传处理后的图片到S3存储桶
let thumbnailUrl: string;
let originalUrl: string;
try {
const uploadResult = await uploadBase64ToS3(imageBase64, mimeType);
thumbnailUrl = uploadResult.thumbnail;
originalUrl = uploadResult.original;
console.log(`[Resize] ✅ Images uploaded to S3 successfully`);
} catch (uploadError) {
console.error("[Resize] ❌ Failed to upload to S3:", uploadError);
return NextResponse.json(
{
error: "图片上传到存储桶失败",
detail: uploadError instanceof Error ? uploadError.message : "未知错误"
},
{ status: 500 }
);
}
// 3. 保存图片记录到数据库(相册和生图记录)
try {
const [savedImage] = await db.insert(generatedImage).values({
userId,
chatId: chatId || null,
messageId: messageId || null,
thumbnailUrl,
originalUrl,
imageSize: `${canvasWidth}x${canvasHeight}`, // 使用实际尺寸
prompt: prompt || "尺寸调整处理",
modelId: "resize-1.0", // 标记为尺寸调整处理
}).returning();
console.log(`[Resize] ✅ 图片已保存到相册和生图记录,ID: ${savedImage.id}`);
} catch (dbError) {
console.error("[Resize] ❌ 保存到数据库失败:", dbError);
// 数据库保存失败时仍然返回成功,但记录错误(图片已上传到S3,用户可以访问)
}
// 4. 返回结果(包含S3 URL和Base64,前端可以选择使用)
return NextResponse.json({
success: true,
image_base64: imageBase64, // 保留Base64用于前端立即显示
thumbnail_url: thumbnailUrl, // S3缩略图URL
original_url: originalUrl, // S3原图URL
canvas_width: canvasWidth,
canvas_height: canvasHeight,
});
} catch (error) {
console.error("[Resize API] 处理错误:", error);
return NextResponse.json(
{ error: "服务器内部错误", detail: error instanceof Error ? error.message : "未知错误" },
{ status: 500 }
);
}
}