const path = require('path') const fs = require('fs/promises') const { PrismaClient } = require('@prisma/client') const prisma = new PrismaClient() const VIDEO_TYPES = ['主图视频', '安装视频', '种草视频'] function detectExtFromHeader(buffer) { if (buffer.slice(4, 8).toString('ascii').startsWith('ftyp')) { return '.mp4' } if (buffer.slice(0, 4).toString('ascii') === 'RIFF') { return '.avi' } if (buffer.slice(0, 4).toString('ascii') === 'fLaC') { return '.flac' } if (buffer.slice(0, 3).toString('ascii') === 'ID3') { return '.mp3' } return null } function resolvePath(filePath) { if (!filePath) return '' return path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath) } function buildStoredPath(originalStoredPath, newName) { if (!originalStoredPath) { return `uploads/${newName}` } const dir = path.dirname(originalStoredPath) return path.join(dir, newName).replace(/\\/g, '/').replace(/^\.\//, '') } function buildVideoSourceUrl(videoSourceUrl, newName) { if (!videoSourceUrl) { return `/uploads/${newName}` } if (videoSourceUrl.startsWith('/uploads/')) { const segments = videoSourceUrl.split('/') segments[segments.length - 1] = newName return segments.join('/') } if (videoSourceUrl.startsWith('uploads/')) { return `uploads/${newName}` } return videoSourceUrl } async function main() { const records = await prisma.material.findMany({ where: { materialType: { in: VIDEO_TYPES }, zipFileName: { endsWith: '.zip' }, deletedAt: null, }, select: { id: true, zipFileName: true, zipFilePath: true, videoSourceUrl: true, }, }) if (records.length === 0) { console.log('没有需要修复的视频文件') return } let fixedCount = 0 let skippedMissing = 0 for (const record of records) { try { const absolutePath = resolvePath(record.zipFilePath) const fileHandle = await fs.open(absolutePath, 'r') const buffer = Buffer.alloc(16) await fileHandle.read(buffer, 0, 16, 0) await fileHandle.close() const detectedExt = detectExtFromHeader(buffer) || '.mp4' const newName = record.zipFileName.replace(/\.zip$/i, detectedExt) const newAbsolutePath = path.join(path.dirname(absolutePath), newName) try { await fs.access(newAbsolutePath) console.warn(`目标文件已存在,跳过重命名: ${newAbsolutePath}`) } catch { await fs.rename(absolutePath, newAbsolutePath) } const storedPath = buildStoredPath(record.zipFilePath, newName) const sourceUrl = buildVideoSourceUrl(record.videoSourceUrl, newName) await prisma.material.update({ where: { id: record.id }, data: { zipFileName: newName, zipFilePath: storedPath, videoSourceUrl: sourceUrl, }, }) fixedCount += 1 console.log(`已修复: ${record.id} -> ${newName}`) } catch (error) { skippedMissing += 1 console.error(`修复失败 ${record.id}:`, error.message) } } console.log(`修复完成:${fixedCount} 条成功,${skippedMissing} 条失败`) } main() .catch((error) => { console.error('修复过程出错:', error) process.exitCode = 1 }) .finally(async () => { await prisma.$disconnect() })