233 lines
7.0 KiB
TypeScript
233 lines
7.0 KiB
TypeScript
/**
|
|
* 统一的素材文件清理逻辑
|
|
* 根据不同的素材类型,收集所有需要删除的文件和关联记录。
|
|
*/
|
|
|
|
import path from 'path'
|
|
import {
|
|
buildPublicDisplayUrl,
|
|
buildWhiteBackgroundPublicDisplayPathFromAssetPath,
|
|
} from './public-display'
|
|
import { prisma } from './prisma'
|
|
import {
|
|
LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE,
|
|
LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX,
|
|
STANDARD_MODEL_WHITE_BG_OTHER_ASSET_TYPE_PREFIX,
|
|
STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX,
|
|
STANDARD_MODEL_WHITE_BG_VIEW_ASSET_TYPE_PREFIX,
|
|
} from './standard-model-white-background'
|
|
|
|
export interface MaterialFileInfo {
|
|
fileUrls: string[]
|
|
directories: string[]
|
|
relatedRecords?: {
|
|
assets?: number
|
|
skus?: number
|
|
}
|
|
}
|
|
|
|
export function normalizeDisplayImages(displayImages: unknown): string[] {
|
|
if (!displayImages) {
|
|
return []
|
|
}
|
|
|
|
if (Array.isArray(displayImages)) {
|
|
return displayImages.filter(
|
|
(img): img is string => typeof img === 'string' && img.trim().length > 0,
|
|
)
|
|
}
|
|
|
|
if (typeof displayImages === 'string') {
|
|
try {
|
|
const parsed = JSON.parse(displayImages)
|
|
if (Array.isArray(parsed)) {
|
|
return parsed.filter(
|
|
(img): img is string => typeof img === 'string' && img.trim().length > 0,
|
|
)
|
|
}
|
|
} catch {
|
|
// 旧数据可能直接存成单个 URL 字符串,这里降级兼容。
|
|
}
|
|
|
|
return displayImages.trim() ? [displayImages] : []
|
|
}
|
|
|
|
return []
|
|
}
|
|
|
|
/**
|
|
* 收集素材的所有文件路径
|
|
* @param materialId 素材 ID
|
|
* @param material 素材对象,可选;不传时会自动查询
|
|
*/
|
|
export async function collectMaterialFiles(
|
|
materialId: string,
|
|
material?: any,
|
|
): Promise<MaterialFileInfo> {
|
|
const fileUrls: string[] = []
|
|
const directories: string[] = []
|
|
const relatedRecords: { assets?: number; skus?: number } = {}
|
|
|
|
if (!material) {
|
|
material = await prisma.material.findUnique({
|
|
where: { id: materialId },
|
|
})
|
|
}
|
|
|
|
if (!material) {
|
|
return { fileUrls: [], directories: [], relatedRecords }
|
|
}
|
|
|
|
const materialType = String((material as any).materialType || '')
|
|
|
|
if ((material as any).zipFilePath) {
|
|
fileUrls.push((material as any).zipFilePath)
|
|
}
|
|
|
|
if (
|
|
(material as any).videoSourceUrl &&
|
|
(material as any).videoSourceUrl !== (material as any).zipFilePath
|
|
) {
|
|
fileUrls.push((material as any).videoSourceUrl)
|
|
}
|
|
|
|
if ((material as any).previewUrl) {
|
|
fileUrls.push((material as any).previewUrl)
|
|
}
|
|
|
|
if ((material as any).videoPreview540Url) {
|
|
fileUrls.push((material as any).videoPreview540Url)
|
|
}
|
|
|
|
if ((material as any).videoPreviewClipUrl) {
|
|
fileUrls.push((material as any).videoPreviewClipUrl)
|
|
}
|
|
|
|
if ((material as any).videoCoverUrl) {
|
|
fileUrls.push((material as any).videoCoverUrl)
|
|
}
|
|
|
|
fileUrls.push(...normalizeDisplayImages((material as any).displayImages))
|
|
|
|
// 任务类素材都可能存在 MaterialAsset 记录。
|
|
const taskMaterialTypes = ['任务-详情', '任务-页面', '任务-其他', '任务-3D原图', '任务-AI原图', '任务-实拍原图', '任务-视频截图', '证书-检测报告'];
|
|
if (taskMaterialTypes.includes(materialType)) {
|
|
const assets = await prisma.materialAsset.findMany({
|
|
where: { materialId },
|
|
select: { fileUrl: true },
|
|
})
|
|
relatedRecords.assets = assets.length
|
|
|
|
for (const asset of assets) {
|
|
if (asset.fileUrl) {
|
|
fileUrls.push(asset.fileUrl)
|
|
}
|
|
}
|
|
|
|
if (materialType === '任务-详情' || materialType === '任务-页面') {
|
|
directories.push(path.join(process.cwd(), 'uploads', 'product-package', materialId))
|
|
}
|
|
}
|
|
|
|
if (materialType === '标准模型') {
|
|
const skus = await prisma.materialSku.findMany({
|
|
where: {
|
|
materialId,
|
|
},
|
|
select: {
|
|
filePath: true,
|
|
previewUrl: true,
|
|
},
|
|
})
|
|
relatedRecords.skus = skus.length
|
|
|
|
for (const sku of skus) {
|
|
if (sku.filePath) {
|
|
fileUrls.push(sku.filePath)
|
|
}
|
|
if (sku.previewUrl) {
|
|
fileUrls.push(sku.previewUrl)
|
|
}
|
|
}
|
|
|
|
// 兼容旧版整素材白底图 ZIP 键,以及新版 SKU 级白底图 ZIP 键。
|
|
const whiteBgAssets = await prisma.materialAsset.findMany({
|
|
where: {
|
|
materialId,
|
|
OR: [
|
|
{ assetType: LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE },
|
|
{ assetType: { startsWith: LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX } },
|
|
{ assetType: { startsWith: STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX } },
|
|
{ assetType: { startsWith: STANDARD_MODEL_WHITE_BG_VIEW_ASSET_TYPE_PREFIX } },
|
|
{ assetType: { startsWith: STANDARD_MODEL_WHITE_BG_OTHER_ASSET_TYPE_PREFIX } },
|
|
],
|
|
},
|
|
select: { fileUrl: true },
|
|
})
|
|
relatedRecords.assets = whiteBgAssets.length;
|
|
|
|
for (const asset of whiteBgAssets) {
|
|
if (asset.fileUrl) {
|
|
fileUrls.push(asset.fileUrl)
|
|
const publicDisplayPath = buildWhiteBackgroundPublicDisplayPathFromAssetPath(asset.fileUrl)
|
|
const publicDisplayUrl = publicDisplayPath ? buildPublicDisplayUrl(publicDisplayPath) : null
|
|
if (publicDisplayUrl) {
|
|
fileUrls.push(publicDisplayUrl)
|
|
}
|
|
}
|
|
}
|
|
|
|
directories.push(path.join(process.cwd(), 'uploads', 'skus', materialId, 'white-background'))
|
|
}
|
|
|
|
return {
|
|
fileUrls: Array.from(new Set(fileUrls.filter((fileUrl) => fileUrl && fileUrl.trim()))),
|
|
directories: Array.from(new Set(directories.filter((dir) => dir && dir.trim()))),
|
|
relatedRecords,
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除素材关联记录
|
|
* @param materialId 素材 ID
|
|
* @param materialType 素材类型
|
|
*/
|
|
export async function deleteMaterialRelatedRecords(
|
|
materialId: string,
|
|
materialType: string,
|
|
): Promise<{ deletedAssets?: number; deletedSkus?: number }> {
|
|
const result: { deletedAssets?: number; deletedSkus?: number } = {}
|
|
|
|
const taskMaterialTypes = ['任务-详情', '任务-页面', '任务-其他', '任务-3D原图', '任务-AI原图', '任务-实拍原图', '任务-视频截图', '证书-检测报告'];
|
|
if (taskMaterialTypes.includes(materialType)) {
|
|
const deleteResult = await prisma.materialAsset.deleteMany({
|
|
where: { materialId },
|
|
})
|
|
result.deletedAssets = deleteResult.count
|
|
}
|
|
|
|
if (materialType === '标准模型') {
|
|
const deleteSkuResult = await prisma.materialSku.deleteMany({
|
|
where: { materialId },
|
|
})
|
|
result.deletedSkus = deleteSkuResult.count
|
|
|
|
const deleteWhiteBgResult = await prisma.materialAsset.deleteMany({
|
|
where: {
|
|
materialId,
|
|
OR: [
|
|
{ assetType: LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE },
|
|
{ assetType: { startsWith: LEGACY_STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX } },
|
|
{ assetType: { startsWith: STANDARD_MODEL_WHITE_BG_ASSET_TYPE_PREFIX } },
|
|
{ assetType: { startsWith: STANDARD_MODEL_WHITE_BG_VIEW_ASSET_TYPE_PREFIX } },
|
|
{ assetType: { startsWith: STANDARD_MODEL_WHITE_BG_OTHER_ASSET_TYPE_PREFIX } },
|
|
],
|
|
},
|
|
})
|
|
|
|
result.deletedAssets = (result.deletedAssets || 0) + deleteWhiteBgResult.count
|
|
}
|
|
|
|
return result
|
|
}
|