123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- #!/usr/bin/env node
-
- /**
- * 图片本地化处理脚本
- * 在静态站点生成后运行,将API返回的图片下载到本地
- */
-
- import fs from 'fs'
- import path from 'path'
- import { createHash } from 'crypto'
- import { fileURLToPath } from 'url'
-
- // 获取当前文件的目录
- const __filename = fileURLToPath(import.meta.url)
- const __dirname = path.dirname(__filename)
- const PROJECT_ROOT = path.resolve(__dirname, '..')
-
- /**
- * 下载并本地化图片
- * @param imageUrl 原始图片URL
- * @param basePath 保存图片的基础路径
- * @returns 本地化后的图片路径
- */
- async function localizeImage(imageUrl, basePath = 'public/images/remote') {
- // 检查URL是否有效
- if (!imageUrl || !imageUrl.startsWith('http')) {
- return imageUrl
- }
-
- try {
- // 创建保存目录
- const fullBasePath = path.resolve(PROJECT_ROOT, basePath)
- if (!fs.existsSync(fullBasePath)) {
- fs.mkdirSync(fullBasePath, { recursive: true })
- }
-
- // 生成唯一文件名(使用URL的MD5哈希)
- const urlHash = createHash('md5').update(imageUrl).digest('hex')
- const fileExt = path.extname(new URL(imageUrl).pathname) || '.jpg'
- const fileName = `${urlHash}${fileExt}`
- const filePath = path.join(fullBasePath, fileName)
-
- // 如果文件已存在,直接返回路径
- if (fs.existsSync(filePath)) {
- return `/images/remote/${fileName}`
- }
-
- // 下载图片
- const response = await fetch(imageUrl)
- if (!response.ok) {
- throw new Error(`下载图片失败: ${response.status} ${response.statusText}`)
- }
-
- const buffer = Buffer.from(await response.arrayBuffer())
- fs.writeFileSync(filePath, buffer)
-
- console.log(`✅ 下载图片成功: ${imageUrl} -> /images/remote/${fileName}`)
-
- // 返回本地URL(相对于public目录)
- return `/images/remote/${fileName}`
- } catch (error) {
- console.error(`❌ 本地化图片失败 ${imageUrl}:`, error)
- return imageUrl // 失败时返回原始URL
- }
- }
-
- /**
- * 递归处理对象中的所有图片URL
- * @param data 需要处理的数据对象或数组
- * @param imageFields 指定哪些字段包含图片URL
- * @returns 处理后的数据
- */
- async function localizeImages(
- data,
- imageFields = ['image', 'imageUrl', 'thumbnail', 'cover', 'avatar', 'photo', 'src']
- ) {
- if (!data) return data
-
- // 处理数组
- if (Array.isArray(data)) {
- return Promise.all(data.map(item => localizeImages(item, imageFields)))
- }
-
- // 处理对象
- if (typeof data === 'object') {
- const result = { ...data }
-
- // 处理所有键
- for (const [key, value] of Object.entries(result)) {
- // 如果是图片字段且值是字符串,本地化图片
- if (imageFields.includes(key) && typeof value === 'string') {
- result[key] = await localizeImage(value)
- }
- // 递归处理嵌套对象或数组
- else if (typeof value === 'object' && value !== null) {
- result[key] = await localizeImages(value, imageFields)
- }
- }
-
- return result
- }
-
- return data
- }
-
- /**
- * 从JSON文件提取图片URL并下载到本地
- * @param jsonFilePath JSON文件路径
- * @param imageFields 包含图片URL的字段名称数组
- */
- async function extractAndDownloadImages(
- jsonFilePath,
- imageFields = ['image', 'imageUrl', 'thumbnail', 'cover', 'avatar', 'photo', 'src']
- ) {
- // 检查文件是否存在
- if (!fs.existsSync(jsonFilePath)) {
- console.error(`文件不存在: ${jsonFilePath}`)
- return
- }
-
- try {
- // 读取JSON文件
- const jsonData = JSON.parse(fs.readFileSync(jsonFilePath, 'utf-8'))
-
- // 递归查找所有图片URL
- const imageUrls = new Set()
-
- // 递归函数查找所有图片URL
- function findImageUrls(obj) {
- if (!obj) return
-
- if (Array.isArray(obj)) {
- obj.forEach(item => findImageUrls(item))
- return
- }
-
- if (typeof obj === 'object') {
- for (const [key, value] of Object.entries(obj)) {
- if (imageFields.includes(key) && typeof value === 'string' && value.startsWith('http')) {
- imageUrls.add(value)
- } else if (typeof value === 'object' && value !== null) {
- findImageUrls(value)
- }
- }
- }
- }
-
- findImageUrls(jsonData)
-
- // 下载所有图片
- console.log(`📋 在 ${jsonFilePath} 中找到 ${imageUrls.size} 个图片URL`)
-
- let downloadedCount = 0
- for (const url of imageUrls) {
- try {
- const localUrl = await localizeImage(url)
- if (localUrl !== url) {
- downloadedCount++
- }
- } catch (error) {
- console.error(`❌ 下载失败 ${url}:`, error)
- }
- }
-
- console.log(`📊 成功下载 ${downloadedCount}/${imageUrls.size} 个图片`)
-
- // 将本地化的图片URL写回到JSON文件
- try {
- const localizedData = await localizeImages(jsonData)
- fs.writeFileSync(jsonFilePath, JSON.stringify(localizedData, null, 2))
- console.log(`💾 已更新图片URL至本地路径: ${jsonFilePath}`)
- } catch (error) {
- console.error(`❌ 无法更新JSON文件 ${jsonFilePath}:`, error)
- }
- } catch (error) {
- console.error(`❌ 处理文件 ${jsonFilePath} 时出错:`, error)
- }
- }
-
- /**
- * 处理API响应文件中的图片
- * @param outputDir 输出目录路径(通常是.output目录)
- */
- async function processApiResponseImages(outputDir = '.output') {
- console.log('🚀 开始处理API响应文件中的图片...')
-
- // 检查输出目录是否存在
- if (!fs.existsSync(outputDir)) {
- console.error(`❌ 输出目录不存在: ${outputDir}`)
- return
- }
-
- const serverDir = path.join(outputDir, 'server/api')
-
- if (!fs.existsSync(serverDir)) {
- console.error(`❌ 服务器API目录不存在: ${serverDir}`)
- return
- }
-
- console.log(`🔍 扫描API响应文件: ${serverDir}`)
-
- // 递归函数查找所有JSON文件
- async function processDirectory(dir) {
- const entries = fs.readdirSync(dir, { withFileTypes: true })
-
- for (const entry of entries) {
- const fullPath = path.join(dir, entry.name)
-
- if (entry.isDirectory()) {
- await processDirectory(fullPath)
- } else if (entry.name.endsWith('.json')) {
- console.log(`📄 处理JSON文件: ${fullPath}`)
- await extractAndDownloadImages(fullPath)
- }
- }
- }
-
- await processDirectory(serverDir)
- console.log('✅ 所有API响应文件处理完成')
- }
-
- // 确保脚本能单独运行
- async function main() {
- console.log('===== 🖼️ 开始图片本地化处理 =====')
-
- try {
- // 默认的输出目录是.output
- const outputDir = process.argv[2] || '.output'
- await processApiResponseImages(outputDir)
- console.log('===== ✅ 图片本地化处理完成 =====')
- process.exit(0)
- } catch (error) {
- console.error('❌ 图片本地化处理失败:', error)
- process.exit(1)
- }
- }
-
- main()
|