fix(canvas): optimize Qiniu image previews

This commit is contained in:
134 2026-08-20 19:47:02 +08:00
parent 3d12c508d3
commit f0a24a1a99
4 changed files with 79 additions and 5 deletions

View File

@ -53,12 +53,39 @@ function isTrustedDirectStorageImageHost(hostname: string): boolean {
);
}
function resolveTrustedDirectStorageDisplayUrl(url: string): string | null {
function isFirstPartyQiniuAsset(target: URL) {
return (
target.hostname.trim().toLowerCase() === "bbyy.ai" &&
target.pathname.startsWith("/uploads/")
);
}
function resolveTrustedDirectStorageDisplayUrl(
url: string,
options: Pick<ImageProxyOptions, "width" | "quality" | "format">
): string | null {
try {
const target = new URL(url);
return isTrustedDirectStorageImageHost(target.hostname)
? target.toString()
: null;
if (
!isTrustedDirectStorageImageHost(target.hostname) &&
!isFirstPartyQiniuAsset(target)
) {
return null;
}
const width = normalizeWidth(options.width);
const quality = normalizeQuality(options.quality);
const format = options.format;
if ((!width && !quality && !format) || target.search) {
return target.toString();
}
const operations = ["imageView2", "2"];
if (width) operations.push("w", String(width));
if (format) operations.push("format", format === "jpeg" ? "jpg" : format);
if (quality) operations.push("q", String(quality));
target.search = `?${operations.join("/")}`;
return target.toString();
} catch {
return null;
}
@ -116,7 +143,10 @@ export function buildImageProxyUrl(url: string, options: ImageProxyOptions = {})
return source;
}
const trustedDirectStorageUrl = resolveTrustedDirectStorageDisplayUrl(source);
const trustedDirectStorageUrl = resolveTrustedDirectStorageDisplayUrl(
source,
options
);
if (trustedDirectStorageUrl) {
return trustedDirectStorageUrl;
}

View File

@ -8,6 +8,7 @@ export default defineConfig({
/routes\/canvas-agent-operations\.test\.ts/,
/routes\/canvas-overlay-density\.test\.ts/,
/routes\/image-message-contracts\.test\.ts/,
/routes\/image-delivery-qiniu\.test\.ts/,
/routes\/production-local-fallbacks\.test\.ts/,
/routes\/canvas-entry-performance\.test\.ts/,
/routes\/settings-visual-consistency\.test\.ts/,

View File

@ -28,6 +28,7 @@ docker run --rm \
test -s "${candidate_dir}/BUILD_ID"
docker stop ai-chatbot-app >/dev/null
docker rm ai-chatbot-app >/dev/null
if [ -d "${app_dir}/.next" ]; then
mv "${app_dir}/.next" "$previous_dir"
fi
@ -39,6 +40,7 @@ docker compose -f "${compose_dir}/docker-compose.yml" up -d app
if ! curl -fsS --retry 12 --retry-delay 2 --max-time 10 \
http://127.0.0.1:3125/login >/dev/null; then
docker stop ai-chatbot-app >/dev/null 2>&1 || true
docker rm ai-chatbot-app >/dev/null 2>&1 || true
failed_dir="${app_dir}/.next-failed-$(date +%Y%m%dT%H%M%S)"
mv "${app_dir}/.next" "$failed_dir"
if [ -d "$previous_dir" ]; then

View File

@ -0,0 +1,41 @@
import { expect, test } from "@playwright/test";
import { buildImageProxyUrl } from "@/lib/image-delivery";
test.describe("Qiniu image delivery", () => {
test("uses Qiniu image processing for direct canvas previews", () => {
expect(
buildImageProxyUrl("https://yybb.top/uploads/result.png", {
width: 1200,
quality: 80,
format: "webp",
})
).toBe(
"https://yybb.top/uploads/result.png?imageView2/2/w/1200/format/webp/q/80"
);
});
test("optimizes legacy first-party upload URLs without changing their identity", () => {
expect(
buildImageProxyUrl("https://bbyy.ai/uploads/result.png", {
width: 960,
quality: 88,
format: "webp",
})
).toBe(
"https://bbyy.ai/uploads/result.png?imageView2/2/w/960/format/webp/q/88"
);
});
test("does not overwrite an existing storage query", () => {
const url = "https://yybb.top/uploads/result.png?imageView2/2/w/400";
expect(buildImageProxyUrl(url, { width: 1200, format: "webp" })).toBe(url);
});
test("keeps unrelated first-party URLs out of the Qiniu path", () => {
const result = buildImageProxyUrl("https://bbyy.ai/logo.png", {
delivery: "proxy",
width: 320,
});
expect(result).toContain("/api/image-proxy?");
});
});