gongxiangp2/scripts/analyze-external-file-sizes.ts

241 lines
8.1 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.

/**
* 分析外网服务器上存在的老素材源文件大小
*/
import { readFile, writeFile } from 'fs/promises'
interface ExternalCheckRow {
素材ID: string
素材类型: string
产品型号: string
作者: string
使用店铺: string
源文件路径: string
外网服务器URL: string
文件是否存在: string
源文件大小: string
创建时间: string
}
async function main() {
console.log('='.repeat(60))
console.log('分析外网服务器上存在的老素材源文件大小')
console.log('='.repeat(60))
console.log()
// 读取外网服务器检查报告
console.log('📋 读取外网服务器检查报告...')
let externalCheckData: ExternalCheckRow[] = []
try {
const externalCheckContent = await readFile('老素材外网服务器检查报告.csv', 'utf-8')
const lines = externalCheckContent.split('\n').filter(line => line.trim())
if (lines.length < 2) {
throw new Error('CSV文件格式错误')
}
// 简单的CSV解析(处理带引号的字段)
function parseCSVLine(line: string): string[] {
const result: string[] = []
let current = ''
let inQuotes = false
for (let i = 0; i < line.length; i++) {
const char = line[i]
if (char === '"') {
if (inQuotes && line[i + 1] === '"') {
current += '"'
i++ // 跳过下一个引号
} else {
inQuotes = !inQuotes
}
} else if (char === ',' && !inQuotes) {
result.push(current)
current = ''
} else {
current += char
}
}
result.push(current)
return result
}
const headers = parseCSVLine(lines[0]).map(h => h.trim())
for (let i = 1; i < lines.length; i++) {
const values = parseCSVLine(lines[i])
const row: any = {}
headers.forEach((header, index) => {
row[header] = (values[index] || '').trim()
})
externalCheckData.push(row as ExternalCheckRow)
}
console.log(` 读取到 ${externalCheckData.length} 条记录`)
} catch (error) {
console.error('❌ 无法读取外网服务器检查报告:', error)
process.exit(1)
}
// 筛选出存在的文件
const existingFiles = externalCheckData.filter(row => row['文件是否存在'] === '是')
const missingFiles = externalCheckData.filter(row => row['文件是否存在'] === '否')
console.log(` 外网服务器存在: ${existingFiles.length} 个`)
console.log(` 外网服务器缺失: ${missingFiles.length} 个`)
console.log()
// 分析文件大小
console.log('📊 分析文件大小...')
let totalSize = 0
let totalSizeGB = 0
const sizeStats: Array<{
materialId: string
materialType: string
productModel: string
authorName: string
categoryName: string
zipFilePath: string
externalUrl: string
fileSize: number
fileSizeMB: number
fileSizeGB: number
createdAt: string
}> = []
for (const row of existingFiles) {
const fileSizeStr = (row['源文件大小(字节)'] || row['源文件大小'] || '0').trim()
const fileSize = parseInt(fileSizeStr, 10)
if (!isNaN(fileSize) && fileSize > 0) {
totalSize += fileSize
const fileSizeMB = fileSize / (1024 * 1024)
const fileSizeGB = fileSize / (1024 * 1024 * 1024)
sizeStats.push({
materialId: row['素材ID'],
materialType: row['素材类型'],
productModel: row['产品型号'],
authorName: row['作者'],
categoryName: row['使用店铺'],
zipFilePath: row['源文件路径'],
externalUrl: row['外网服务器URL'],
fileSize,
fileSizeMB: Math.round(fileSizeMB * 100) / 100,
fileSizeGB: Math.round(fileSizeGB * 1000) / 1000,
createdAt: row['创建时间'],
})
}
}
totalSizeGB = totalSize / (1024 * 1024 * 1024)
console.log(` 总文件数: ${sizeStats.length} 个`)
console.log(` 总大小: ${(totalSizeGB).toFixed(2)} GB (${totalSize} 字节)`)
console.log()
// 按大小排序
sizeStats.sort((a, b) => b.fileSize - a.fileSize)
// 统计信息
const avgSize = totalSize / sizeStats.length
const avgSizeMB = avgSize / (1024 * 1024)
const maxSize = sizeStats[0]?.fileSize || 0
const minSize = sizeStats[sizeStats.length - 1]?.fileSize || 0
console.log('📈 大小统计:')
console.log(` 平均大小: ${(avgSizeMB).toFixed(2)} MB`)
console.log(` 最大文件: ${(maxSize / (1024 * 1024)).toFixed(2)} MB (${sizeStats[0]?.productModel || 'N/A'})`)
console.log(` 最小文件: ${(minSize / (1024 * 1024)).toFixed(2)} MB (${sizeStats[sizeStats.length - 1]?.productModel || 'N/A'})`)
console.log()
// 按素材类型统计
const typeStats: Record<string, { count: number; totalSize: number; avgSize: number }> = {}
for (const stat of sizeStats) {
if (!typeStats[stat.materialType]) {
typeStats[stat.materialType] = { count: 0, totalSize: 0, avgSize: 0 }
}
typeStats[stat.materialType].count++
typeStats[stat.materialType].totalSize += stat.fileSize
}
for (const type in typeStats) {
typeStats[type].avgSize = typeStats[type].totalSize / typeStats[type].count
}
console.log('📦 按素材类型统计:')
for (const [type, stats] of Object.entries(typeStats).sort((a, b) => b[1].totalSize - a[1].totalSize)) {
const totalGB = stats.totalSize / (1024 * 1024 * 1024)
const avgMB = stats.avgSize / (1024 * 1024)
console.log(` ${type}: ${stats.count} 个, 总计 ${totalGB.toFixed(2)} GB, 平均 ${avgMB.toFixed(2)} MB`)
}
console.log()
// 生成详细报告
console.log('💾 生成详细报告...')
const reportRows: string[] = []
reportRows.push('素材ID,素材类型,产品型号,作者,使用店铺,源文件路径,外网服务器URL,文件大小(字节),文件大小(MB),文件大小(GB),创建时间')
for (const stat of sizeStats) {
const row = [
stat.materialId,
stat.materialType,
stat.productModel,
stat.authorName,
stat.categoryName,
stat.zipFilePath,
stat.externalUrl,
stat.fileSize.toString(),
stat.fileSizeMB.toFixed(2),
stat.fileSizeGB.toFixed(3),
stat.createdAt,
]
reportRows.push(row.map(field => {
if (field === null || field === undefined) return ''
const str = String(field)
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
return `"${str.replace(/"/g, '""')}"`
}
return str
}).join(','))
}
const csvContent = reportRows.join('\n')
const outputDir = process.env.IS_IN_CONTAINER === 'true' ? '/tmp' : '.'
const statsFile = `${outputDir}/老素材外网服务器源文件大小统计.csv`
const summaryFile = `${outputDir}/老素材外网服务器源文件大小汇总.csv`
await writeFile(statsFile, csvContent, 'utf-8')
console.log(`✅ 报告已生成: ${statsFile}`)
console.log(` 包含 ${sizeStats.length} 个存在文件的大小信息`)
console.log()
// 生成汇总报告
const summaryRows: string[] = []
summaryRows.push('统计项,数值')
summaryRows.push(`总文件数,${sizeStats.length}`)
summaryRows.push(`总大小(字节),${totalSize}`)
summaryRows.push(`总大小(MB),${(totalSize / (1024 * 1024)).toFixed(2)}`)
summaryRows.push(`总大小(GB),${totalSizeGB.toFixed(2)}`)
summaryRows.push(`平均大小(MB),${avgSizeMB.toFixed(2)}`)
summaryRows.push(`最大文件(MB),${(maxSize / (1024 * 1024)).toFixed(2)}`)
summaryRows.push(`最小文件(MB),${(minSize / (1024 * 1024)).toFixed(2)}`)
summaryRows.push('')
summaryRows.push('素材类型,文件数,总大小(GB),平均大小(MB)')
for (const [type, stats] of Object.entries(typeStats).sort((a, b) => b[1].totalSize - a[1].totalSize)) {
const totalGB = stats.totalSize / (1024 * 1024 * 1024)
const avgMB = stats.avgSize / (1024 * 1024)
summaryRows.push(`${type},${stats.count},${totalGB.toFixed(2)},${avgMB.toFixed(2)}`)
}
await writeFile(summaryFile, summaryRows.join('\n'), 'utf-8')
console.log(`✅ 汇总报告已生成: ${summaryFile}`)
console.log()
}
main().catch((error) => {
console.error('执行失败:', error)
process.exit(1)
})