gongxiangp2/lib/task-detail-upload-policy.ts

106 lines
4.7 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { MAX_LOGICAL_UPLOAD_BYTES } from '@/lib/material-upload-chunk-policy'
export const TASK_DETAIL_UPLOAD_LIMITS = {
/** 单个图片原图的保护上限,避免浏览器解码/服务器兜底处理一次吞掉过多内存。 */
maxSingleImageBytes: 100 * 1024 * 1024,
/** 一次任务允许的图片数量上限;详情长图的切片不计入此门禁。生产存量素材 SKU 图最多 220 张,上限需覆盖真实业务。 */
maxImagesPerGroup: 300,
maxTotalImageCount: 600,
/** PSD/PSB/TIFF 等源文件不是图片计数的一部分,单独限制,防止小文件堆积绕过图片上限。 */
maxPsdFileCount: 20,
/** 原图与源文件的总条目数上限。 */
maxTotalSourceFileCount: 700,
/** 仅图片原图总大小。PSD/压缩包不算进这项,避免把源文件误卡在 1GB。 */
maxTotalSourceBytes: 1024 * 1024 * 1024,
/** 单个 PSD/压缩包与分片上传单文件上限对齐。 */
maxSinglePsdBytes: MAX_LOGICAL_UPLOAD_BYTES,
/** 一次提交的源文件总量:大约够主图/SKU/详情各一份大源文件。 */
maxTotalPsdBytes: 3 * MAX_LOGICAL_UPLOAD_BYTES,
maxPreviewCountPerImage: 60,
maxTotalPreviewCount: 2_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 totalImageBytes = imageFiles.reduce((sum, file) => sum + Math.max(0, file.size), 0)
if (totalImageBytes > TASK_DETAIL_UPLOAD_LIMITS.maxTotalSourceBytes) {
return `本次图片总大小超过 ${formatTaskDetailByteLimit(TASK_DETAIL_UPLOAD_LIMITS.maxTotalSourceBytes)},请分批提交`
}
const oversizedPsd = psdFiles.find((file) => file.size > TASK_DETAIL_UPLOAD_LIMITS.maxSinglePsdBytes)
if (oversizedPsd) {
return `源文件「${oversizedPsd.name}」超过 ${formatTaskDetailByteLimit(TASK_DETAIL_UPLOAD_LIMITS.maxSinglePsdBytes)},请先拆分或压缩`
}
const totalPsdBytes = psdFiles.reduce((sum, file) => sum + Math.max(0, file.size), 0)
if (totalPsdBytes > TASK_DETAIL_UPLOAD_LIMITS.maxTotalPsdBytes) {
return `本次源文件总大小超过 ${formatTaskDetailByteLimit(TASK_DETAIL_UPLOAD_LIMITS.maxTotalPsdBytes)},请分批提交`
}
return null
}
export function formatTaskDetailByteLimit(bytes: number): string {
const gib = 1024 * 1024 * 1024
const mib = 1024 * 1024
if (bytes >= gib) {
const value = bytes / gib
return Number.isInteger(value) ? `${value}GB` : `${value.toFixed(1)}GB`
}
if (bytes >= mib) {
const value = bytes / mib
return Number.isInteger(value) ? `${value}MB` : `${value.toFixed(1)}MB`
}
return `${bytes}B`
}