gongxiangp2/scripts/cleanup-upload-temp.ts

182 lines
5.2 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.

/**
* 清理临时上传文件的定时任务脚本
* 用于清理:1. 过期的上传会话对应的临时文件 2. 失败的上传临时文件
*
* 运行方式:
* 1. 手动运行:npx tsx scripts/cleanup-upload-temp.ts
* 2. 定时任务:添加到 cron 或系统定时任务中
*
* 建议:每小时运行一次
*/
import { promises as fs } from 'fs'
import path from 'path'
import { getUploadSession } from '../lib/upload-sessions'
const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads'
const MAX_AGE_HOURS = 8 // 8小时后清理
const MAX_AGE = MAX_AGE_HOURS * 60 * 60 * 1000 // 转换为毫秒
async function cleanupUploadSession(uploadId: string): Promise<{
success: boolean
deletedFiles: number
freedSpace: number
error?: string
}> {
const tempDir = path.join(UPLOAD_DIR, 'temp', uploadId)
let deletedFiles = 0
let freedSpace = 0
try {
// 检查目录是否存在
try {
await fs.access(tempDir)
} catch {
// 目录不存在,说明已经清理过了
return { success: true, deletedFiles: 0, freedSpace: 0 }
}
// 读取目录中的所有文件
const files = await fs.readdir(tempDir)
for (const file of files) {
const filePath = path.join(tempDir, file)
try {
const stat = await fs.stat(filePath)
if (stat.isFile()) {
await fs.unlink(filePath)
deletedFiles++
freedSpace += stat.size
}
} catch (err) {
console.warn(`[Cleanup] 删除文件失败: ${filePath}`, err)
}
}
// 删除目录本身
try {
await fs.rmdir(tempDir)
} catch (err) {
console.warn(`[Cleanup] 删除目录失败: ${tempDir}`, err)
}
return { success: true, deletedFiles, freedSpace }
} catch (error: any) {
return {
success: false,
deletedFiles,
freedSpace,
error: error.message || '清理失败'
}
}
}
function formatBytes(bytes: number): string {
if (bytes === 0) return '0 B'
const k = 1024
const sizes = ['B', 'KB', 'MB', 'GB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]
}
async function main() {
console.log(`[Cleanup] 开始清理临时上传文件...`)
console.log(`[Cleanup] 临时目录: ${path.join(UPLOAD_DIR, 'temp')}`)
console.log(`[Cleanup] 最大年龄: ${MAX_AGE_HOURS} 小时`)
const tempBaseDir = path.join(UPLOAD_DIR, 'temp')
// 确保临时目录存在
try {
await fs.access(tempBaseDir)
} catch {
console.log('[Cleanup] 临时目录不存在,无需清理')
return
}
const now = Date.now()
const tempDirs = await fs.readdir(tempBaseDir)
let cleaned = 0
let totalFreedSpace = 0
const errors: string[] = []
for (const dir of tempDirs) {
const uploadId = dir
const tempDir = path.join(tempBaseDir, dir)
try {
// 检查目录
const stat = await fs.stat(tempDir)
if (!stat.isDirectory()) {
continue
}
// 读取元数据文件(如果存在)
const metaPath = path.join(tempDir, 'meta.json')
let createdAt = stat.mtimeMs // 使用目录修改时间作为fallback
try {
const metaContent = await fs.readFile(metaPath, 'utf-8')
const meta = JSON.parse(metaContent)
if (meta.createdAt) {
createdAt = meta.createdAt
}
} catch {
// 元数据文件不存在,使用目录修改时间
}
// 检查是否过期
const age = now - createdAt
const ageHours = age / (60 * 60 * 1000)
const isExpired = age > MAX_AGE
// 检查会话是否存在
let sessionExists = false
try {
const session = await getUploadSession(uploadId)
sessionExists = !!session
} catch (err) {
// 获取会话失败,认为不存在
sessionExists = false
}
// 决定是否清理:过期的 或 会话不存在的
const shouldCleanup = isExpired || !sessionExists
if (shouldCleanup) {
const reason = isExpired ? `过期 (${ageHours.toFixed(1)}小时)` : '会话不存在'
console.log(`[Cleanup] 清理上传会话: ${uploadId} (${reason})`)
const result = await cleanupUploadSession(uploadId)
if (result.success) {
cleaned += result.deletedFiles
totalFreedSpace += result.freedSpace
console.log(`[Cleanup] ✓ 清理成功: ${result.deletedFiles} 个文件, 释放 ${formatBytes(result.freedSpace)}`)
} else {
errors.push(`${uploadId}: ${result.error}`)
console.error(`[Cleanup] ✗ 清理失败: ${uploadId} - ${result.error}`)
}
}
} catch (err: any) {
errors.push(`${dir}: ${err.message || '读取失败'}`)
console.error(`[Cleanup] ✗ 处理目录失败: ${dir} - ${err.message}`)
}
}
console.log(`[Cleanup] 清理完成:`)
console.log(` - 清理文件数: ${cleaned}`)
console.log(` - 释放空间: ${formatBytes(totalFreedSpace)}`)
if (errors.length > 0) {
console.log(` - 错误数: ${errors.length}`)
errors.forEach(err => console.log(` - ${err}`))
}
}
// 运行清理
main().catch(error => {
console.error('[Cleanup] 清理过程出错:', error)
process.exit(1)
})