import { NextRequest, NextResponse } from "next/server"; import { getSystemSetting } from "@/lib/db/queries"; 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 { 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(`[RMBG] Thumbnail created, size: ${thumbnailBuffer.length} bytes (original: ${buffer.length} bytes)`); return thumbnailBuffer; } catch (error) { console.error("[RMBG] Failed to create thumbnail:", error); // 如果失败,返回原图 return buffer; } } /** * 将图片 buffer 上传到 S3,返回缩略图和原图的 URL */ async function uploadBufferToS3(buffer: Buffer, mimeType: string = "image/png"): Promise<{ thumbnail: string; original: string }> { try { // 生成文件名 const ext = mimeType.split("/")[1] || "png"; const timestamp = Date.now(); const randomStr = Math.random().toString(36).slice(2, 8); const thumbnailFilename = `rmbg-thumb-${timestamp}-${randomStr}.${ext}`; const originalFilename = `rmbg-orig-${timestamp}-${randomStr}.${ext}`; console.log(`[RMBG] 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(`[RMBG] Uploading thumbnail and original to S3...`); const [thumbnailResult, originalResult] = await Promise.all([ uploadFile(thumbnailFilename, thumbnailArrayBuffer), uploadFile(originalFilename, originalArrayBuffer), ]); console.log(`[RMBG] Uploaded to S3:`); console.log(` - Thumbnail: ${thumbnailResult.url}`); console.log(` - Original: ${originalResult.url}`); return { thumbnail: thumbnailResult.url, original: originalResult.url, }; } catch (error) { console.error("[RMBG] Failed to upload to S3:", error); throw new Error(`图片上传到存储桶失败: ${error instanceof Error ? error.message : "未知错误"}。请检查系统设置中的存储配置。`); } } /** * 将 base64 图片上传到 S3,返回缩略图和原图的 URL */ async function uploadBase64ToS3(base64Data: string, mimeType: string = "image/png"): Promise<{ thumbnail: string; original: string }> { const buffer = Buffer.from(base64Data, "base64"); return uploadBufferToS3(buffer, mimeType); } /** * 使用 RMBG-2.0 API 进行抠图 */ async function processWithRMBG( imageBase64: string, config: { apiUrl: string; apiKey?: string } ): Promise<{ success: boolean; image_base64?: string; processing_time?: number; error?: string }> { const apiUrl = config.apiUrl.endsWith("/") ? `${config.apiUrl}rmbg/base64` : `${config.apiUrl}/rmbg/base64`; const requestBody = { image_base64: imageBase64, }; const headers: HeadersInit = { "Content-Type": "application/json", }; if (config.apiKey) { headers["X-API-Key"] = config.apiKey; } const startTime = Date.now(); // 添加超时控制(30秒) const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 30000); try { const response = await fetch(apiUrl, { method: "POST", headers, body: JSON.stringify(requestBody), signal: controller.signal, }); clearTimeout(timeoutId); const processingTime = (Date.now() - startTime) / 1000; if (!response.ok) { const errorText = await response.text(); console.error("[RMBG API] 请求失败:", { status: response.status, error: errorText }); return { success: false, error: errorText || response.statusText }; } const result = await response.json(); if (!result.success || !result.image_base64) { return { success: false, error: result.detail || "未知错误" }; } return { success: true, image_base64: result.image_base64, processing_time: result.processing_time || processingTime, }; } catch (error) { clearTimeout(timeoutId); if (error instanceof Error && error.name === 'AbortError') { return { success: false, error: "请求超时(30秒)" }; } return { success: false, error: error instanceof Error ? error.message : "未知错误" }; } } /** * 使用 PhotoRoom API 进行抠图 */ async function processWithPhotoRoom( imageBuffer: Buffer, apiKey: string ): Promise<{ success: boolean; imageBuffer?: Buffer; processing_time?: number; error?: string }> { const startTime = Date.now(); try { // 构建 FormData const formData = new FormData(); // 将 Buffer 转换为 Uint8Array 以避免类型问题 const uint8Array = new Uint8Array(imageBuffer); const blob = new Blob([uint8Array], { type: "image/png" }); formData.append("imageFile", blob, "image.png"); formData.append("referenceBox", "originalImage"); console.log("[PhotoRoom] Sending request to API..."); // 添加超时控制(60秒,PhotoRoom可能需要更长时间) const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 60000); const response = await fetch("https://image-api.photoroom.com/v2/edit", { method: "POST", headers: { "x-api-key": apiKey, "pr-hd-background-removal": "auto", }, body: formData, signal: controller.signal, }); clearTimeout(timeoutId); const processingTime = (Date.now() - startTime) / 1000; console.log("[PhotoRoom] Response status:", response.status); if (!response.ok) { const errorText = await response.text(); console.error("[PhotoRoom API] 请求失败:", { status: response.status, error: errorText }); return { success: false, error: `PhotoRoom API错误: ${response.status} - ${errorText}` }; } // PhotoRoom 直接返回图片二进制数据 const resultBuffer = Buffer.from(await response.arrayBuffer()); console.log("[PhotoRoom] Received image buffer, size:", resultBuffer.length, "bytes"); return { success: true, imageBuffer: resultBuffer, processing_time: processingTime, }; } catch (error) { if (error instanceof Error && error.name === 'AbortError') { return { success: false, error: "PhotoRoom请求超时(60秒)" }; } console.error("[PhotoRoom] Error:", error); return { success: false, error: error instanceof Error ? error.message : "未知错误" }; } } /** * 抠图API路由 * 从数据库读取API配置,支持 RMBG-2.0 和 PhotoRoom 两种服务 */ 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. 从数据库获取抠图API配置 const rmbgConfig = await getSystemSetting({ key: "rmbg_api_config" }); if (!rmbgConfig || !rmbgConfig.value) { return NextResponse.json( { error: "抠图API配置未设置,请在系统设置中配置" }, { status: 400 } ); } const config = rmbgConfig.value as { provider?: "rmbg" | "photoroom"; apiUrl?: string; apiKey?: string; photoroomApiKey?: string; }; const provider = config.provider || "rmbg"; // 验证配置 if (provider === "rmbg" && !config.apiUrl) { return NextResponse.json( { error: "RMBG API地址未配置" }, { status: 400 } ); } if (provider === "photoroom" && !config.photoroomApiKey) { return NextResponse.json( { error: "PhotoRoom API密钥未配置" }, { status: 400 } ); } // 2. 获取请求体 const contentType = request.headers.get("content-type") || ""; let imageBuffer: Buffer; let imageBase64: string; let prompt = ""; let chatId: string | null = null; let messageId: string | null = null; if (contentType.includes("application/json")) { const body = await request.json(); prompt = body.prompt || "抠图处理"; chatId = body.chatId || null; messageId = body.messageId || null; if (body.imageUrl) { console.log(`[RMBG] 从 URL 获取图片: ${body.imageUrl}`); try { const imageResponse = await fetch(body.imageUrl); if (!imageResponse.ok) { return NextResponse.json( { error: `无法获取图片: ${imageResponse.status} ${imageResponse.statusText}` }, { status: 400 } ); } const arrayBuffer = await imageResponse.arrayBuffer(); imageBuffer = Buffer.from(arrayBuffer); imageBase64 = imageBuffer.toString("base64"); console.log(`[RMBG] 图片已获取,大小: ${imageBuffer.length} bytes`); } catch (fetchError) { console.error("[RMBG] 获取图片失败:", fetchError); return NextResponse.json( { error: "无法获取图片,请检查图片链接是否有效" }, { status: 400 } ); } } else if (body.image_base64) { imageBase64 = body.image_base64; imageBuffer = Buffer.from(imageBase64, "base64"); } else { return NextResponse.json( { error: "缺少 imageUrl 或 image_base64 参数" }, { status: 400 } ); } } else if (contentType.includes("multipart/form-data")) { const formData = await request.formData(); const file = formData.get("file") as File; if (!file) { return NextResponse.json( { error: "缺少file参数" }, { status: 400 } ); } const arrayBuffer = await file.arrayBuffer(); imageBuffer = Buffer.from(arrayBuffer); imageBase64 = imageBuffer.toString("base64"); prompt = (formData.get("prompt") as string) || "抠图处理"; chatId = (formData.get("chatId") as string) || null; messageId = (formData.get("messageId") as string) || null; } else { return NextResponse.json( { error: "不支持的Content-Type,请使用application/json或multipart/form-data" }, { status: 400 } ); } // 3. 根据提供商调用不同的API console.log(`[RMBG] Using provider: ${provider}`); let resultBuffer: Buffer; let resultBase64: string; let processingTime: number; if (provider === "photoroom") { // 使用 PhotoRoom API const result = await processWithPhotoRoom(imageBuffer, config.photoroomApiKey!); if (!result.success || !result.imageBuffer) { return NextResponse.json( { error: "抠图处理失败", detail: result.error }, { status: 500 } ); } resultBuffer = result.imageBuffer; resultBase64 = resultBuffer.toString("base64"); processingTime = result.processing_time || 0; } else { // 使用 RMBG-2.0 API const result = await processWithRMBG(imageBase64, { apiUrl: config.apiUrl!, apiKey: config.apiKey, }); if (!result.success || !result.image_base64) { return NextResponse.json( { error: "抠图处理失败", detail: result.error }, { status: 500 } ); } resultBase64 = result.image_base64; resultBuffer = Buffer.from(resultBase64, "base64"); processingTime = result.processing_time || 0; } // 4. 上传处理后的图片到S3 let thumbnailUrl: string; let originalUrl: string; try { const uploadResult = await uploadBufferToS3(resultBuffer, "image/png"); thumbnailUrl = uploadResult.thumbnail; originalUrl = uploadResult.original; console.log(`[RMBG] ✅ Images uploaded to S3 successfully`); } catch (uploadError) { console.error("[RMBG] ❌ Failed to upload to S3:", uploadError); return NextResponse.json( { error: "图片上传到存储桶失败", detail: uploadError instanceof Error ? uploadError.message : "未知错误" }, { status: 500 } ); } // 5. 保存图片记录到数据库 try { const [savedImage] = await db.insert(generatedImage).values({ userId, chatId: chatId || null, messageId: messageId || null, thumbnailUrl, originalUrl, imageSize: "2K", prompt: prompt || "抠图处理", modelId: provider === "photoroom" ? "photoroom-hd" : "rmbg-2.0", }).returning(); console.log(`[RMBG] ✅ 图片已保存到相册,ID: ${savedImage.id}`); } catch (dbError) { console.error("[RMBG] ❌ 保存到数据库失败:", dbError); } // 6. 返回结果 return NextResponse.json({ success: true, image_base64: resultBase64, thumbnail_url: thumbnailUrl, original_url: originalUrl, processing_time: processingTime, provider: provider, }); } catch (error) { console.error("[RMBG API] 处理错误:", error); return NextResponse.json( { error: "服务器内部错误", detail: error instanceof Error ? error.message : "未知错误" }, { status: 500 } ); } }