106 lines
3.5 KiB
TypeScript
106 lines
3.5 KiB
TypeScript
import { NextRequest, NextResponse } from "next/server";
|
|
import { auth } from "@/app/(auth)/auth";
|
|
import { fetchImageBufferFromUrl } from "@/lib/ai/rmbg/service";
|
|
import { uploadFile } from "@/lib/storage/service";
|
|
|
|
export const runtime = "nodejs";
|
|
export const maxDuration = 120;
|
|
|
|
const DEFAULT_WIDTH = 790;
|
|
const DEFAULT_HEIGHT = 1300;
|
|
const MAX_DIMENSION = 4096;
|
|
|
|
function bufferToArrayBuffer(buffer: Buffer): ArrayBuffer {
|
|
return buffer.buffer.slice(
|
|
buffer.byteOffset,
|
|
buffer.byteOffset + buffer.byteLength,
|
|
) as ArrayBuffer;
|
|
}
|
|
|
|
function normalizeDimension(value: unknown, fallback: number) {
|
|
const next = Math.round(Number(value));
|
|
if (!Number.isFinite(next) || next <= 0) return fallback;
|
|
return Math.min(MAX_DIMENSION, Math.max(64, next));
|
|
}
|
|
|
|
function normalizeFit(value: unknown) {
|
|
const fit = String(value || "").trim();
|
|
return fit === "contain" ? "contain" : "cover-top";
|
|
}
|
|
|
|
async function createThumbnailBuffer(buffer: Buffer) {
|
|
const sharp = (await import("sharp")).default;
|
|
return sharp(buffer, { failOn: "none" })
|
|
.resize(500, null, {
|
|
fit: "inside",
|
|
withoutEnlargement: true,
|
|
})
|
|
.jpeg({ quality: 82, mozjpeg: true })
|
|
.toBuffer();
|
|
}
|
|
|
|
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 imageUrl = String(body.imageUrl || "").trim();
|
|
if (!/^https?:\/\//i.test(imageUrl)) {
|
|
return NextResponse.json({ success: false, error: "缺少有效图片地址" }, { status: 400 });
|
|
}
|
|
|
|
const width = normalizeDimension(body.width, DEFAULT_WIDTH);
|
|
const height = normalizeDimension(body.height, DEFAULT_HEIGHT);
|
|
const fit = normalizeFit(body.fit);
|
|
const sourceBuffer = await fetchImageBufferFromUrl(imageUrl);
|
|
const sharp = (await import("sharp")).default;
|
|
const pipeline = sharp(sourceBuffer, { failOn: "none" }).rotate();
|
|
const normalizedBuffer =
|
|
fit === "contain"
|
|
? await pipeline
|
|
.resize(width, height, {
|
|
fit: "contain",
|
|
position: "centre",
|
|
background: { r: 255, g: 255, b: 255, alpha: 1 },
|
|
withoutEnlargement: false,
|
|
})
|
|
.png()
|
|
.toBuffer()
|
|
: await pipeline
|
|
.resize(width, height, {
|
|
fit: "cover",
|
|
position: "top",
|
|
withoutEnlargement: false,
|
|
})
|
|
.png()
|
|
.toBuffer();
|
|
const thumbnailBuffer = await createThumbnailBuffer(normalizedBuffer);
|
|
const suffix = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
const [originalUpload, thumbnailUpload] = await Promise.all([
|
|
uploadFile(`agent-detail-normalized-${suffix}.png`, bufferToArrayBuffer(normalizedBuffer)),
|
|
uploadFile(`agent-detail-normalized-thumb-${suffix}.jpg`, bufferToArrayBuffer(thumbnailBuffer)),
|
|
]);
|
|
|
|
return NextResponse.json({
|
|
success: true,
|
|
data: {
|
|
originalUrl: originalUpload.url,
|
|
thumbnailUrl: thumbnailUpload.url,
|
|
imageSize: `${width}x${height}`,
|
|
},
|
|
});
|
|
} catch (error) {
|
|
console.error("[Agent Detail Image Normalize] Failed:", error);
|
|
return NextResponse.json(
|
|
{
|
|
success: false,
|
|
error: error instanceof Error ? error.message : "详情图尺寸归一化失败",
|
|
},
|
|
{ status: 500 },
|
|
);
|
|
}
|
|
}
|