- 创建统一的下载错误处理模块 (lib/download-errors.ts) - 实现13种错误类型分类和中文错误消息 - 后端统一使用 createDownloadError 创建标准化错误响应 - 前端统一使用 parseDownloadError 解析错误并显示 - 修复正式系统数据库Schema,添加产品全案相关字段和表 - 执行数据库迁移:添加 task_no, task_status, task_fetched_at 字段 - 创建 material_assets, api_keys, api_call_logs 表
167 lines
5.4 KiB
TypeScript
167 lines
5.4 KiB
TypeScript
/**
|
||
* 检查正式系统中上传失败的素材
|
||
*
|
||
* 用途:排查上传中的素材问题
|
||
*/
|
||
|
||
import { PrismaClient } from '@prisma/client'
|
||
import { existsSync } from 'fs'
|
||
|
||
const prisma = new PrismaClient()
|
||
|
||
async function checkUploadIssues() {
|
||
try {
|
||
console.log('=== 检查上传失败的素材 ===\n')
|
||
|
||
// 查询最近24小时内创建的素材
|
||
const twentyFourHoursAgo = new Date(Date.now() - 24 * 60 * 60 * 1000)
|
||
|
||
const recentMaterials = await prisma.material.findMany({
|
||
where: {
|
||
createdAt: {
|
||
gte: twentyFourHoursAgo,
|
||
},
|
||
deletedAt: null,
|
||
},
|
||
orderBy: {
|
||
createdAt: 'desc',
|
||
},
|
||
include: {
|
||
user: {
|
||
select: {
|
||
username: true,
|
||
email: true,
|
||
},
|
||
},
|
||
},
|
||
})
|
||
|
||
console.log(`找到 ${recentMaterials.length} 条最近24小时创建的素材记录\n`)
|
||
|
||
// 检查可能有问题的情况
|
||
const issues: Array<{
|
||
material: any
|
||
issue: string
|
||
details: string
|
||
}> = []
|
||
|
||
for (const material of recentMaterials) {
|
||
const issuesForThis: string[] = []
|
||
const detailsForThis: string[] = []
|
||
|
||
// 1. 检查视频处理状态
|
||
if (material.videoProcessingStatus) {
|
||
if (material.videoProcessingStatus === 'failed') {
|
||
issuesForThis.push('视频转码失败')
|
||
if (material.transcodeLastError) {
|
||
detailsForThis.push(`错误: ${material.transcodeLastError}`)
|
||
}
|
||
} else if (material.videoProcessingStatus === 'pending') {
|
||
// 检查是否pending太久(超过1小时)
|
||
const hoursPending = (Date.now() - material.createdAt.getTime()) / (1000 * 60 * 60)
|
||
if (hoursPending > 1) {
|
||
issuesForThis.push('视频转码pending时间过长')
|
||
detailsForThis.push(`已pending ${hoursPending.toFixed(1)} 小时`)
|
||
}
|
||
} else if (material.videoProcessingStatus === 'processing') {
|
||
// 检查是否processing太久(超过2小时)
|
||
const hoursProcessing = (Date.now() - material.updatedAt.getTime()) / (1000 * 60 * 60)
|
||
if (hoursProcessing > 2) {
|
||
issuesForThis.push('视频转码processing时间过长')
|
||
detailsForThis.push(`已processing ${hoursProcessing.toFixed(1)} 小时`)
|
||
}
|
||
}
|
||
}
|
||
|
||
// 2. 检查文件是否存在
|
||
if (material.zipFilePath) {
|
||
if (!existsSync(material.zipFilePath)) {
|
||
issuesForThis.push('源文件不存在')
|
||
detailsForThis.push(`路径: ${material.zipFilePath}`)
|
||
}
|
||
}
|
||
|
||
// 3. 检查预览图URL是否为空
|
||
if (!material.previewUrl || material.previewUrl.trim() === '') {
|
||
issuesForThis.push('预览图URL为空')
|
||
}
|
||
|
||
// 4. 检查是否有zipFileSize但zipFilePath为空
|
||
if (material.zipFileSize > 0 && (!material.zipFilePath || material.zipFilePath.trim() === '')) {
|
||
issuesForThis.push('有文件大小但文件路径为空')
|
||
detailsForThis.push(`文件大小: ${material.zipFileSize} 字节`)
|
||
}
|
||
|
||
if (issuesForThis.length > 0) {
|
||
issues.push({
|
||
material,
|
||
issue: issuesForThis.join(', '),
|
||
details: detailsForThis.join(' | '),
|
||
})
|
||
}
|
||
}
|
||
|
||
// 输出结果
|
||
if (issues.length === 0) {
|
||
console.log('✅ 未发现明显的问题\n')
|
||
} else {
|
||
console.log(`⚠️ 发现 ${issues.length} 个可能有问题素材:\n`)
|
||
|
||
for (const { material, issue, details } of issues) {
|
||
console.log(`素材ID: ${material.id}`)
|
||
console.log(` SPU: ${material.productModel}`)
|
||
console.log(` 类型: ${material.materialType}`)
|
||
console.log(` 作者: ${material.authorName} (${material.user?.username || 'N/A'})`)
|
||
console.log(` 创建时间: ${material.createdAt.toLocaleString('zh-CN')}`)
|
||
console.log(` 更新时间: ${material.updatedAt.toLocaleString('zh-CN')}`)
|
||
console.log(` 问题: ${issue}`)
|
||
if (details) {
|
||
console.log(` 详情: ${details}`)
|
||
}
|
||
if (material.videoProcessingStatus) {
|
||
console.log(` 视频状态: ${material.videoProcessingStatus}`)
|
||
if (material.transcodeAttempts) {
|
||
console.log(` 转码尝试次数: ${material.transcodeAttempts}`)
|
||
}
|
||
}
|
||
if (material.zipFilePath) {
|
||
console.log(` 文件路径: ${material.zipFilePath}`)
|
||
console.log(` 文件大小: ${material.zipFileSize} 字节`)
|
||
}
|
||
if (material.previewUrl) {
|
||
console.log(` 预览图: ${material.previewUrl.substring(0, 80)}...`)
|
||
}
|
||
console.log('')
|
||
}
|
||
}
|
||
|
||
// 输出所有最近创建的素材(简要信息)
|
||
console.log('\n=== 最近创建的素材列表(全部) ===\n')
|
||
for (const material of recentMaterials) {
|
||
const statusInfo = material.videoProcessingStatus
|
||
? ` [视频状态: ${material.videoProcessingStatus}]`
|
||
: ''
|
||
console.log(
|
||
`${material.createdAt.toLocaleString('zh-CN')} | ${material.materialType} | ${material.productModel} | ${material.authorName}${statusInfo}`
|
||
)
|
||
}
|
||
|
||
} catch (error) {
|
||
console.error('查询失败:', error)
|
||
throw error
|
||
} finally {
|
||
await prisma.$disconnect()
|
||
}
|
||
}
|
||
|
||
checkUploadIssues()
|
||
.then(() => {
|
||
console.log('\n✅ 检查完成')
|
||
process.exit(0)
|
||
})
|
||
.catch((error) => {
|
||
console.error('\n❌ 检查失败:', error)
|
||
process.exit(1)
|
||
})
|
||
|