113 lines
3.9 KiB
TypeScript

import { resolveCanvasCdnDownload } from "@/lib/storage/canvas-cdn-download";
import { prepareImageArchiveDownload } from "@/lib/storage/image-download-preparation";
import { after, NextRequest, NextResponse } from "next/server";
import { auth } from "@/app/(auth)/auth";
import {
buildAttachmentContentDisposition,
fetchCanvasDownloadAsset,
sanitizeCanvasDownloadFileName,
} from "@/lib/canvas/photoshop-safe-download";
export const runtime = "nodejs";
export const maxDuration = 120;
function getRequestOrigin(request: NextRequest): string {
const forwardedHost = (request.headers.get("x-forwarded-host") || "").split(",")[0]?.trim();
const forwardedProto = (request.headers.get("x-forwarded-proto") || "").split(",")[0]?.trim();
if (forwardedHost && forwardedProto) {
return `${forwardedProto}://${forwardedHost}`;
}
return new URL(request.url).origin;
}
function resolveDownloadUrl(rawUrl: unknown, origin: string): string {
const value = String(rawUrl || "").trim();
if (!value) return "";
try {
const parsed = new URL(value, origin);
if (!/^https?:$/i.test(parsed.protocol)) return "";
return parsed.toString();
} catch (_error) {
return "";
}
}
function resolveDownloadUrls(body: Record<string, unknown>, origin: string): string[] {
const rawCandidates = Array.isArray(body.urls) ? body.urls : [body.url];
const urls: string[] = [];
const seen = new Set<string>();
for (const rawUrl of rawCandidates) {
const url = resolveDownloadUrl(rawUrl, origin);
if (!url || seen.has(url)) continue;
seen.add(url);
urls.push(url);
}
return urls.slice(0, 6);
}
export async function POST(request: NextRequest) {
try {
const session = await auth();
if (!session?.user?.id) {
return NextResponse.json({ success: false, error: "未登录" }, { status: 401 });
}
const body = (await request.json().catch(() => ({}))) as Record<string, unknown>;
const requestOrigin = getRequestOrigin(request);
const urls = resolveDownloadUrls(body, requestOrigin);
if (urls.length === 0) {
return NextResponse.json({ success: false, error: "图片地址无效" }, { status: 400 });
}
const name = sanitizeCanvasDownloadFileName(String(body.name || ""), "canvas-image");
if (body.preferDirect === true) {
const { task, direct } = await resolveCanvasCdnDownload(urls, `${name}.png`).catch(() => ({ task: null, direct: null }));
if (direct) return NextResponse.json(direct, { headers: { "Cache-Control": "no-store" } });
if (task?.targetUrl) {
if (!urls.includes(task.targetUrl)) urls.unshift(task.targetUrl);
after(() => prepareImageArchiveDownload(task.id));
}
}
const cookie = request.headers.get("cookie") || "";
let result: Awaited<ReturnType<typeof fetchCanvasDownloadAsset>> | null = null;
const failures: string[] = [];
for (const url of urls) {
result = await fetchCanvasDownloadAsset({
file: { url, name },
index: 0,
requestOrigin,
cookie,
includeSequencePrefix: false,
});
if (result.ok) break;
failures.push(`${url}: ${result.error}`);
}
if (!result?.ok) {
if (failures.length > 0) {
console.warn("[CanvasDownloadImage] All candidates failed:", failures);
}
return NextResponse.json(
{ success: false, error: result?.error || "图片源文件暂时不可访问" },
{ status: 502 }
);
}
return new NextResponse(result.bytes, {
status: 200,
headers: {
"Content-Type": result.contentType,
"Content-Length": String(result.bytes.length),
"Content-Disposition": buildAttachmentContentDisposition(result.filename),
"Cache-Control": "no-store",
},
});
} catch (error) {
console.error("[CanvasDownloadImage] Failed:", error);
return NextResponse.json({ success: false, error: "图片下载失败" }, { status: 500 });
}
}