65 lines
2.6 KiB
TypeScript
65 lines
2.6 KiB
TypeScript
import type { NextApiRequest, NextApiResponse } from "next";
|
|
import sharp from "sharp";
|
|
import { authorizeDocuments, documentApiError } from "@/lib/documents-api";
|
|
import { saveDocumentImage } from "@/lib/document-images";
|
|
|
|
const maxUploadBytes = 8 * 1024 * 1024;
|
|
const maxStoredBytes = 2 * 1024 * 1024;
|
|
const imageTypes = new Set(["image/png", "image/jpeg", "image/webp"]);
|
|
const imageFormats = new Set(["png", "jpeg", "webp"]);
|
|
|
|
export const config = { api: { bodyParser: false } };
|
|
|
|
export default async function handler(request: NextApiRequest, response: NextApiResponse) {
|
|
try {
|
|
if (request.method !== "POST") {
|
|
response.setHeader("Allow", "POST");
|
|
return response.status(405).end();
|
|
}
|
|
if (!authorizeDocuments(request, response, true)) return;
|
|
|
|
const type = request.headers["content-type"]?.split(";")[0].trim().toLowerCase();
|
|
if (!type || !imageTypes.has(type)) {
|
|
return response.status(415).json({ error: "请上传 PNG、JPEG 或 WebP 图片" });
|
|
}
|
|
if (Number(request.headers["content-length"]) > maxUploadBytes) {
|
|
return response.status(413).json({ error: "图片上传上限为 8 MiB" });
|
|
}
|
|
|
|
const chunks: Buffer[] = [];
|
|
let length = 0;
|
|
for await (const chunk of request.iterator({ destroyOnReturn: false })) {
|
|
const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
length += bytes.length;
|
|
if (length > maxUploadBytes) {
|
|
return response.status(413).json({ error: "图片上传上限为 8 MiB" });
|
|
}
|
|
chunks.push(bytes);
|
|
}
|
|
|
|
let encoded: Buffer;
|
|
try {
|
|
const input = sharp(Buffer.concat(chunks), { limitInputPixels: 25_000_000 });
|
|
const { format, pages } = await input.metadata();
|
|
if (!format || !imageFormats.has(format) || type !== `image/${format}` || (pages ?? 1) > 1) {
|
|
return response.status(415).json({ error: "图片格式需与声明一致,且只支持静态图片" });
|
|
}
|
|
encoded = await input.rotate()
|
|
.resize({ width: 2000, height: 4000, fit: "inside", withoutEnlargement: true })
|
|
.webp({ quality: 85 })
|
|
.toBuffer();
|
|
} catch {
|
|
return response.status(400).json({ error: "无法解码图片,或像素数量超过限制" });
|
|
}
|
|
if (encoded.length > maxStoredBytes) {
|
|
return response.status(413).json({ error: "处理后的图片超过 2 MiB,请缩小后重试" });
|
|
}
|
|
|
|
const id = saveDocumentImage(encoded);
|
|
const url = `/api/documents/images/${id}`;
|
|
return response.status(200).json({ id, url, contentType: "image/webp", size: encoded.length });
|
|
} catch (error) {
|
|
return documentApiError(response, error);
|
|
}
|
|
}
|