78 lines
3.4 KiB
TypeScript
78 lines
3.4 KiB
TypeScript
export const TASK_DETAIL_UPLOAD_LIMITS = {
|
||
/** 单个图片原图的保护上限,避免浏览器解码/服务器兜底处理一次吞掉过多内存。 */
|
||
maxSingleImageBytes: 100 * 1024 * 1024,
|
||
/** 一次任务允许的图片数量上限;详情长图的切片不计入此门禁。 */
|
||
maxImagesPerGroup: 60,
|
||
maxTotalImageCount: 120,
|
||
/** PSD/PSB/TIFF 等源文件不是图片计数的一部分,单独限制,防止小文件堆积绕过图片上限。 */
|
||
maxPsdFileCount: 20,
|
||
/** 原图与源文件的总条目数上限。 */
|
||
maxTotalSourceFileCount: 140,
|
||
/** 原图总大小保护上限,给大批量上传留出空间,同时避免误操作打爆客户端和服务器。 */
|
||
maxTotalSourceBytes: 1024 * 1024 * 1024,
|
||
maxPreviewCountPerImage: 60,
|
||
maxTotalPreviewCount: 1_000,
|
||
maxSinglePreviewBytes: 10 * 1024 * 1024,
|
||
maxTotalPreviewBytes: 512 * 1024 * 1024,
|
||
/** 普通预览为 800x1200,详情切片最大 1600x1500。 */
|
||
maxPreviewWidth: 800,
|
||
maxPreviewHeight: 1_200,
|
||
maxPreviewPixels: 800 * 1_200,
|
||
maxSliceWidth: 1_600,
|
||
maxSliceHeight: 1_500,
|
||
maxSlicePixels: 1_600 * 1_500,
|
||
maxPreviewAnimationFrames: 1,
|
||
/** 单个用户最多排队 3 个,防止一个账号长期占满唯一图片 worker。 */
|
||
maxQueuedJobsPerUser: 3,
|
||
/** 全站最多等待 20 个任务;processing 任务不计入等待容量。 */
|
||
maxQueuedJobsGlobal: 20,
|
||
/** 合法最大提交:140 个源文件 + 1000 个客户端预览。 */
|
||
maxTotalUploadSessionCount: 1_140,
|
||
} as const
|
||
|
||
export type TaskDetailUploadFileGroups = {
|
||
main: File[]
|
||
sku: File[]
|
||
detail: File[]
|
||
psd?: File[]
|
||
}
|
||
|
||
export function validateTaskDetailUploadFiles(groups: TaskDetailUploadFileGroups): string | null {
|
||
const imageGroups: Array<[string, File[]]> = [
|
||
['主图', groups.main],
|
||
['SKU 图', groups.sku],
|
||
['详情图', groups.detail],
|
||
]
|
||
|
||
for (const [label, files] of imageGroups) {
|
||
if (files.length > TASK_DETAIL_UPLOAD_LIMITS.maxImagesPerGroup) {
|
||
return `${label}最多上传 ${TASK_DETAIL_UPLOAD_LIMITS.maxImagesPerGroup} 张,请分批提交`
|
||
}
|
||
const oversized = files.find((file) => file.size > TASK_DETAIL_UPLOAD_LIMITS.maxSingleImageBytes)
|
||
if (oversized) {
|
||
return `${label}「${oversized.name}」超过单图 ${Math.round(TASK_DETAIL_UPLOAD_LIMITS.maxSingleImageBytes / 1024 / 1024)}MB 限制,请先压缩或拆分`
|
||
}
|
||
}
|
||
|
||
const imageFiles = imageGroups.flatMap(([, files]) => files)
|
||
if (imageFiles.length > TASK_DETAIL_UPLOAD_LIMITS.maxTotalImageCount) {
|
||
return `本次图片最多上传 ${TASK_DETAIL_UPLOAD_LIMITS.maxTotalImageCount} 张,请分批提交`
|
||
}
|
||
|
||
const psdFiles = groups.psd || []
|
||
if (psdFiles.length > TASK_DETAIL_UPLOAD_LIMITS.maxPsdFileCount) {
|
||
return `PSD 源文件最多上传 ${TASK_DETAIL_UPLOAD_LIMITS.maxPsdFileCount} 个,请分批提交`
|
||
}
|
||
|
||
const allFiles = [...imageFiles, ...psdFiles]
|
||
if (allFiles.length > TASK_DETAIL_UPLOAD_LIMITS.maxTotalSourceFileCount) {
|
||
return `本次原图和源文件最多上传 ${TASK_DETAIL_UPLOAD_LIMITS.maxTotalSourceFileCount} 个,请分批提交`
|
||
}
|
||
const totalBytes = allFiles.reduce((sum, file) => sum + Math.max(0, file.size), 0)
|
||
if (totalBytes > TASK_DETAIL_UPLOAD_LIMITS.maxTotalSourceBytes) {
|
||
return `本次素材总大小超过 ${Math.round(TASK_DETAIL_UPLOAD_LIMITS.maxTotalSourceBytes / 1024 / 1024 / 1024)}GB,请分批提交`
|
||
}
|
||
|
||
return null
|
||
}
|