fix(canvas): optimize Qiniu image previews
This commit is contained in:
parent
3d12c508d3
commit
f0a24a1a99
@ -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;
|
||||
}
|
||||
|
||||
@ -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/,
|
||||
|
||||
@ -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
|
||||
|
||||
41
tests/routes/image-delivery-qiniu.test.ts
Normal file
41
tests/routes/image-delivery-qiniu.test.ts
Normal 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?");
|
||||
});
|
||||
});
|
||||
Loading…
x
Reference in New Issue
Block a user