2025-08-19 14:33:59 +08:00
|
|
|
|
import fs from 'fs'
|
|
|
|
|
|
import { isEmpty } from 'lodash'
|
|
|
|
|
|
import path from 'path'
|
|
|
|
|
|
import util from 'util'
|
|
|
|
|
|
import { exec } from 'child_process'
|
|
|
|
|
|
const execAsync = util.promisify(exec)
|
|
|
|
|
|
const fspromises = fs.promises
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 判断文件或目录是否存在
|
|
|
|
|
|
* @param {*} path 文件或目录的路径
|
|
|
|
|
|
* @returns true表示存在,false表示不存在
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function CheckFileOrDirExist(filePath) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
let newFilePath = path.resolve(filePath)
|
|
|
|
|
|
await fspromises.access(newFilePath)
|
|
|
|
|
|
return true // 文件或目录存在
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
return false // 文件或目录不存在
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 检查文件夹是不是存在,不存在的话,创建
|
|
|
|
|
|
export async function CheckFolderExistsOrCreate(folderPath) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!(await CheckFileOrDirExist(folderPath))) {
|
|
|
|
|
|
await fspromises.mkdir(folderPath, { recursive: true })
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 拼接两个地址,返回拼接后的地址
|
|
|
|
|
|
* @param {*} rootPath 根目录的消息
|
|
|
|
|
|
* @param {*} subPath 子目录的消息
|
|
|
|
|
|
* @returns
|
|
|
|
|
|
*/
|
|
|
|
|
|
export function JoinPath(rootPath: string, subPath: string | null): string | undefined {
|
|
|
|
|
|
// 判断第二个地址是不是存在,不存在返回null,存在返回拼接后的地址
|
|
|
|
|
|
if (subPath && !isEmpty(subPath)) {
|
|
|
|
|
|
return path.resolve(rootPath, subPath)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return undefined
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除指定的文件中里面所有的文件和文件夹
|
|
|
|
|
|
* @param {*} folderPath 文件夹地址
|
|
|
|
|
|
* @param {*} isDeleteOut 是否删除最外层的文件夹,默认false,不删除
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function DeleteFolderAllFile(
|
|
|
|
|
|
folderPath: string,
|
|
|
|
|
|
isDeleteOut: boolean = false
|
|
|
|
|
|
): Promise<void> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
let folderIsExist = await CheckFileOrDirExist(folderPath)
|
|
|
|
|
|
if (!folderIsExist) {
|
|
|
|
|
|
throw new Error('目的文件夹不存在,' + folderPath)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 开始删除
|
|
|
|
|
|
let files = await fspromises.readdir(folderPath)
|
|
|
|
|
|
for (const file of files) {
|
|
|
|
|
|
const curPath = path.join(folderPath, file)
|
|
|
|
|
|
const stat = await fspromises.stat(curPath)
|
|
|
|
|
|
if (stat.isDirectory()) {
|
|
|
|
|
|
// 判断是不是文件夹
|
|
|
|
|
|
await DeleteFolderAllFile(curPath) // 递归删除文件夹内容
|
|
|
|
|
|
await fspromises.rmdir(curPath) // 删除空文件夹
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 删除文件
|
|
|
|
|
|
await fspromises.unlink(curPath)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// 判断是不是要删除最外部的文件夹
|
|
|
|
|
|
if (isDeleteOut) {
|
|
|
|
|
|
await fspromises.rmdir(folderPath)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 拷贝一个文件或者是文件夹到指定的目标地址
|
|
|
|
|
|
* @param {*} source 源文件或文件夹地址
|
|
|
|
|
|
* @param {*} target 目标文件或文件夹地址
|
|
|
|
|
|
* @param {*} checkParent 是否检查父文件夹是否存在,不存在的话创建,默认false,不检查,不存在直接创建
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function CopyFileOrFolder(source, target, checkParent = false) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 判断源文件或文件夹是不是存在
|
|
|
|
|
|
if (!(await CheckFileOrDirExist(source))) {
|
|
|
|
|
|
throw new Error(`源文件或文件夹不存在: ${source}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
// 判断父文件夹是否存在,不存在创建
|
|
|
|
|
|
const parent_path = path.dirname(target)
|
|
|
|
|
|
let parentIsExist = await CheckFileOrDirExist(parent_path)
|
|
|
|
|
|
if (!parentIsExist) {
|
|
|
|
|
|
if (checkParent) {
|
|
|
|
|
|
throw new Error(`目的文件或文件夹的父文件夹不存在: ${parent_path}`)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await fspromises.mkdir(parent_path, { recursive: true })
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是不是文件夹
|
|
|
|
|
|
const isDirectory = await IsDirectory(source)
|
|
|
|
|
|
// 复制文件夹的逻辑
|
|
|
|
|
|
async function copyDirectory(source, target) {
|
|
|
|
|
|
// 创建目标文件夹
|
|
|
|
|
|
await fspromises.mkdir(target, { recursive: true })
|
|
|
|
|
|
let entries = await fspromises.readdir(source, { withFileTypes: true })
|
|
|
|
|
|
for (let entry of entries) {
|
|
|
|
|
|
let srcPath = path.join(source, entry.name)
|
|
|
|
|
|
let tgtPath = path.join(target, entry.name)
|
|
|
|
|
|
|
|
|
|
|
|
if (entry.isDirectory()) {
|
|
|
|
|
|
await copyDirectory(srcPath, tgtPath)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
await fspromises.copyFile(srcPath, tgtPath)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if (isDirectory) {
|
|
|
|
|
|
// 创建目标文件夹
|
|
|
|
|
|
await copyDirectory(source, target)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 复制文件
|
|
|
|
|
|
await fspromises.copyFile(source, target)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/** * 判断一个文件地址是不是文件夹
|
|
|
|
|
|
* @param {*} path 输入的文件地址
|
|
|
|
|
|
* @returns true 是 false 不是
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function IsDirectory(path) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
const stat = await fspromises.stat(path)
|
|
|
|
|
|
return stat.isDirectory()
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw new Error(`获取文件夹信息失败: ${path}`)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 将文件或者是文件夹备份到指定的文职
|
|
|
|
|
|
* @param {*} source_path 源文件/文件夹地址
|
|
|
|
|
|
* @param {*} target_path 目标文件/文件夹地址
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function BackupFileOrFolder(source_path: string, target_path: string): Promise<void> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 判断父文件夹是否存在,不存在创建
|
|
|
|
|
|
const parent_path = path.dirname(target_path)
|
|
|
|
|
|
if (!(await CheckFileOrDirExist(parent_path))) {
|
|
|
|
|
|
await fspromises.mkdir(parent_path, { recursive: true })
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 判断是不是文件夹
|
|
|
|
|
|
const isDirectory = await IsDirectory(source_path)
|
|
|
|
|
|
|
|
|
|
|
|
if (isDirectory) {
|
|
|
|
|
|
// 复制文件夹
|
|
|
|
|
|
await fspromises.rename(source_path, target_path)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// 复制文件
|
|
|
|
|
|
await fspromises.copyFile(source_path, target_path)
|
|
|
|
|
|
}
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取指定的文件夹下面的所有的指定的拓展名的文件
|
|
|
|
|
|
* @param {*} folderPath 文件夹地址
|
|
|
|
|
|
* @param {*} extensions 拓展地址
|
|
|
|
|
|
* @returns 返回文件中指定的后缀文件地址(绝对地址)
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function GetFilesWithExtensions(
|
|
|
|
|
|
folderPath: string,
|
|
|
|
|
|
extensions: string[]
|
|
|
|
|
|
): Promise<string[]> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 判断当前是不是文件夹
|
|
|
|
|
|
if (!(await IsDirectory(folderPath))) {
|
|
|
|
|
|
throw new Error('输入的不是有效的文件夹地址')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
let entries = await fspromises.readdir(folderPath, { withFileTypes: true })
|
|
|
|
|
|
let files = [] as any
|
|
|
|
|
|
// 使用Promise.all来并行处理所有的stat调用
|
|
|
|
|
|
const fileStats = await Promise.all(
|
|
|
|
|
|
entries.map(async (entry) => {
|
|
|
|
|
|
const entryPath = path.join(folderPath, entry.name)
|
|
|
|
|
|
if (entry.isFile()) {
|
|
|
|
|
|
return {
|
|
|
|
|
|
name: entry.name,
|
|
|
|
|
|
path: entryPath,
|
|
|
|
|
|
isFile: true
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return {
|
|
|
|
|
|
isFile: false
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 过滤出文件并且满足扩展名要求的文件
|
|
|
|
|
|
files = fileStats.filter(
|
|
|
|
|
|
(fileStat) =>
|
|
|
|
|
|
fileStat.isFile && extensions.includes(path.extname(fileStat.name ?? '').toLowerCase())
|
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 对files数组进行排序,基于文件名
|
|
|
|
|
|
files.sort((a: any, b: any) => a.name.localeCompare(b.name))
|
|
|
|
|
|
|
|
|
|
|
|
// 返回文件名数组(完整的)
|
|
|
|
|
|
return files.map((fileStat) => path.join(folderPath, fileStat.name))
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取文件的大小
|
|
|
|
|
|
* @param filePath 文件的地址
|
|
|
|
|
|
* @returns 返回的文件大小为 kb单位
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function GetFileSize(filePath: string): Promise<number> {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (!(await CheckFileOrDirExist(filePath))) {
|
|
|
|
|
|
throw new Error('获取文件大小,指定的文件不存在')
|
|
|
|
|
|
}
|
|
|
|
|
|
const stats = await fspromises.stat(filePath)
|
|
|
|
|
|
return stats.size / 1024
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 获取文件夹下的所有子文件夹信息,按创建时间排序
|
|
|
|
|
|
* @param folderPath 文件夹的路径
|
|
|
|
|
|
* @returns 返回包含子文件夹名称和完整路径的对象数组,按创建时间排序(最新的在前)
|
|
|
|
|
|
*/
|
2025-09-04 16:58:42 +08:00
|
|
|
|
export async function GetSubdirectoriesWithInfo(
|
|
|
|
|
|
folderPath: string
|
|
|
|
|
|
): Promise<Array<{ name: string; fullPath: string; ctime: Date }>> {
|
2025-08-19 14:33:59 +08:00
|
|
|
|
try {
|
|
|
|
|
|
const filesAndDirectories = await fs.promises.readdir(folderPath, { withFileTypes: true })
|
2025-09-04 16:58:42 +08:00
|
|
|
|
|
2025-08-19 14:33:59 +08:00
|
|
|
|
// 过滤出文件夹
|
|
|
|
|
|
const directories = filesAndDirectories.filter((dirent) => dirent.isDirectory())
|
2025-09-04 16:58:42 +08:00
|
|
|
|
|
2025-08-19 14:33:59 +08:00
|
|
|
|
// 并行获取所有文件夹的状态信息
|
2025-09-04 16:58:42 +08:00
|
|
|
|
const directoryStatsPromises = directories.map((dirent) =>
|
2025-08-19 14:33:59 +08:00
|
|
|
|
fs.promises.stat(path.join(folderPath, dirent.name))
|
|
|
|
|
|
)
|
|
|
|
|
|
const directoryStats = await Promise.all(directoryStatsPromises)
|
2025-09-04 16:58:42 +08:00
|
|
|
|
|
2025-08-19 14:33:59 +08:00
|
|
|
|
// 将目录信息和状态对象组合
|
|
|
|
|
|
const directoriesWithInfo = directories.map((dirent, index) => ({
|
|
|
|
|
|
name: dirent.name,
|
|
|
|
|
|
fullPath: path.join(folderPath, dirent.name),
|
|
|
|
|
|
ctime: directoryStats[index].ctime
|
|
|
|
|
|
}))
|
2025-09-04 16:58:42 +08:00
|
|
|
|
|
2025-08-19 14:33:59 +08:00
|
|
|
|
// 按创建时间排序,最新的在前
|
|
|
|
|
|
directoriesWithInfo.sort((a, b) => b.ctime.getTime() - a.ctime.getTime())
|
2025-09-04 16:58:42 +08:00
|
|
|
|
|
2025-08-19 14:33:59 +08:00
|
|
|
|
return directoriesWithInfo
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 删除目标图片,然后将原图片的exif信息删除,然后将原图片复制到目标图片地址
|
|
|
|
|
|
* @param {*} exiftoolPath exiftool的地址
|
|
|
|
|
|
* @param {*} source 原图片地址
|
|
|
|
|
|
* @param {*} target 目标图片地址
|
|
|
|
|
|
*/
|
|
|
|
|
|
export async function DeleteFileExifData(exiftoolPath: string, source: string, target: string) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
if (await CheckFileOrDirExist(target)) {
|
|
|
|
|
|
await fspromises.unlink(target)
|
|
|
|
|
|
}
|
|
|
|
|
|
let script = `"${exiftoolPath}" -all= -overwrite_original "${source}" -o "${target}"`
|
|
|
|
|
|
await execAsync(script, { maxBuffer: 1024 * 1024 * 10, encoding: 'utf-8' })
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
throw error
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* 下载网络图片到本地
|
|
|
|
|
|
*
|
|
|
|
|
|
* 该方法从指定的URL下载图片文件,并将其保存到本地指定路径。
|
|
|
|
|
|
* 如果目标文件夹不存在,会自动创建。如果指定路径已存在文件,则会覆盖。
|
2025-09-04 16:58:42 +08:00
|
|
|
|
* 支持重试机制和详细的错误处理。
|
2025-08-19 14:33:59 +08:00
|
|
|
|
*
|
|
|
|
|
|
* @param {string} imageUrl - 图片的网络URL地址
|
|
|
|
|
|
* @param {string} localPath - 保存到本地的完整路径,包含文件名和扩展名
|
2025-09-04 16:58:42 +08:00
|
|
|
|
* @param {number} maxRetries - 最大重试次数,默认为3次
|
|
|
|
|
|
* @param {number} timeout - 超时时间(毫秒),默认为60秒
|
2025-08-19 14:33:59 +08:00
|
|
|
|
* @returns {Promise<string>} 成功时返回保存的本地文件路径
|
|
|
|
|
|
* @throws {Error} 当网络请求失败、写入失败或其他错误时抛出异常
|
|
|
|
|
|
*
|
|
|
|
|
|
* @example
|
|
|
|
|
|
* // 下载图片到指定路径
|
|
|
|
|
|
* try {
|
|
|
|
|
|
* const savedPath = await DownloadImageFromUrl(
|
|
|
|
|
|
* 'https://example.com/image.jpg',
|
|
|
|
|
|
* 'd:/images/downloaded.jpg'
|
|
|
|
|
|
* );
|
|
|
|
|
|
* console.log('图片已保存至:', savedPath);
|
|
|
|
|
|
* } catch (error) {
|
|
|
|
|
|
* console.error('下载图片失败:', error.message);
|
|
|
|
|
|
* }
|
|
|
|
|
|
*/
|
2025-09-04 16:58:42 +08:00
|
|
|
|
export async function DownloadImageFromUrl(
|
|
|
|
|
|
imageUrl: string,
|
|
|
|
|
|
localPath: string,
|
|
|
|
|
|
maxRetries: number = 3,
|
|
|
|
|
|
timeout: number = 60000
|
|
|
|
|
|
): Promise<string> {
|
|
|
|
|
|
// 确保目标文件夹存在
|
|
|
|
|
|
const dirPath = path.dirname(localPath)
|
|
|
|
|
|
await CheckFolderExistsOrCreate(dirPath)
|
2025-08-19 14:33:59 +08:00
|
|
|
|
|
2025-09-04 16:58:42 +08:00
|
|
|
|
let lastError: Error | null = null
|
2025-08-19 14:33:59 +08:00
|
|
|
|
|
2025-09-04 16:58:42 +08:00
|
|
|
|
for (let attempt = 1; attempt <= maxRetries; attempt++) {
|
|
|
|
|
|
try {
|
|
|
|
|
|
// 使用fetch获取图片数据,设置超时和重试友好的配置
|
|
|
|
|
|
const response = await fetch(imageUrl, {
|
|
|
|
|
|
signal: AbortSignal.timeout(timeout),
|
|
|
|
|
|
headers: {
|
|
|
|
|
|
'User-Agent':
|
|
|
|
|
|
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36',
|
|
|
|
|
|
Accept: 'image/*,*/*;q=0.8',
|
|
|
|
|
|
'Accept-Encoding': 'gzip, deflate, br',
|
|
|
|
|
|
Connection: 'keep-alive',
|
|
|
|
|
|
'Cache-Control': 'no-cache'
|
|
|
|
|
|
}
|
|
|
|
|
|
})
|
2025-08-19 14:33:59 +08:00
|
|
|
|
|
2025-09-04 16:58:42 +08:00
|
|
|
|
if (!response.ok) {
|
|
|
|
|
|
throw new Error(`HTTP错误: ${response.status} ${response.statusText}`)
|
|
|
|
|
|
}
|
2025-08-19 14:33:59 +08:00
|
|
|
|
|
2025-09-04 16:58:42 +08:00
|
|
|
|
// 获取图片的二进制数据
|
|
|
|
|
|
const arrayBuffer = await response.arrayBuffer()
|
|
|
|
|
|
const buffer = Buffer.from(arrayBuffer)
|
2025-08-19 14:33:59 +08:00
|
|
|
|
|
2025-09-04 16:58:42 +08:00
|
|
|
|
// 验证下载的数据是否有效
|
|
|
|
|
|
if (buffer.length === 0) {
|
|
|
|
|
|
throw new Error('下载的文件为空')
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// 将图片数据写入本地文件
|
|
|
|
|
|
await fspromises.writeFile(localPath, buffer)
|
|
|
|
|
|
|
|
|
|
|
|
console.log(`图片下载成功: ${localPath} (大小: ${(buffer.length / 1024).toFixed(2)} KB)`)
|
|
|
|
|
|
return localPath
|
|
|
|
|
|
} catch (error) {
|
|
|
|
|
|
lastError = error instanceof Error ? error : new Error('未知错误')
|
|
|
|
|
|
|
|
|
|
|
|
console.error(`第${attempt}次下载失败:`, lastError.message)
|
|
|
|
|
|
|
|
|
|
|
|
// 如果不是最后一次尝试,等待一段时间再重试
|
|
|
|
|
|
if (attempt < maxRetries) {
|
|
|
|
|
|
const waitTime = Math.min(1000 * Math.pow(2, attempt - 1), 5000) // 指数退避,最大5秒
|
|
|
|
|
|
console.log(`等待 ${waitTime}ms 后重试...`)
|
|
|
|
|
|
await new Promise((resolve) => setTimeout(resolve, waitTime))
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw lastError
|
|
|
|
|
|
}
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2025-09-04 16:58:42 +08:00
|
|
|
|
|
|
|
|
|
|
// 所有重试都失败了,抛出最后一个错误
|
|
|
|
|
|
const errorMessage = lastError?.message || '未知错误'
|
|
|
|
|
|
|
|
|
|
|
|
if (errorMessage.includes('timeout') || errorMessage.includes('Timeout')) {
|
|
|
|
|
|
throw new Error(`下载图片超时 (${timeout / 1000}秒),已重试${maxRetries}次: ${errorMessage}`)
|
|
|
|
|
|
} else if (errorMessage.includes('ENOTFOUND') || errorMessage.includes('ECONNREFUSED')) {
|
|
|
|
|
|
throw new Error(`网络连接失败,无法访问图片地址,已重试${maxRetries}次: ${errorMessage}`)
|
|
|
|
|
|
} else if (errorMessage.includes('Connect Timeout Error')) {
|
|
|
|
|
|
throw new Error(`连接超时,服务器响应缓慢,已重试${maxRetries}次: ${errorMessage}`)
|
|
|
|
|
|
} else {
|
|
|
|
|
|
throw new Error(`下载图片失败,已重试${maxRetries}次: ${errorMessage}`)
|
|
|
|
|
|
}
|
2025-08-19 14:33:59 +08:00
|
|
|
|
}
|