- 新增尺寸调整功能:用户可以自定义画布尺寸、背景颜色、图片位置和缩放 - 创建/api/resize API端点:上传处理后的图片到存储桶并保存到数据库 - 统一图片处理按钮样式:将变清晰、抠图、图像拓展按钮底色改为70%透明黑色 - 支持透明背景和彩色背景的尺寸调整 - 图片可拖拽移动和滚轮缩放 - 处理后的图片自动保存到相册和生图记录
200 lines
6.8 KiB
TypeScript
200 lines
6.8 KiB
TypeScript
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 }
|
||
);
|
||
}
|
||
}
|
||
|