ai-chatbot/lib/image-delivery.ts
Your Name a7b9533d72
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
perf: optimize oss image delivery globally
2026-04-29 22:32:06 +08:00

117 lines
3.0 KiB
TypeScript

const IMAGE_PROXY_PATH = "/api/image-proxy";
type ImageProxyFormat = "webp" | "avif" | "jpeg" | "png";
export type ImageProxyOptions = {
width?: number;
quality?: number;
format?: ImageProxyFormat;
delivery?: "display" | "proxy";
};
function clamp(value: number, min: number, max: number): number {
return Math.max(min, Math.min(max, value));
}
function normalizeWidth(width?: number): number | undefined {
if (!Number.isFinite(width) || !width) return undefined;
return clamp(Math.round(width), 64, 4096);
}
function normalizeQuality(quality?: number): number | undefined {
if (!Number.isFinite(quality) || !quality) return undefined;
return clamp(Math.round(quality), 40, 100);
}
function isAbsoluteHttpUrl(url: string): boolean {
return /^https?:\/\//i.test(url);
}
function isBypassUrl(url: string): boolean {
return (
!url ||
url.startsWith("data:") ||
url.startsWith("blob:") ||
url.startsWith(IMAGE_PROXY_PATH) ||
!isAbsoluteHttpUrl(url)
);
}
function isAliyunOssImageHost(hostname: string): boolean {
const normalized = hostname.trim().toLowerCase();
return normalized.endsWith(".aliyuncs.com") && normalized.includes(".oss-");
}
function resolveAliyunOssDisplayUrl(
url: string,
options: Pick<ImageProxyOptions, "width" | "quality" | "format">
): string | null {
let target: URL;
try {
target = new URL(url);
} catch {
return null;
}
if (!isAliyunOssImageHost(target.hostname)) {
return null;
}
if (target.searchParams.has("x-oss-process")) {
return target.toString();
}
const width = normalizeWidth(options.width);
const quality = normalizeQuality(options.quality);
const format = options.format;
if (!width && !quality && !format) {
return target.toString();
}
const processSteps = ["image"];
if (width) {
processSteps.push(`resize,w_${width}`);
}
if (quality) {
processSteps.push(`quality,q_${quality}`);
}
if (format) {
const ossFormat = format === "jpeg" ? "jpg" : format;
if (ossFormat !== "webp" && ossFormat !== "jpg" && ossFormat !== "png") {
return null;
}
processSteps.push(`format,${ossFormat}`);
}
target.searchParams.set("x-oss-process", processSteps.join("/"));
return target.toString();
}
export function buildImageProxyUrl(url: string, options: ImageProxyOptions = {}): string {
const source = String(url || "").trim();
if (isBypassUrl(source)) {
return source;
}
if (options.delivery !== "proxy") {
const directDisplayUrl = resolveAliyunOssDisplayUrl(source, options);
if (directDisplayUrl) {
return directDisplayUrl;
}
return source;
}
const width = normalizeWidth(options.width);
const quality = normalizeQuality(options.quality);
const format = options.format;
const params = new URLSearchParams();
params.set("url", source);
if (width) params.set("w", String(width));
if (quality) params.set("q", String(quality));
if (format) params.set("f", format);
return `${IMAGE_PROXY_PATH}?${params.toString()}`;
}