WuYongJie d1c6efc79d
Some checks failed
CI Check / check (push) Has been cancelled
feat: NAS 部署与 Harbor 推送流程
2025-12-03 14:39:06 +08:00

127 lines
3.8 KiB
TypeScript
Raw 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 { NextRequest, NextResponse } from 'next/server'
import { getServerSession } from 'next-auth'
import { authOptions } from '@/lib/auth'
import { prisma } from '@/lib/prisma'
import { deleteFromS3, isS3Url } from '@/lib/s3'
import { unlink } from 'fs/promises'
// Cleanup materials deleted more than 7 days ago (admin only)
export async function POST(req: NextRequest) {
try {
const session = await getServerSession(authOptions)
if (!session || (session.user as any).role !== 'ADMIN') {
return NextResponse.json(
{ error: '无权限' },
{ status: 403 }
)
}
// 计算7天前的日期
const sevenDaysAgo = new Date()
sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7)
// 查找7天前删除的素材
const deletedMaterials = await prisma.material.findMany({
where: {
deletedAt: {
not: null,
lte: sevenDaysAgo,
},
},
})
// 收集所有需要删除的文件并删除
let deletedFiles = 0
let failedFiles = 0
for (const material of deletedMaterials) {
const filesToDelete: string[] = []
// 源文件(zipFilePath 或 videoSourceUrl)
if (material.zipFilePath) filesToDelete.push(material.zipFilePath)
if ((material as any).videoSourceUrl && (material as any).videoSourceUrl !== material.zipFilePath) {
filesToDelete.push((material as any).videoSourceUrl)
}
// 预览图
if (material.previewUrl) filesToDelete.push(material.previewUrl)
// 多预览图(JSON数组)
if ((material as any).previewUrls) {
try {
const previewUrls = JSON.parse((material as any).previewUrls)
if (Array.isArray(previewUrls)) {
filesToDelete.push(...previewUrls.filter((url: string) => url && url.trim()))
}
} catch {
// 忽略解析错误
}
}
// 视频相关文件
if ((material as any).videoPreview540Url) filesToDelete.push((material as any).videoPreview540Url)
if ((material as any).videoPreviewClipUrl) filesToDelete.push((material as any).videoPreviewClipUrl)
if ((material as any).videoCoverUrl) filesToDelete.push((material as any).videoCoverUrl)
// 展示图片(JSON数组)
if (material.displayImages) {
try {
const displayImages = JSON.parse(material.displayImages)
if (Array.isArray(displayImages)) {
filesToDelete.push(...displayImages.filter((url: string) => url && url.trim()))
}
} catch {
// 忽略解析错误
}
}
// 去重并过滤空值
const uniqueFiles = Array.from(new Set(filesToDelete.filter((url) => url && url.trim())))
// 删除所有文件
for (const fileUrl of uniqueFiles) {
try {
if (isS3Url(fileUrl)) {
await deleteFromS3(fileUrl)
deletedFiles++
} else {
try {
await unlink(fileUrl)
deletedFiles++
} catch (fileError: any) {
if (fileError.code !== 'ENOENT') {
failedFiles++
}
}
}
} catch (error) {
console.error(`[Cleanup] Failed to delete file: ${fileUrl}`, error)
failedFiles++
}
}
}
// 永久删除数据库记录
const deletePromises = deletedMaterials.map((material) =>
prisma.material.delete({
where: { id: material.id },
})
)
await Promise.all(deletePromises)
return NextResponse.json({
message: '清理完成',
deletedCount: deletedMaterials.length
})
} catch (error) {
console.error('[Materials] CLEANUP error:', error)
return NextResponse.json(
{ error: '清理失败' },
{ status: 500 }
)
}
}