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, }) .jpeg({ quality: 80 }) // JPEG格式 .toBuffer(); console.log(`[Enhance] Thumbnail created, size: ${thumbnailBuffer.length} bytes (original: ${buffer.length} bytes)`); return thumbnailBuffer; } catch (error) { console.error("[Enhance] Failed to create thumbnail:", error); // 如果失败,返回原图 return buffer; } } /** * 将 base64 图片上传到 S3,返回缩略图和原图的 URL */ async function uploadBase64ToS3(base64Data: string, mimeType: string = "image/jpeg"): Promise<{ thumbnail: string; original: string }> { try { // 解析 base64 数据 const buffer = Buffer.from(base64Data, "base64"); // 生成文件名 const ext = mimeType.split("/")[1] || "jpg"; const timestamp = Date.now(); const randomStr = Math.random().toString(36).slice(2, 8); const thumbnailFilename = `enhance-thumb-${timestamp}-${randomStr}.${ext}`; const originalFilename = `enhance-orig-${timestamp}-${randomStr}.${ext}`; console.log(`[Enhance] 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(`[Enhance] Uploading thumbnail and original to S3...`); const [thumbnailResult, originalResult] = await Promise.all([ uploadFile(thumbnailFilename, thumbnailArrayBuffer), uploadFile(originalFilename, originalArrayBuffer), ]); console.log(`[Enhance] Uploaded to S3:`); console.log(` - Thumbnail: ${thumbnailResult.url}`); console.log(` - Original: ${originalResult.url}`); return { thumbnail: thumbnailResult.url, original: originalResult.url, }; } catch (error) { console.error("[Enhance] Failed to upload to S3:", error); throw new Error(`图片上传到存储桶失败: ${error instanceof Error ? error.message : "未知错误"}。请检查系统设置中的存储配置。`); } } /** * 轮询获取任务结果 */ async function pollTaskResult(apiUrl: string, apiKey: string, taskId: string, maxAttempts: number = 30): Promise { const pollUrl = `${apiUrl}/api/tasks/visual/scale/${taskId}`; for (let attempt = 0; attempt < maxAttempts; attempt++) { try { const response = await fetch(pollUrl, { method: "GET", headers: { "X-API-KEY": apiKey, }, }); if (!response.ok) { throw new Error(`轮询请求失败: ${response.status} ${response.statusText}`); } const result = await response.json(); // 检查任务状态 if (result.status === 200 && result.data) { if (result.data.state === 1) { // 任务完成 return result.data; } else if (result.data.state === 0) { // 任务进行中,继续轮询 await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒 continue; } else { // 任务失败 throw new Error(`任务失败: state=${result.data.state}`); } } else { throw new Error(`API返回错误: ${result.message || "未知错误"}`); } } catch (error) { if (attempt === maxAttempts - 1) { throw error; } await new Promise(resolve => setTimeout(resolve, 1000)); // 等待1秒后重试 } } throw new Error("轮询超时,任务未在30秒内完成"); } /** * 图片变清晰API路由 * 从数据库读取变清晰API配置,调用外部API进行图片增强处理 * 将处理后的图片上传到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. 从数据库获取变清晰API配置 const enhanceConfig = await getSystemSetting({ key: "enhance_api_config" }); if (!enhanceConfig || !enhanceConfig.value) { return NextResponse.json( { error: "变清晰API配置未设置,请在系统设置中配置" }, { status: 400 } ); } const config = enhanceConfig.value as { apiUrl?: string; apiKey?: string; type?: string; // "clean" | "face" scaleFactor?: number; // 1 | 2 | 4 }; if (!config.apiUrl || !config.apiKey) { return NextResponse.json( { error: "变清晰API地址或密钥未配置" }, { status: 400 } ); } // 2. 获取请求体(Base64图片或FormData) const contentType = request.headers.get("content-type") || ""; let imageBase64: string; let prompt = ""; // 用户提示词(可选) let chatId: string | null = null; // 聊天ID(可选) let messageId: string | null = null; // 消息ID(可选) if (contentType.includes("application/json")) { // JSON格式:Base64图片 const body = await request.json(); imageBase64 = body.image_base64; prompt = body.prompt || "图片变清晰处理"; chatId = body.chatId || null; messageId = body.messageId || null; if (!imageBase64) { return NextResponse.json( { error: "缺少image_base64参数" }, { status: 400 } ); } } else if (contentType.includes("multipart/form-data")) { // FormData格式:文件上传 const formData = await request.formData(); const file = formData.get("file") as File; if (!file) { return NextResponse.json( { error: "缺少file参数" }, { status: 400 } ); } // 将文件转换为Base64 const arrayBuffer = await file.arrayBuffer(); const buffer = Buffer.from(arrayBuffer); imageBase64 = buffer.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(使用同步模式) const apiUrl = config.apiUrl.endsWith("/") ? config.apiUrl.slice(0, -1) : config.apiUrl; const enhanceUrl = `${apiUrl}/api/tasks/visual/scale`; // 将Base64转换为Buffer const imageBuffer = Buffer.from(imageBase64, "base64"); // 创建FormData const formData = new FormData(); const blob = new Blob([imageBuffer], { type: "image/jpeg" }); formData.append("image_file", blob, "image.jpg"); formData.append("sync", "1"); // 同步模式 formData.append("type", config.type || "clean"); // 默认通用清晰化 if (config.scaleFactor) { formData.append("scale_factor", String(config.scaleFactor)); } formData.append("return_type", "2"); // 返回base64 const headers: HeadersInit = { "X-API-KEY": config.apiKey, }; const startTime = Date.now(); let response: Response; try { // 添加超时控制(60秒,因为同步模式可能需要更长时间) const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), 60000); response = await fetch(enhanceUrl, { method: "POST", headers, body: formData, signal: controller.signal, }); clearTimeout(timeoutId); } catch (fetchError) { const processingTime = (Date.now() - startTime) / 1000; console.error("[Enhance API] 网络请求失败:", { error: fetchError, enhanceUrl, processingTime, }); // 检查是否是网络连接错误 if (fetchError instanceof Error) { if (fetchError.name === 'AbortError') { return NextResponse.json( { error: "变清晰API请求超时", detail: "请求超过60秒未响应,请检查API服务器状态或网络连接", }, { status: 504 } ); } // 检查错误消息和cause中的网络错误 const errorMessage = fetchError.message || ''; const errorCause = (fetchError as any).cause; const causeMessage = errorCause?.message || ''; const causeCode = errorCause?.code || ''; // 检查各种网络连接错误 if (errorMessage.includes('EHOSTUNREACH') || errorMessage.includes('ENOTFOUND') || errorMessage.includes('ECONNREFUSED') || errorMessage.includes('No route to host') || causeMessage.includes('EHOSTUNREACH') || causeMessage.includes('ENOTFOUND') || causeMessage.includes('ECONNREFUSED') || causeMessage.includes('No route to host') || causeCode === 'EHOSTUNREACH' || causeCode === 'ENOTFOUND' || causeCode === 'ECONNREFUSED') { return NextResponse.json( { error: "无法连接到变清晰API服务器", detail: `服务器无法访问 ${enhanceUrl},请检查:\n1. API服务器是否在线\n2. 网络连接是否正常\n3. 防火墙设置是否正确\n4. API地址和密钥是否正确`, }, { status: 503 } ); } } return NextResponse.json( { error: "变清晰API请求失败", detail: fetchError instanceof Error ? fetchError.message : "未知网络错误", }, { status: 500 } ); } const processingTime = (Date.now() - startTime) / 1000; if (!response.ok) { const errorText = await response.text(); console.error("[Enhance API] 请求失败:", { status: response.status, statusText: response.statusText, error: errorText, enhanceUrl, }); return NextResponse.json( { error: "变清晰API请求失败", detail: errorText || response.statusText, }, { status: response.status } ); } const result = await response.json(); // 4. 处理返回结果 if (result.status !== 200 || !result.data) { return NextResponse.json( { error: "变清晰处理失败", detail: result.message || "未知错误" }, { status: 500 } ); } // 检查是否是异步任务(返回task_id) let enhancedImageBase64: string; let finalProcessingTime = processingTime; if (result.data.task_id && !result.data.image && !result.data.image_base64) { // 异步模式,需要轮询 console.log(`[Enhance] 异步任务,task_id: ${result.data.task_id}`); const taskResult = await pollTaskResult(apiUrl, config.apiKey, result.data.task_id); if (taskResult.image_base64) { enhancedImageBase64 = taskResult.image_base64; } else if (taskResult.image) { // 返回的是URL,需要下载 const imageResponse = await fetch(taskResult.image); const imageBuffer = await imageResponse.arrayBuffer(); enhancedImageBase64 = Buffer.from(imageBuffer).toString("base64"); } else { return NextResponse.json( { error: "变清晰处理失败", detail: "轮询结果未包含图片数据" }, { status: 500 } ); } finalProcessingTime = (Date.now() - startTime) / 1000; } else if (result.data.image_base64) { // 同步模式,直接返回base64 enhancedImageBase64 = result.data.image_base64; } else if (result.data.image) { // 返回的是URL,需要下载 const imageResponse = await fetch(result.data.image); if (!imageResponse.ok) { return NextResponse.json( { error: "变清晰处理失败", detail: "无法下载处理后的图片" }, { status: 500 } ); } const imageBuffer = await imageResponse.arrayBuffer(); enhancedImageBase64 = Buffer.from(imageBuffer).toString("base64"); } else { return NextResponse.json( { error: "变清晰处理失败", detail: "API未返回处理后的图片" }, { status: 500 } ); } if (!enhancedImageBase64) { return NextResponse.json( { error: "变清晰处理失败", detail: "无法获取处理后的图片" }, { status: 500 } ); } // 5. 上传处理后的图片到S3存储桶 let thumbnailUrl: string; let originalUrl: string; try { const uploadResult = await uploadBase64ToS3(enhancedImageBase64, "image/jpeg"); thumbnailUrl = uploadResult.thumbnail; originalUrl = uploadResult.original; console.log(`[Enhance] ✅ Images uploaded to S3 successfully`); } catch (uploadError) { console.error("[Enhance] ❌ Failed to upload to S3:", uploadError); return NextResponse.json( { error: "图片上传到存储桶失败", detail: uploadError instanceof Error ? uploadError.message : "未知错误" }, { status: 500 } ); } // 6. 保存图片记录到数据库 try { await db.insert(generatedImage).values({ userId, chatId: chatId || null, messageId: messageId || null, thumbnailUrl, originalUrl, imageSize: "2K", // 默认尺寸 prompt: prompt || "图片变清晰处理", modelId: "enhance-1.0", // 标记为变清晰处理 }); console.log(`[Enhance] ✅ Image record saved to database`); } catch (dbError) { console.error("[Enhance] ❌ Failed to save image record to database:", dbError); // 即使数据库保存失败,也返回成功(因为图片已上传到S3) } // 7. 返回结果(包含S3 URL和Base64,前端可以选择使用) return NextResponse.json({ success: true, image_base64: enhancedImageBase64, // 保留Base64用于前端立即显示 thumbnail_url: thumbnailUrl, // S3缩略图URL original_url: originalUrl, // S3原图URL processing_time: finalProcessingTime, }); } catch (error) { console.error("[Enhance API] 处理错误:", error); return NextResponse.json( { error: "服务器内部错误", detail: error instanceof Error ? error.message : "未知错误" }, { status: 500 } ); } }