import fs from 'fs' import path from 'path' import { createHash } from 'crypto' /** * 下载并本地化图片 * @param imageUrl 原始图片URL * @param basePath 保存图片的基础路径 * @returns 本地化后的图片路径 */ export async function localizeImage(imageUrl: string, basePath = 'public/images/remote'): Promise { // 检查URL是否有效 if (!imageUrl || !imageUrl.startsWith('http')) { return imageUrl } try { // 创建保存目录 const fullBasePath = path.resolve(process.cwd(), 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) // 返回本地URL(相对于public目录) return `/images/remote/${fileName}` } catch (error) { console.error(`本地化图片失败 ${imageUrl}:`, error) return imageUrl // 失败时返回原始URL } } /** * 递归处理对象中的所有图片URL * @param data 需要处理的数据对象或数组 * @param imageFields 指定哪些字段包含图片URL * @returns 处理后的数据 */ export async function localizeImages( data: any, imageFields = ['image', 'imageUrl', 'thumbnail', 'cover', 'avatar', 'photo', 'src'] ): Promise { 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 }