93 lines
2.4 KiB
TypeScript
93 lines
2.4 KiB
TypeScript
import "server-only";
|
|
|
|
import { uploadFile } from "@/lib/storage/service";
|
|
|
|
const DATA_IMAGE_URL_PATTERN = /^data:(image\/[a-z0-9.+-]+);base64,([a-z0-9+/=\s]+)$/i;
|
|
|
|
const MIME_TYPE_EXTENSION_MAP: Record<string, string> = {
|
|
"image/jpeg": "jpg",
|
|
"image/jpg": "jpg",
|
|
"image/png": "png",
|
|
"image/webp": "webp",
|
|
"image/gif": "gif",
|
|
"image/bmp": "bmp",
|
|
"image/tiff": "tiff",
|
|
"image/svg+xml": "svg",
|
|
};
|
|
|
|
function normalizeImageMimeType(mimeType: string) {
|
|
return String(mimeType || "").trim().toLowerCase();
|
|
}
|
|
|
|
function resolveImageExtension(mimeType: string) {
|
|
const normalized = normalizeImageMimeType(mimeType);
|
|
return MIME_TYPE_EXTENSION_MAP[normalized] || normalized.split("/")[1] || "png";
|
|
}
|
|
|
|
function toArrayBuffer(buffer: Buffer) {
|
|
return buffer.buffer.slice(
|
|
buffer.byteOffset,
|
|
buffer.byteOffset + buffer.byteLength
|
|
) as ArrayBuffer;
|
|
}
|
|
|
|
export function parseDataImageUrl(raw: string) {
|
|
const trimmed = String(raw || "").trim();
|
|
const match = trimmed.match(DATA_IMAGE_URL_PATTERN);
|
|
if (!match) return null;
|
|
|
|
const mimeType = normalizeImageMimeType(match[1]);
|
|
const base64Payload = match[2].replace(/\s+/g, "");
|
|
|
|
return {
|
|
mimeType,
|
|
base64Payload,
|
|
extension: resolveImageExtension(mimeType),
|
|
};
|
|
}
|
|
|
|
export async function uploadDataImageUrl(params: {
|
|
imageUrl: string;
|
|
prefix: string;
|
|
}) {
|
|
const parsed = parseDataImageUrl(params.imageUrl);
|
|
if (!parsed) {
|
|
throw new Error("图片必须是有效的 data:image;base64 格式");
|
|
}
|
|
|
|
const buffer = Buffer.from(parsed.base64Payload, "base64");
|
|
const filename = `${params.prefix}-${Date.now()}-${Math.random()
|
|
.toString(36)
|
|
.slice(2, 8)}.${parsed.extension}`;
|
|
const uploaded = await uploadFile(filename, toArrayBuffer(buffer));
|
|
return uploaded.url;
|
|
}
|
|
|
|
export async function ensurePublicImageUrl(params: {
|
|
imageUrl: string;
|
|
prefix: string;
|
|
requestOrigin?: string;
|
|
}) {
|
|
const trimmed = String(params.imageUrl || "").trim();
|
|
if (!trimmed) {
|
|
throw new Error("缺少输入图片");
|
|
}
|
|
|
|
if (/^https?:\/\//i.test(trimmed)) {
|
|
return trimmed;
|
|
}
|
|
|
|
if (params.requestOrigin && trimmed.startsWith("/")) {
|
|
return new URL(trimmed, params.requestOrigin).toString();
|
|
}
|
|
|
|
if (trimmed.startsWith("data:")) {
|
|
return uploadDataImageUrl({
|
|
imageUrl: trimmed,
|
|
prefix: params.prefix,
|
|
});
|
|
}
|
|
|
|
throw new Error("图片必须是公网 URL、站内相对路径或 data:image;base64");
|
|
}
|