fix: stabilize agent psd template uploads
This commit is contained in:
parent
1f766e745c
commit
f87669c15a
@ -96,6 +96,7 @@ const SYSTEM_FONT_OPTIONS = [
|
|||||||
const EMPTY_CATEGORY_SELECT_VALUE = "__none__";
|
const EMPTY_CATEGORY_SELECT_VALUE = "__none__";
|
||||||
const EMPTY_BRAND_SELECT_VALUE = "__unassigned__";
|
const EMPTY_BRAND_SELECT_VALUE = "__unassigned__";
|
||||||
const UNASSIGNED_BRAND_LABEL = "未分类";
|
const UNASSIGNED_BRAND_LABEL = "未分类";
|
||||||
|
const MAX_PSD_TEMPLATE_FILE_SIZE = 80 * 1024 * 1024;
|
||||||
const PSD_TEMPLATE_MODE_OPTIONS: Array<{
|
const PSD_TEMPLATE_MODE_OPTIONS: Array<{
|
||||||
value: AgentPsdTemplateMode;
|
value: AgentPsdTemplateMode;
|
||||||
label: string;
|
label: string;
|
||||||
@ -117,6 +118,13 @@ function getPsdTemplateModeLabel(mode?: AgentPsdTemplateMode | null) {
|
|||||||
return mode === "sku" ? "SKU图模式" : "主图模式";
|
return mode === "sku" ? "SKU图模式" : "主图模式";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function formatUploadFileSize(size: number) {
|
||||||
|
if (!Number.isFinite(size) || size <= 0) return "0MB";
|
||||||
|
const mb = size / (1024 * 1024);
|
||||||
|
if (mb >= 1) return `${mb.toFixed(mb >= 10 ? 0 : 1)}MB`;
|
||||||
|
return `${Math.max(1, Math.round(size / 1024))}KB`;
|
||||||
|
}
|
||||||
|
|
||||||
function normalizeManagedBrandName(value?: string | null) {
|
function normalizeManagedBrandName(value?: string | null) {
|
||||||
const normalized = String(value || "").trim();
|
const normalized = String(value || "").trim();
|
||||||
if (!normalized || normalized === "未分配品牌") {
|
if (!normalized || normalized === "未分配品牌") {
|
||||||
@ -577,6 +585,15 @@ export function PsdTemplatesSettingsClient({
|
|||||||
|
|
||||||
function handleTemplateFileChange(event: ChangeEvent<HTMLInputElement>) {
|
function handleTemplateFileChange(event: ChangeEvent<HTMLInputElement>) {
|
||||||
const nextFile = event.target.files?.[0] || null;
|
const nextFile = event.target.files?.[0] || null;
|
||||||
|
if (nextFile && nextFile.size > MAX_PSD_TEMPLATE_FILE_SIZE) {
|
||||||
|
toast.error(
|
||||||
|
`PSD 模板不能超过 80MB,当前文件约 ${formatUploadFileSize(nextFile.size)}`
|
||||||
|
);
|
||||||
|
event.currentTarget.value = "";
|
||||||
|
setUploadFile(null);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
setUploadFile(nextFile);
|
setUploadFile(nextFile);
|
||||||
|
|
||||||
if (nextFile && !uploadName.trim()) {
|
if (nextFile && !uploadName.trim()) {
|
||||||
@ -702,6 +719,13 @@ export function PsdTemplatesSettingsClient({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (uploadFile.size > MAX_PSD_TEMPLATE_FILE_SIZE) {
|
||||||
|
toast.error(
|
||||||
|
`PSD 模板不能超过 80MB,当前文件约 ${formatUploadFileSize(uploadFile.size)}`
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (!uploadBrandName.trim()) {
|
if (!uploadBrandName.trim()) {
|
||||||
toast.error("请先选择模板品牌");
|
toast.error("请先选择模板品牌");
|
||||||
return;
|
return;
|
||||||
@ -757,8 +781,10 @@ export function PsdTemplatesSettingsClient({
|
|||||||
reject(new Error(responsePayload.error || "PSD 模板上传失败"));
|
reject(new Error(responsePayload.error || "PSD 模板上传失败"));
|
||||||
};
|
};
|
||||||
|
|
||||||
xhr.onerror = () => reject(new Error("网络错误,请稍后重试"));
|
xhr.onerror = () =>
|
||||||
xhr.ontimeout = () => reject(new Error("上传超时,请稍后重试"));
|
reject(new Error("上传连接被中断,请检查网络或压缩 PSD 后重试"));
|
||||||
|
xhr.onabort = () => reject(new Error("上传已取消,请重新选择 PSD 后再试"));
|
||||||
|
xhr.ontimeout = () => reject(new Error("上传超时,请检查网络或压缩 PSD 后重试"));
|
||||||
|
|
||||||
xhr.open("POST", "/api/admin/psd-templates");
|
xhr.open("POST", "/api/admin/psd-templates");
|
||||||
xhr.timeout = 10 * 60 * 1000;
|
xhr.timeout = 10 * 60 * 1000;
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { randomUUID } from "node:crypto";
|
|||||||
import { NextRequest, NextResponse } from "next/server";
|
import { NextRequest, NextResponse } from "next/server";
|
||||||
import { upsertSystemSetting } from "@/lib/db/queries";
|
import { upsertSystemSetting } from "@/lib/db/queries";
|
||||||
import { requireAgentWorkflowAdmin } from "@/lib/middleware/admin";
|
import { requireAgentWorkflowAdmin } from "@/lib/middleware/admin";
|
||||||
import { uploadFile } from "@/lib/storage/service";
|
import { createStorageUploadSession } from "@/lib/storage/service";
|
||||||
import {
|
import {
|
||||||
AGENT_PSD_TEMPLATE_ASSET_VERSION,
|
AGENT_PSD_TEMPLATE_ASSET_VERSION,
|
||||||
AGENT_PSD_TEMPLATES_KEY,
|
AGENT_PSD_TEMPLATES_KEY,
|
||||||
@ -60,6 +60,89 @@ function toArrayBuffer(buffer: Buffer) {
|
|||||||
) as ArrayBuffer;
|
) as ArrayBuffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function updateTemplateById(params: {
|
||||||
|
templateId: string;
|
||||||
|
updatedBy: string;
|
||||||
|
updater: (template: AgentPsdTemplate) => AgentPsdTemplate;
|
||||||
|
}) {
|
||||||
|
const templates = await readAgentPsdTemplates();
|
||||||
|
const currentTemplate = templates.find((entry) => entry.id === params.templateId);
|
||||||
|
|
||||||
|
if (!currentTemplate) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const nextTemplate = params.updater(currentTemplate);
|
||||||
|
await upsertSystemSetting({
|
||||||
|
key: AGENT_PSD_TEMPLATES_KEY,
|
||||||
|
value: templates.map((entry) =>
|
||||||
|
entry.id === params.templateId ? nextTemplate : entry
|
||||||
|
),
|
||||||
|
updatedBy: params.updatedBy,
|
||||||
|
});
|
||||||
|
|
||||||
|
return nextTemplate;
|
||||||
|
}
|
||||||
|
|
||||||
|
function schedulePsdSourceRemoteBackup(params: {
|
||||||
|
templateId: string;
|
||||||
|
updatedBy: string;
|
||||||
|
fileName: string;
|
||||||
|
fileBuffer: ArrayBuffer;
|
||||||
|
uploadAsset: Awaited<ReturnType<typeof createStorageUploadSession>>;
|
||||||
|
}) {
|
||||||
|
queueMicrotask(() => {
|
||||||
|
void (async () => {
|
||||||
|
try {
|
||||||
|
console.log("[PSD Templates][Admin] backing up PSD source in background:", {
|
||||||
|
templateId: params.templateId,
|
||||||
|
fileName: params.fileName,
|
||||||
|
size: params.fileBuffer.byteLength,
|
||||||
|
});
|
||||||
|
const uploadedFile = await params.uploadAsset(params.fileName, params.fileBuffer);
|
||||||
|
await updateTemplateById({
|
||||||
|
templateId: params.templateId,
|
||||||
|
updatedBy: params.updatedBy,
|
||||||
|
updater: (template) => ({
|
||||||
|
...template,
|
||||||
|
fileUrl: uploadedFile.url,
|
||||||
|
filePath: uploadedFile.pathname,
|
||||||
|
parseStage:
|
||||||
|
template.parseStatus === "pending"
|
||||||
|
? "PSD 已上传,等待手动解析"
|
||||||
|
: template.parseStage,
|
||||||
|
parseError: undefined,
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
console.log("[PSD Templates][Admin] PSD source background backup finished:", {
|
||||||
|
templateId: params.templateId,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error("[PSD Templates][Admin] PSD source background backup failed:", {
|
||||||
|
templateId: params.templateId,
|
||||||
|
error,
|
||||||
|
});
|
||||||
|
await updateTemplateById({
|
||||||
|
templateId: params.templateId,
|
||||||
|
updatedBy: params.updatedBy,
|
||||||
|
updater: (template) => ({
|
||||||
|
...template,
|
||||||
|
parseStage:
|
||||||
|
template.parseStatus === "pending"
|
||||||
|
? "本地 PSD 已保存,远端源文件备份失败,可先尝试解析或重新上传"
|
||||||
|
: template.parseStage,
|
||||||
|
parseError:
|
||||||
|
template.parseError ||
|
||||||
|
"远端 PSD 源文件备份失败,本地缓存仍可用于当前解析",
|
||||||
|
updatedAt: new Date().toISOString(),
|
||||||
|
}),
|
||||||
|
}).catch(() => undefined);
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export async function GET() {
|
export async function GET() {
|
||||||
const session = await requireAgentWorkflowAdmin().catch(() => null);
|
const session = await requireAgentWorkflowAdmin().catch(() => null);
|
||||||
if (!session?.user?.id) {
|
if (!session?.user?.id) {
|
||||||
@ -153,6 +236,11 @@ export async function POST(request: NextRequest) {
|
|||||||
try {
|
try {
|
||||||
const fileBuffer = await file.arrayBuffer();
|
const fileBuffer = await file.arrayBuffer();
|
||||||
const templateId = randomUUID();
|
const templateId = randomUUID();
|
||||||
|
console.log("[PSD Templates][Admin] received PSD template upload:", {
|
||||||
|
templateId,
|
||||||
|
fileName: file.name,
|
||||||
|
size: file.size,
|
||||||
|
});
|
||||||
await cacheAgentPsdSourceLocally({
|
await cacheAgentPsdSourceLocally({
|
||||||
templateId,
|
templateId,
|
||||||
fileName: file.name,
|
fileName: file.name,
|
||||||
@ -163,13 +251,11 @@ export async function POST(request: NextRequest) {
|
|||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
const [uploadedFile, uploadedPreview] = await Promise.all([
|
const uploadAsset = await createStorageUploadSession();
|
||||||
uploadFile(file.name, fileBuffer),
|
const uploadedPreview = await uploadAsset(
|
||||||
uploadFile(
|
`agent-psd-preview-placeholder-${Date.now()}.png`,
|
||||||
`agent-psd-preview-placeholder-${Date.now()}.png`,
|
toArrayBuffer(PLACEHOLDER_PREVIEW_PNG)
|
||||||
toArrayBuffer(PLACEHOLDER_PREVIEW_PNG)
|
);
|
||||||
),
|
|
||||||
]);
|
|
||||||
|
|
||||||
const now = new Date().toISOString();
|
const now = new Date().toISOString();
|
||||||
const nextTemplate: AgentPsdTemplate = {
|
const nextTemplate: AgentPsdTemplate = {
|
||||||
@ -183,11 +269,11 @@ export async function POST(request: NextRequest) {
|
|||||||
status,
|
status,
|
||||||
parseStatus: "pending",
|
parseStatus: "pending",
|
||||||
parseProgress: 0,
|
parseProgress: 0,
|
||||||
parseStage: "等待手动解析",
|
parseStage: "PSD 已接收,远端源文件正在后台备份",
|
||||||
parseError: undefined,
|
parseError: undefined,
|
||||||
fileName: file.name,
|
fileName: file.name,
|
||||||
fileUrl: uploadedFile.url,
|
fileUrl: uploadedPreview.url,
|
||||||
filePath: uploadedFile.pathname,
|
filePath: uploadedPreview.pathname,
|
||||||
previewUrl: uploadedPreview.url,
|
previewUrl: uploadedPreview.url,
|
||||||
previewPath: uploadedPreview.pathname,
|
previewPath: uploadedPreview.pathname,
|
||||||
renderBaseUrl: undefined,
|
renderBaseUrl: undefined,
|
||||||
@ -211,6 +297,14 @@ export async function POST(request: NextRequest) {
|
|||||||
updatedBy: session.user.id,
|
updatedBy: session.user.id,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
schedulePsdSourceRemoteBackup({
|
||||||
|
templateId,
|
||||||
|
updatedBy: session.user.id,
|
||||||
|
fileName: file.name,
|
||||||
|
fileBuffer,
|
||||||
|
uploadAsset,
|
||||||
|
});
|
||||||
|
|
||||||
return NextResponse.json({
|
return NextResponse.json({
|
||||||
success: true,
|
success: true,
|
||||||
template: nextTemplate,
|
template: nextTemplate,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user