108 lines
3.5 KiB
JavaScript
108 lines
3.5 KiB
JavaScript
/**
|
||
* 一次性全量视频重转码脚本(容器内执行)
|
||
* - 直接复用 /api/materials/transcode 队列接口,无需登录
|
||
* - 自动兼容缺失 zipFilePath / deletedAt 列的旧库
|
||
* - 逐个排队并输出进度,避免误以为卡住
|
||
*
|
||
* 使用方式(宿主机运行):
|
||
* docker cp scripts/retranscode-once.js youyidian-3d-material:/app/
|
||
* docker exec -it youyidian-3d-material node /app/retranscode-once.js
|
||
*/
|
||
|
||
const { PrismaClient } = require('/app/node_modules/@prisma/client')
|
||
const prisma = new PrismaClient()
|
||
|
||
const sleep = (ms) => new Promise((r) => setTimeout(r, ms))
|
||
|
||
async function hasColumn(column) {
|
||
try {
|
||
const rows = await prisma.$queryRaw`
|
||
SELECT column_name FROM information_schema.columns
|
||
WHERE table_name = 'materials' AND column_name = ${column}
|
||
`
|
||
return rows.length > 0
|
||
} catch (e) {
|
||
console.warn('[retranscode] column check failed:', column, e?.message || e)
|
||
return false
|
||
}
|
||
}
|
||
|
||
async function main() {
|
||
console.log('========== 全量重转码开始 ==========')
|
||
|
||
const hasZipFilePath = await hasColumn('zipFilePath')
|
||
const hasDeletedAt = await hasColumn('deletedAt')
|
||
console.log('列检测 => zipFilePath:', hasZipFilePath, 'deletedAt:', hasDeletedAt)
|
||
|
||
// 使用最兼容的条件:仅基于 videoSourceUrl,避免 Prisma 客户端缺少字段类型时报错
|
||
const andConditions = [
|
||
{ materialType: { in: ['主图视频', '安装视频', '种草视频', '视频', 'VIDEO'] } },
|
||
{ videoSourceUrl: { not: null } },
|
||
]
|
||
if (hasDeletedAt) {
|
||
andConditions.push({ deletedAt: null })
|
||
}
|
||
|
||
console.log('[retranscode] 查询候选素材...')
|
||
const candidates = await prisma.material.findMany({
|
||
where: { AND: andConditions },
|
||
select: { id: true, productModel: true, createdAt: true },
|
||
orderBy: { createdAt: 'asc' },
|
||
})
|
||
|
||
console.log(`[retranscode] 匹配到 ${candidates.length} 条素材,开始逐个入队...`)
|
||
|
||
let baseUrl = process.env.NEXTAUTH_URL || process.env.APP_PUBLIC_URL || 'http://127.0.0.1:3000'
|
||
// 避免 localhost 解析到 IPv6(::1) 导致连接失败
|
||
baseUrl = baseUrl.replace('localhost', '127.0.0.1').replace(/\/$/, '')
|
||
console.log('[retranscode] Base URL =', baseUrl)
|
||
|
||
let success = 0
|
||
let failed = 0
|
||
|
||
for (let i = 0; i < candidates.length; i++) {
|
||
const m = candidates[i]
|
||
const idx = i + 1
|
||
const prefix = `[${idx}/${candidates.length}] ${m.id} (${m.productModel || '无型号'})`
|
||
try {
|
||
process.stdout.write(`${prefix} -> 入队中... `)
|
||
const res = await fetch(`${baseUrl}/api/materials/transcode`, {
|
||
method: 'POST',
|
||
headers: { 'Content-Type': 'application/json' },
|
||
body: JSON.stringify({ materialId: m.id, forceRetry: true }),
|
||
})
|
||
if (!res.ok) {
|
||
const text = await res.text().catch(() => '')
|
||
console.log(`失败 ${res.status} ${text}`)
|
||
failed++
|
||
} else {
|
||
console.log('成功')
|
||
success++
|
||
}
|
||
} catch (e) {
|
||
console.log(`异常 ${(e && e.message) || e}`)
|
||
if (e && e.stack) {
|
||
console.log(String(e.stack).split('\n')[0])
|
||
}
|
||
failed++
|
||
}
|
||
|
||
// 节流,避免请求过快
|
||
if (idx % 10 === 0) {
|
||
await sleep(100)
|
||
}
|
||
}
|
||
|
||
console.log('========== 全量重转码完成 ==========')
|
||
console.log(`总计: ${candidates.length},入队成功: ${success},失败: ${failed}`)
|
||
|
||
await prisma.$disconnect()
|
||
}
|
||
|
||
main().catch(async (e) => {
|
||
console.error('运行出错:', e)
|
||
await prisma.$disconnect()
|
||
process.exit(1)
|
||
})
|
||
|