Skip to main content
Glama
file.js14.7 kB
/** * 文件工具模块 * 提供文件读写、路径处理、压缩解压等功能 */ import fs from 'fs/promises'; import path from 'path'; import { createReadStream, createWriteStream } from 'fs'; import { createGzip, createGunzip } from 'zlib'; import { pipeline } from 'stream/promises'; import { logger } from './logger.js'; /** * 文件工具类 */ export class FileUtils { constructor() { this.defaultEncoding = 'utf8'; this.defaultJsonIndent = 2; } /** * 检查文件是否存在 * @param {string} filePath - 文件路径 * @returns {Promise<boolean>} 是否存在 */ static async exists(filePath) { try { await fs.access(filePath); return true; } catch { return false; } } /** * 检查是否是文件 * @param {string} filePath - 文件路径 * @returns {Promise<boolean>} 是否是文件 */ static async isFile(filePath) { try { const stats = await fs.stat(filePath); return stats.isFile(); } catch { return false; } } /** * 检查是否是目录 * @param {string} dirPath - 目录路径 * @returns {Promise<boolean>} 是否是目录 */ static async isDirectory(dirPath) { try { const stats = await fs.stat(dirPath); return stats.isDirectory(); } catch { return false; } } /** * 读取文件内容 * @param {string} filePath - 文件路径 * @param {string} encoding - 编码格式 * @returns {Promise<string>} 文件内容 */ static async read(filePath, encoding = 'utf8') { try { const content = await fs.readFile(filePath, encoding); logger.debug(`文件读取成功: ${filePath}`); return content; } catch (error) { logger.error(`文件读取失败: ${filePath}`, error); throw error; } } /** * 写入文件内容 * @param {string} filePath - 文件路径 * @param {string|Buffer} content - 文件内容 * @param {string} encoding - 编码格式 * @returns {Promise<void>} */ static async write(filePath, content, encoding = 'utf8') { try { // 确保目录存在 await this.ensureDir(path.dirname(filePath)); await fs.writeFile(filePath, content, encoding); logger.debug(`文件写入成功: ${filePath}`); } catch (error) { logger.error(`文件写入失败: ${filePath}`, error); throw error; } } /** * 追加文件内容 * @param {string} filePath - 文件路径 * @param {string} content - 追加内容 * @param {string} encoding - 编码格式 * @returns {Promise<void>} */ static async append(filePath, content, encoding = 'utf8') { try { await fs.appendFile(filePath, content, encoding); logger.debug(`文件追加成功: ${filePath}`); } catch (error) { logger.error(`文件追加失败: ${filePath}`, error); throw error; } } /** * 读取JSON文件 * @param {string} filePath - JSON文件路径 * @returns {Promise<Object>} JSON对象 */ static async readJson(filePath) { try { const content = await this.read(filePath, 'utf8'); return JSON.parse(content); } catch (error) { logger.error(`JSON文件读取失败: ${filePath}`, error); throw error; } } /** * 写入JSON文件 * @param {string} filePath - JSON文件路径 * @param {Object} data - JSON数据 * @param {number} indent - 缩进空格数 * @returns {Promise<void>} */ static async writeJson(filePath, data, indent = 2) { try { const jsonString = JSON.stringify(data, null, indent); await this.write(filePath, jsonString, 'utf8'); logger.debug(`JSON文件写入成功: ${filePath}`); } catch (error) { logger.error(`JSON文件写入失败: ${filePath}`, error); throw error; } } /** * 复制文件 * @param {string} sourcePath - 源文件路径 * @param {string} targetPath - 目标文件路径 * @returns {Promise<void>} */ static async copy(sourcePath, targetPath) { try { // 确保目标目录存在 await this.ensureDir(path.dirname(targetPath)); await fs.copyFile(sourcePath, targetPath); logger.debug(`文件复制成功: ${sourcePath} -> ${targetPath}`); } catch (error) { logger.error(`文件复制失败: ${sourcePath} -> ${targetPath}`, error); throw error; } } /** * 移动文件 * @param {string} sourcePath - 源文件路径 * @param {string} targetPath - 目标文件路径 * @returns {Promise<void>} */ static async move(sourcePath, targetPath) { try { // 确保目标目录存在 await this.ensureDir(path.dirname(targetPath)); await fs.rename(sourcePath, targetPath); logger.debug(`文件移动成功: ${sourcePath} -> ${targetPath}`); } catch (error) { logger.error(`文件移动失败: ${sourcePath} -> ${targetPath}`, error); throw error; } } /** * 删除文件 * @param {string} filePath - 文件路径 * @returns {Promise<void>} */ static async delete(filePath) { try { await fs.unlink(filePath); logger.debug(`文件删除成功: ${filePath}`); } catch (error) { logger.error(`文件删除失败: ${filePath}`, error); throw error; } } /** * 创建目录 * @param {string} dirPath - 目录路径 * @param {Object} options - 选项 * @returns {Promise<void>} */ static async ensureDir(dirPath, options = { recursive: true }) { try { await fs.mkdir(dirPath, options); logger.debug(`目录创建成功: ${dirPath}`); } catch (error) { if (error.code !== 'EEXIST') { logger.error(`目录创建失败: ${dirPath}`, error); throw error; } } } /** * 删除目录 * @param {string} dirPath - 目录路径 * @param {Object} options - 选项 * @returns {Promise<void>} */ static async removeDir(dirPath, options = { recursive: true }) { try { await fs.rmdir(dirPath, options); logger.debug(`目录删除成功: ${dirPath}`); } catch (error) { logger.error(`目录删除失败: ${dirPath}`, error); throw error; } } /** * 读取目录内容 * @param {string} dirPath - 目录路径 * @param {Object} options - 选项 * @returns {Promise<Array>} 文件和目录列表 */ static async readDir(dirPath, options = {}) { try { const items = await fs.readdir(dirPath, options); logger.debug(`目录读取成功: ${dirPath}`); return items; } catch (error) { logger.error(`目录读取失败: ${dirPath}`, error); throw error; } } /** * 获取文件信息 * @param {string} filePath - 文件路径 * @returns {Promise<Object>} 文件信息 */ static async stat(filePath) { try { const stats = await fs.stat(filePath); logger.debug(`文件信息获取成功: ${filePath}`); return stats; } catch (error) { logger.error(`文件信息获取失败: ${filePath}`, error); throw error; } } /** * 获取文件大小 * @param {string} filePath - 文件路径 * @returns {Promise<number>} 文件大小(字节) */ static async getFileSize(filePath) { try { const stats = await this.stat(filePath); return stats.size; } catch { return 0; } } /** * 压缩文件 * @param {string} sourcePath - 源文件路径 * @param {string} targetPath - 目标文件路径 * @returns {Promise<void>} */ static async compress(sourcePath, targetPath) { try { const source = createReadStream(sourcePath); const target = createWriteStream(targetPath); const gzip = createGzip(); await pipeline(source, gzip, target); logger.debug(`文件压缩成功: ${sourcePath} -> ${targetPath}`); } catch (error) { logger.error(`文件压缩失败: ${sourcePath} -> ${targetPath}`, error); throw error; } } /** * 解压文件 * @param {string} sourcePath - 压缩文件路径 * @param {string} targetPath - 目标文件路径 * @returns {Promise<void>} */ static async decompress(sourcePath, targetPath) { try { const source = createReadStream(sourcePath); const target = createWriteStream(targetPath); const gunzip = createGunzip(); await pipeline(source, gunzip, target); logger.debug(`文件解压成功: ${sourcePath} -> ${targetPath}`); } catch (error) { logger.error(`文件解压失败: ${sourcePath} -> ${targetPath}`, error); throw error; } } /** * 获取文件扩展名 * @param {string} filePath - 文件路径 * @returns {string} 扩展名 */ static getExtension(filePath) { return path.extname(filePath); } /** * 获取文件名(不含扩展名) * @param {string} filePath - 文件路径 * @returns {string} 文件名 */ static getBaseName(filePath) { return path.basename(filePath, path.extname(filePath)); } /** * 获取文件名(含扩展名) * @param {string} filePath - 文件路径 * @returns {string} 文件名 */ static getFileName(filePath) { return path.basename(filePath); } /** * 获取目录路径 * @param {string} filePath - 文件路径 * @returns {string} 目录路径 */ static getDirName(filePath) { return path.dirname(filePath); } /** * 拼接路径 * @param {...string} paths - 路径片段 * @returns {string} 完整路径 */ static joinPath(...paths) { return path.join(...paths); } /** * 解析路径 * @param {string} filePath - 文件路径 * @returns {Object} 路径信息 */ static parsePath(filePath) { return path.parse(filePath); } /** * 获取绝对路径 * @param {string} filePath - 文件路径 * @returns {string} 绝对路径 */ static resolvePath(filePath) { return path.resolve(filePath); } /** * 获取相对路径 * @param {string} from - 起始路径 * @param {string} to - 目标路径 * @returns {string} 相对路径 */ static relativePath(from, to) { return path.relative(from, to); } /** * 批量读取JSON文件 * @param {Array<string>} filePaths - JSON文件路径数组 * @returns {Promise<Object>} 文件内容对象 */ static async readMultipleJson(filePaths) { const results = {}; for (const filePath of filePaths) { try { const data = await this.readJson(filePath); const fileName = this.getBaseName(filePath); results[fileName] = data; } catch (error) { logger.error(`批量读取JSON失败: ${filePath}`, error); } } return results; } /** * 批量写入JSON文件 * @param {Object} fileData - 文件数据对象 {文件名: 数据} * @param {string} outputDir - 输出目录 * @returns {Promise<void>} */ static async writeMultipleJson(fileData, outputDir) { for (const [fileName, data] of Object.entries(fileData)) { try { const filePath = this.joinPath(outputDir, `${fileName}.json`); await this.writeJson(filePath, data); } catch (error) { logger.error(`批量写入JSON失败: ${fileName}`, error); } } } /** * 监控文件变化 * @param {string} filePath - 文件路径 * @param {Function} callback - 变化回调函数 * @param {Object} options - 监控选项 * @returns {Object} 监控器对象 */ static watchFile(filePath, callback, options = {}) { try { const watcher = fs.watch(filePath, options, (eventType, filename) => { logger.debug(`文件变化: ${eventType} - ${filename}`); callback(eventType, filename); }); logger.debug(`开始监控文件: ${filePath}`); return watcher; } catch (error) { logger.error(`文件监控失败: ${filePath}`, error); throw error; } } /** * 获取文件MD5哈希 * @param {string} filePath - 文件路径 * @returns {Promise<string>} MD5哈希值 */ static async getFileHash(filePath) { try { const crypto = await import('crypto'); const hash = crypto.createHash('md5'); const stream = createReadStream(filePath); return new Promise((resolve, reject) => { stream.on('data', chunk => hash.update(chunk)); stream.on('end', () => resolve(hash.digest('hex'))); stream.on('error', reject); }); } catch (error) { logger.error(`文件哈希计算失败: ${filePath}`, error); throw error; } } /** * 清理临时文件 * @param {string} tempDir - 临时目录 * @param {number} maxAge - 最大存活时间(毫秒) * @returns {Promise<void>} */ static async cleanTempFiles(tempDir, maxAge = 24 * 60 * 60 * 1000) { try { if (!(await this.exists(tempDir))) { return; } const items = await this.readDir(tempDir); const now = Date.now(); for (const item of items) { const itemPath = this.joinPath(tempDir, item); const stats = await this.stat(itemPath); if (now - stats.mtime.getTime() > maxAge) { if (stats.isFile()) { await this.delete(itemPath); } else if (stats.isDirectory()) { await this.removeDir(itemPath); } } } logger.debug(`临时文件清理完成: ${tempDir}`); } catch (error) { logger.error(`临时文件清理失败: ${tempDir}`, error); } } } /** * 快捷函数 */ export const readFile = FileUtils.read; export const writeFile = FileUtils.write; export const readJsonFile = FileUtils.readJson; export const writeJsonFile = FileUtils.writeJson; export const ensureDirectory = FileUtils.ensureDir; export const copyFile = FileUtils.copy; export const moveFile = FileUtils.move; export const deleteFile = FileUtils.delete; export const getFileExtension = FileUtils.getExtension; export const getBaseName = FileUtils.getBaseName; export const getFileName = FileUtils.getFileName; export const getDirectoryName = FileUtils.getDirName; export const joinPath = FileUtils.joinPath; export const resolvePath = FileUtils.resolvePath; export default { FileUtils, readFile, writeFile, readJsonFile, writeJsonFile, ensureDirectory, copyFile, moveFile, deleteFile, getFileExtension, getBaseName, getFileName, getDirectoryName, joinPath, resolvePath };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/billyangbc/xiaohongshu-mcp-nodejs'

If you have feedback or need assistance with the MCP directory API, please join our Discord server