Your Name 2a40607fc1
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
fix(scene-fusion): strengthen random-bg replacement, UI actions, and gallery persistence
2026-03-06 00:56:42 +08:00

377 lines
12 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 { db } from "@/lib/db";
import { systemSettings, generatedImage } from "@/lib/db/schema";
import { eq } from "drizzle-orm";
import { uploadFile } from "@/lib/storage/service";
// PicWish API 需要公网可访问的 HTTP/HTTPS URL
const WATERMARK_SYNC_WAIT_TIMEOUT_MS = 20_000;
const WATERMARK_POLL_INTERVAL_MS = 1200;
// 获取去水印API配置
async function getWatermarkRemoveConfig() {
try {
const setting = await db
.select()
.from(systemSettings)
.where(eq(systemSettings.key, "watermark_remove_config"))
.limit(1);
if (setting.length > 0 && setting[0].value) {
return setting[0].value as { apiKey: string };
}
return null;
} catch (error) {
console.error("[WatermarkRemove] 获取配置失败:", error);
return null;
}
}
// 轮询获取处理结果
async function pollForResult(
taskId: string,
apiKey: string,
timeoutMs = WATERMARK_SYNC_WAIT_TIMEOUT_MS
): Promise<{ success: boolean; imageUrl?: string; error?: string; pending?: boolean }> {
const maxAttempts = Math.max(1, Math.ceil(timeoutMs / WATERMARK_POLL_INTERVAL_MS));
const pollInterval = WATERMARK_POLL_INTERVAL_MS;
for (let attempt = 0; attempt < maxAttempts; attempt++) {
try {
const response = await fetch(
`https://techsz.aoscdn.com/api/tasks/visual/external/watermark-remove/${taskId}`,
{
method: "GET",
headers: {
"X-API-KEY": apiKey,
},
}
);
if (!response.ok) {
const errorText = await response.text();
console.error(`[WatermarkRemove] 轮询失败 (attempt ${attempt + 1}):`, errorText);
// 继续轮询
await new Promise(resolve => setTimeout(resolve, pollInterval));
continue;
}
const result = await response.json();
console.log(`[WatermarkRemove] 轮询结果 (attempt ${attempt + 1}):`, JSON.stringify(result).substring(0, 200));
const state = result.data?.state;
const stateDetail = result.data?.state_detail;
// 检查任务状态
if (state === 1) {
// 任务完成 - PicWish API 返回 "file" 字段
const imageUrl = result.data?.file || result.data?.image;
if (imageUrl) {
return { success: true, imageUrl };
}
return { success: false, error: "处理完成但未返回图片URL" };
} else if (typeof state === "number" && state < 0) {
// 任务失败
return { success: false, error: result.data?.error || "去水印处理失败" };
} else if (state === 4 || state === 0) {
// 明确处理中状态,继续轮询
console.log(`[WatermarkRemove] 任务处理中: state=${state}, detail=${stateDetail || "N/A"}`);
}
// 其他状态默认继续轮询
await new Promise(resolve => setTimeout(resolve, pollInterval));
} catch (error) {
console.error(`[WatermarkRemove] 轮询错误 (attempt ${attempt + 1}):`, error);
await new Promise(resolve => setTimeout(resolve, pollInterval));
}
}
return {
success: false,
pending: true,
error: "去水印任务仍在处理中",
};
}
function buildWatermarkTaskPendingResponse(taskId: string) {
return NextResponse.json(
{
success: true,
processing: true,
status: "running",
taskId,
pollAfterMs: WATERMARK_POLL_INTERVAL_MS,
},
{ status: 202 }
);
}
async function finalizeWatermarkTaskResult(params: {
taskId: string;
userId: string;
startedAt?: number;
apiKey: string;
}): Promise<{
success: boolean;
original_url: string;
thumbnail_url: string;
processing_time?: number;
}> {
const pollResult = await pollForResult(params.taskId, params.apiKey, WATERMARK_SYNC_WAIT_TIMEOUT_MS);
if (pollResult.pending) {
throw new Error("WATERMARK_TASK_PENDING");
}
if (!pollResult.success || !pollResult.imageUrl) {
throw new Error(pollResult.error || "去水印处理失败");
}
const processedImageUrl = pollResult.imageUrl;
try {
const imageResponse = await fetch(processedImageUrl);
if (!imageResponse.ok) {
throw new Error("下载处理后的图片失败");
}
const imageBuffer = await imageResponse.arrayBuffer();
const filename = `watermark-removed-${Date.now()}.png`;
const uploadResult = await uploadFile(filename, imageBuffer);
let saved = false;
let saveError: unknown = null;
for (let attempt = 1; attempt <= 3; attempt += 1) {
try {
await db.insert(generatedImage).values({
userId: params.userId,
thumbnailUrl: uploadResult.url,
originalUrl: uploadResult.url,
imageSize: "2K",
prompt: "去水印处理",
modelId: "watermark-remove",
});
saved = true;
break;
} catch (error) {
saveError = error;
if (attempt < 3) {
await new Promise((resolve) => setTimeout(resolve, 120 * attempt));
}
}
}
if (!saved) {
console.error("[WatermarkRemove] 保存到相册失败:", saveError);
throw new Error("去水印成功但保存到相册失败,请稍后重试");
}
return {
success: true,
original_url: uploadResult.url,
thumbnail_url: uploadResult.url,
processing_time:
typeof params.startedAt === "number" ? (Date.now() - params.startedAt) / 1000 : undefined,
};
} catch (uploadError) {
console.error("[WatermarkRemove] 上传结果失败,回退原始URL:", uploadError);
return {
success: true,
original_url: processedImageUrl,
thumbnail_url: processedImageUrl,
processing_time:
typeof params.startedAt === "number" ? (Date.now() - params.startedAt) / 1000 : undefined,
};
}
}
export async function GET(request: NextRequest) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "请先登录" }, { status: 401 });
}
const { searchParams } = new URL(request.url);
const taskId = String(searchParams.get("taskId") || "").trim();
if (!taskId) {
return NextResponse.json({ error: "缺少 taskId 参数" }, { status: 400 });
}
const config = await getWatermarkRemoveConfig();
if (!config?.apiKey) {
return NextResponse.json(
{ error: "去水印服务未配置,请联系管理员在系统设置中配置API密钥" },
{ status: 500 }
);
}
try {
const result = await finalizeWatermarkTaskResult({
taskId,
userId: session.user.id,
apiKey: config.apiKey,
});
return NextResponse.json(result);
} catch (error) {
if (error instanceof Error && error.message === "WATERMARK_TASK_PENDING") {
return buildWatermarkTaskPendingResponse(taskId);
}
throw error;
}
} catch (error) {
console.error("[WatermarkRemove][TaskStatus] 处理错误:", error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : "去水印处理失败" },
{ status: 500 }
);
}
}
export async function POST(request: NextRequest) {
try {
// 验证用户登录
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ error: "请先登录" }, { status: 401 });
}
const startTime = Date.now();
// 获取请求参数
const body = await request.json();
const { image_url } = body;
if (!image_url) {
return NextResponse.json(
{ error: "请提供图片URL" },
{ status: 400 }
);
}
// 获取API配置
const config = await getWatermarkRemoveConfig();
if (!config?.apiKey) {
return NextResponse.json(
{ error: "去水印服务未配置,请联系管理员在系统设置中配置API密钥" },
{ status: 500 }
);
}
console.log("[WatermarkRemove] 使用原图URL:", image_url.substring(0, 100) + "...");
// 处理图片URL,确保获取公网可访问的URL
let publicImageUrl: string;
// 如果是 base64 格式的 data URL,先上传到 S3
if (image_url.startsWith("data:")) {
console.log("[WatermarkRemove] 检测到base64格式,上传到S3...");
try {
// 解析 base64 data URL
const matches = image_url.match(/^data:image\/(\w+);base64,(.+)$/);
if (!matches) {
return NextResponse.json(
{ error: "无效的图片格式" },
{ status: 400 }
);
}
const ext = matches[1] === "jpeg" ? "jpg" : matches[1];
const base64Data = matches[2];
const buffer = Buffer.from(base64Data, "base64");
// 上传到 S3
const filename = `watermark-input-${Date.now()}.${ext}`;
const uploadResult = await uploadFile(filename, buffer.buffer.slice(buffer.byteOffset, buffer.byteOffset + buffer.byteLength));
publicImageUrl = uploadResult.url;
console.log("[WatermarkRemove] base64图片已上传到S3:", publicImageUrl);
} catch (uploadError) {
console.error("[WatermarkRemove] base64上传失败:", uploadError);
return NextResponse.json(
{ error: "图片上传失败" },
{ status: 500 }
);
}
}
// 如果是 HTTP/HTTPS URL,下载后重新上传确保公网可访问
else if (image_url.startsWith("http://") || image_url.startsWith("https://")) {
try {
console.log("[WatermarkRemove] 下载原图并上传到S3...");
const imageResponse = await fetch(image_url);
if (!imageResponse.ok) {
throw new Error("下载原图失败");
}
const imageBuffer = await imageResponse.arrayBuffer();
const filename = `watermark-input-${Date.now()}.jpg`;
const uploadResult = await uploadFile(filename, imageBuffer);
publicImageUrl = uploadResult.url;
console.log("[WatermarkRemove] 图片已上传到S3:", publicImageUrl);
} catch (downloadError) {
console.error("[WatermarkRemove] 下载/上传原图失败:", downloadError);
// 如果下载失败,尝试使用原始URL
console.log("[WatermarkRemove] 尝试使用原始URL");
publicImageUrl = image_url;
}
} else {
return NextResponse.json(
{ error: "图片URL格式无效" },
{ status: 400 }
);
}
console.log("[WatermarkRemove] 开始处理图片:", publicImageUrl);
// 创建去水印任务
const formData = new FormData();
formData.append("sync", "0");
formData.append("url", publicImageUrl);
const createResponse = await fetch(
"https://techsz.aoscdn.com/api/tasks/visual/external/watermark-remove",
{
method: "POST",
headers: {
"X-API-KEY": config.apiKey,
},
body: formData,
}
);
if (!createResponse.ok) {
const errorText = await createResponse.text();
console.error("[WatermarkRemove] 创建任务失败:", errorText);
return NextResponse.json(
{ error: "创建去水印任务失败" },
{ status: 500 }
);
}
const createResult = await createResponse.json();
console.log("[WatermarkRemove] 创建任务结果:", JSON.stringify(createResult).substring(0, 200));
const taskId = createResult.data?.task_id;
if (!taskId) {
return NextResponse.json(
{ error: "未获取到任务ID" },
{ status: 500 }
);
}
try {
const result = await finalizeWatermarkTaskResult({
taskId,
userId: session.user.id,
startedAt: startTime,
apiKey: config.apiKey,
});
return NextResponse.json(result);
} catch (error) {
if (error instanceof Error && error.message === "WATERMARK_TASK_PENDING") {
return buildWatermarkTaskPendingResponse(taskId);
}
throw error;
}
} catch (error) {
console.error("[WatermarkRemove] 处理错误:", error);
return NextResponse.json(
{ error: error instanceof Error ? error.message : "去水印处理失败" },
{ status: 500 }
);
}
}