gongxiangp2/scripts/check-missing-video-files.ts

165 lines
5.3 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.

/**
* 检查缺失的本地视频文件
* 分析这些文件的 videoSourceUrl,判断是否需要从 MinIO 重新下载
*/
import { PrismaClient } from '@prisma/client'
import { access } from 'fs/promises'
import { constants as fsConstants } from 'fs'
import path from 'path'
const prisma = new PrismaClient()
const S3_PUBLIC_DOMAIN = process.env.S3_PUBLIC_DOMAIN || ''
const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads'
const VIDEO_TYPES = ['主图视频', '安装视频', '种草视频']
async function fileExists(fullPath: string): Promise<boolean> {
try {
await access(fullPath, fsConstants.F_OK)
return true
} catch {
return false
}
}
async function main() {
console.log('==== 检查缺失的本地视频文件 ====\n')
// 查询所有视频类素材
const allVideos = await prisma.material.findMany({
where: {
deletedAt: null,
materialType: { in: VIDEO_TYPES } as any,
},
select: {
id: true,
materialType: true,
videoSourceUrl: true,
createdAt: true,
zipFileName: true,
zipFilePath: true,
},
orderBy: {
createdAt: 'desc',
},
})
// 找出所有本地路径但文件缺失的记录
const missingFiles: Array<{
id: string
materialType: string
videoSourceUrl: string
createdAt: Date
zipFileName: string | null
zipFilePath: string | null
}> = []
for (const m of allVideos) {
if (!m.videoSourceUrl) continue
const url = m.videoSourceUrl as string
if (!url.startsWith('/uploads/video-')) continue
const fileName = url.replace('/uploads/', '')
const fullPath = path.join(UPLOAD_DIR, fileName)
const exists = await fileExists(fullPath)
if (!exists) {
missingFiles.push({
id: m.id,
materialType: m.materialType,
videoSourceUrl: url,
createdAt: m.createdAt,
zipFileName: m.zipFileName,
zipFilePath: m.zipFilePath,
})
}
}
console.log(`共找到 ${missingFiles.length} 条本地路径但文件缺失的记录\n`)
if (missingFiles.length === 0) {
console.log('✅ 没有缺失的文件')
await prisma.$disconnect()
return
}
// 分析这些缺失文件
console.log('📊 分析缺失文件:\n')
// 按类型分组
const byType: Record<string, number> = {}
for (const m of missingFiles) {
byType[m.materialType] = (byType[m.materialType] || 0) + 1
}
console.log('按类型分布:')
for (const [type, count] of Object.entries(byType)) {
console.log(` ${type}: ${count} 条`)
}
console.log('')
// 检查是否有 zipFileName/zipFilePath 信息
const withZipInfo = missingFiles.filter((m) => m.zipFileName || m.zipFilePath)
const withoutZipInfo = missingFiles.filter((m) => !m.zipFileName && !m.zipFilePath)
console.log(`有 zipFileName/zipFilePath 信息: ${withZipInfo.length} 条`)
console.log(`无 zipFileName/zipFilePath 信息: ${withoutZipInfo.length} 条\n`)
// 检查这些文件的 videoSourceUrl 是否可能是从 S3 迁移过来的
// 如果文件名格式是 video-timestamp-random.mp4,可能是老的上传逻辑
// 如果文件名格式是 video-materialId-timestamp.mp4,可能是新的迁移逻辑
const oldFormat = missingFiles.filter((m) => {
const fileName = m.videoSourceUrl.replace('/uploads/video-', '')
// 老格式:video-timestamp-random.mp4(没有 materialId)
// 新格式:video-materialId-timestamp.mp4(有 materialId)
return !fileName.startsWith(m.id)
})
const newFormat = missingFiles.filter((m) => {
const fileName = m.videoSourceUrl.replace('/uploads/video-', '')
return fileName.startsWith(m.id)
})
console.log('按文件名格式分析:')
console.log(` 老格式 (video-timestamp-random.mp4): ${oldFormat.length} 条`)
console.log(` 新格式 (video-materialId-timestamp.mp4): ${newFormat.length} 条\n`)
// 显示前 20 条缺失文件的详细信息
console.log('缺失文件详情(前 20 条):\n')
missingFiles.slice(0, 20).forEach((m, index) => {
console.log(`${index + 1}. [${m.id}] ${m.materialType}`)
console.log(` videoSourceUrl: ${m.videoSourceUrl}`)
console.log(` 创建时间: ${m.createdAt.toISOString()}`)
console.log(` zipFileName: ${m.zipFileName || '(空)'}`)
console.log(` zipFilePath: ${m.zipFilePath || '(空)'}`)
console.log('')
})
if (missingFiles.length > 20) {
console.log(`... 还有 ${missingFiles.length - 20} 条未显示\n`)
}
// 检查这些文件是否可能在 MinIO 上
console.log('💡 建议:')
console.log(' 1. 这些文件的 videoSourceUrl 已经是本地路径格式,但本地文件不存在')
console.log(' 2. 可能的原因:')
console.log(' - 文件被手动删除')
console.log(' - 文件从未成功上传到本地(上传失败但数据库已更新)')
console.log(' - 文件路径变更或服务器迁移')
console.log(' 3. 如果这些文件还在 MinIO 上,需要:')
console.log(' - 根据 zipFileName/zipFilePath 或 videoSourceUrl 的文件名,')
console.log(' 在 MinIO 中查找对应的源文件')
console.log(' - 从 MinIO 下载到本地,并确保 videoSourceUrl 指向正确的本地路径')
await prisma.$disconnect()
}
main().catch((error) => {
console.error('脚本执行出错:', error)
process.exit(1)
})