fix: fail unsupported psb templates fast

This commit is contained in:
Your Name 2026-06-04 13:26:14 +08:00
parent faa70a315c
commit c8073c6c46
4 changed files with 145 additions and 16 deletions

View File

@ -751,7 +751,7 @@ export function PsdTemplatesSettingsClient({
async function handleCreateTemplate(options?: { parseAfterUpload?: boolean }) {
const parseAfterUpload = options?.parseAfterUpload === true;
if (!uploadFile) {
toast.error("请先选择 PSD / PSB 文件");
toast.error("请先选择 PSD 文件");
return;
}
@ -2581,7 +2581,10 @@ export function PsdTemplatesSettingsClient({
<Card>
<CardHeader>
<CardTitle className="text-base">上传新模板</CardTitle>
<CardDescription>支持 PSD / PSB,建议图层命名清晰,便于后台配置图片位和文字位。</CardDescription>
<CardDescription>
支持 Photoshop PSD(.psd),建议图层命名清晰,便于后台配置图片位和文字位。PSB
请先在 Photoshop 中另存为 PSD 后上传。
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
@ -2742,11 +2745,11 @@ export function PsdTemplatesSettingsClient({
<Input
id="psdTemplateFile"
type="file"
accept=".psd,.psb"
accept=".psd"
onChange={handleTemplateFileChange}
/>
<p className="text-xs text-muted-foreground">
{uploadFile ? `已选择:${uploadFile.name}` : "支持 80MB 以内 PSD / PSB 文件"}
{uploadFile ? `已选择:${uploadFile.name}` : "支持 80MB 以内 PSD 文件"}
</p>
</div>
<div className="grid gap-3 sm:grid-cols-2">

View File

@ -12,6 +12,10 @@ import {
} from "@/lib/agents/psd-template-parser";
import { pickPrimaryPsdImageLayerId } from "@/lib/agents/psd-template-image-slot-selection";
import { schedulePsdTemplateAssetBackfill } from "@/lib/agents/psd-template-asset-backfill";
import {
AGENT_PSD_TEMPLATE_UNSUPPORTED_PSB_MESSAGE,
assertSupportedAgentPsdTemplateFile,
} from "@/lib/agents/psd-template-format";
import {
createAgentPsdTemplateParseHeartbeat,
updateAgentPsdTemplateById,
@ -181,6 +185,11 @@ export async function POST(
}
const { id } = await context.params;
let latestProgress = {
progress: 0,
stage: "准备解析 PSD",
error: undefined as string | undefined,
};
try {
const { templates, timedOutTemplates } = await failTimedOutAgentPsdTemplates({
@ -204,10 +213,10 @@ export async function POST(
return jsonError(409, "该模板正在解析中,请稍后刷新");
}
let latestProgress = {
latestProgress = {
progress: 5,
stage: "准备解析 PSD",
error: undefined as string | undefined,
error: undefined,
};
const setParseProgress = async (progress: number, stage: string, error?: string) => {
latestProgress = {
@ -279,6 +288,9 @@ export async function POST(
});
}
await setParseProgress(22, "正在校验 PSD 文件格式");
assertSupportedAgentPsdTemplateFile(fileBuffer);
await setParseProgress(24, "正在解析 PSD 图层结构与预览");
const parsedTemplate = await parsePsdTemplateFile(fileBuffer, {
onProgress: async (event) => {
@ -353,10 +365,12 @@ export async function POST(
const existingTemplate = (await readAgentPsdTemplates()).find((entry) => entry.id === id);
if (existingTemplate) {
const fallbackStatus =
existingTemplate.parseStatus === "completed" && existingTemplate.layers.length > 0
? "completed"
: "failed";
const hasUsablePreviousParse =
existingTemplate.layers.length > 0 &&
existingTemplate.width > 1 &&
existingTemplate.height > 1 &&
Boolean(existingTemplate.previewUrl);
const fallbackStatus = hasUsablePreviousParse ? "completed" : "failed";
await updateAgentPsdTemplateById({
templateId: id,
@ -364,9 +378,14 @@ export async function POST(
updater: (template) => ({
...template,
parseStatus: fallbackStatus,
parseProgress: fallbackStatus === "completed" ? 100 : 0,
parseProgress:
fallbackStatus === "completed"
? 100
: Math.max(0, Math.round(Number(latestProgress.progress || 0))),
parseStage:
fallbackStatus === "completed" ? "沿用上次成功解析结果" : undefined,
fallbackStatus === "completed"
? "沿用上次成功解析结果"
: latestProgress.stage || "PSD 解析失败",
parseError:
fallbackStatus === "completed"
? `重新解析失败:${message}`
@ -376,7 +395,19 @@ export async function POST(
}).catch(() => undefined);
}
console.error("[PSD Templates][Admin] parse failed:", error);
return jsonError(502, message);
console.error("[PSD Templates][Admin] parse failed:", {
templateId: id,
fileName: existingTemplate?.fileName,
progress: latestProgress.progress,
stage: latestProgress.stage,
error,
});
return jsonError(
message === AGENT_PSD_TEMPLATE_UNSUPPORTED_PSB_MESSAGE ||
message.startsWith("模板文件格式不正确")
? 400
: 502,
message
);
}
}

View File

@ -15,6 +15,10 @@ import {
type AgentPsdTemplateMutationResponse,
} from "@/lib/agents/psd-template-config";
import { normalizeAgentPsdTemplateCategoryTags } from "@/lib/agents/psd-template-category-tag-config";
import {
AGENT_PSD_TEMPLATE_UNSUPPORTED_PSB_MESSAGE,
assertSupportedAgentPsdTemplateFile,
} from "@/lib/agents/psd-template-format";
import { cacheAgentPsdSourceLocally } from "@/lib/agents/psd-local-cache";
import { readAgentPsdTemplates } from "@/lib/agents/psd-template-settings";
import { failTimedOutAgentPsdTemplates } from "@/lib/agents/psd-template-timeout";
@ -22,7 +26,7 @@ import { failTimedOutAgentPsdTemplates } from "@/lib/agents/psd-template-timeout
export const runtime = "nodejs";
const MAX_TEMPLATE_FILE_SIZE = 80 * 1024 * 1024;
const ALLOWED_TEMPLATE_EXTENSIONS = new Set(["psd", "psb"]);
const ALLOWED_TEMPLATE_EXTENSIONS = new Set(["psd"]);
const PLACEHOLDER_PREVIEW_PNG = Buffer.from(
"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO2+p4kAAAAASUVORK5CYII=",
"base64"
@ -222,7 +226,12 @@ export async function POST(request: NextRequest) {
const extension = getFileExtension(file.name);
if (!ALLOWED_TEMPLATE_EXTENSIONS.has(extension)) {
return jsonMutationError(400, "模板文件仅支持 PSD / PSB");
return jsonMutationError(
400,
extension === "psb"
? AGENT_PSD_TEMPLATE_UNSUPPORTED_PSB_MESSAGE
: "模板文件仅支持 Photoshop PSD(.psd)"
);
}
if (file.size <= 0) {
@ -235,11 +244,15 @@ export async function POST(request: NextRequest) {
try {
const fileBuffer = await file.arrayBuffer();
const fileInspection = assertSupportedAgentPsdTemplateFile(fileBuffer);
const templateId = randomUUID();
console.log("[PSD Templates][Admin] received PSD template upload:", {
templateId,
fileName: file.name,
size: file.size,
fileFormat: fileInspection.format,
width: fileInspection.width,
height: fileInspection.height,
});
await cacheAgentPsdSourceLocally({
templateId,
@ -311,6 +324,14 @@ export async function POST(request: NextRequest) {
} satisfies AgentPsdTemplateMutationResponse);
} catch (error) {
console.error("[PSD Templates][Admin] create failed:", error);
if (error instanceof Error && error.message.trim()) {
if (
error.message === AGENT_PSD_TEMPLATE_UNSUPPORTED_PSB_MESSAGE ||
error.message.startsWith("模板文件格式不正确")
) {
return jsonMutationError(400, error.message);
}
}
return jsonMutationError(502, "PSD 模板上传或解析失败");
}
}

View File

@ -0,0 +1,74 @@
import "server-only";
export type AgentPsdTemplateFileFormat = "psd" | "psb" | "unknown";
export interface AgentPsdTemplateFileInspection {
signature: string;
version: number;
format: AgentPsdTemplateFileFormat;
channels: number;
height: number;
width: number;
depth: number;
colorMode: number;
}
export const AGENT_PSD_TEMPLATE_UNSUPPORTED_PSB_MESSAGE =
"当前生产模板解析仅支持 Photoshop PSD(.psd)。检测到 PSB / Large Document 格式,请在 Photoshop 中另存为 PSD 后再上传解析。";
export function inspectAgentPsdTemplateFile(
value: ArrayBuffer | Buffer
): AgentPsdTemplateFileInspection {
const buffer = Buffer.isBuffer(value)
? value
: Buffer.from(value);
if (buffer.byteLength < 26) {
return {
signature: "",
version: 0,
format: "unknown",
channels: 0,
height: 0,
width: 0,
depth: 0,
colorMode: 0,
};
}
const signature = buffer.subarray(0, 4).toString("ascii");
const version = buffer.readUInt16BE(4);
const format =
signature === "8BPS" && version === 1
? "psd"
: signature === "8BPS" && version === 2
? "psb"
: "unknown";
return {
signature,
version,
format,
channels: buffer.readUInt16BE(12),
height: buffer.readUInt32BE(14),
width: buffer.readUInt32BE(18),
depth: buffer.readUInt16BE(22),
colorMode: buffer.readUInt16BE(24),
};
}
export function assertSupportedAgentPsdTemplateFile(
value: ArrayBuffer | Buffer
) {
const inspection = inspectAgentPsdTemplateFile(value);
if (inspection.format === "psd") {
return inspection;
}
if (inspection.format === "psb") {
throw new Error(AGENT_PSD_TEMPLATE_UNSUPPORTED_PSB_MESSAGE);
}
throw new Error("模板文件格式不正确,请上传 Photoshop PSD(.psd)文件。");
}