[转]所有lib文件名写入TXT方法(CMD命令dir)

本文转载了两篇技术文章,分别来自优快云和CNBlog平台,详细内容请访问原文链接。
<think>我们使用Multer处理文件上传,但需要在文件写入磁盘前进行压缩和换。 由于Multer的diskStorage会将文件写入磁盘,而我们要避免写入原始文件,直接在内存中处理并保存为WebP格式。 因此,我们可以使用自定义存储引擎,在_handleFile方法中处理文件流,将其通过sharp换后直接写入目标文件(WebP格式)。 这样,原始文件不会保存到磁盘,只有换后的WebP文件被保存。 步骤: 1. 创建自定义存储引擎,继承multer的StorageEngine。 2. 在_handleFile方法中,获取上传的文件流,通过sharp进行换,然后写入到目标文件。 3. 在换完成后,调用回调函数返回文件信息(包括新的文件名、路径等)。 4. 同时,我们需要处理错误和清理部分写入文件。 注意:由于我们不在磁盘上保存原始文件,所以不需要destination和filename函数,但我们需要生成目标文件名(如使用uuid)和确定输出目录。 我们将创建一个类`WebPStorage`,它实现`_handleFile`和`_removeFile`方法。 代码实现如下:</think># Node.js + Multer:在文件写入磁盘前进行图片压缩和 WebP 换 下面我将提供一个完整的解决方案,使用 Multer 的自定义存储引擎,在文件写入磁盘前直接进行图片压缩和 WebP 换。这种方法避免了中间文件,提高了处理效率并减少了磁盘 I/O。 ## 完整解决方案 ```javascript const express = require('express'); const multer = require('multer'); const sharp = require('sharp'); const path = require('path'); const { v4: uuidv4 } = require('uuid'); const fs = require('fs').promises; const stream = require('stream'); const os = require('os'); const cluster = require('cluster'); const numCPUs = require('os').cpus().length; // 创建输出目录 const OUTPUT_DIR = path.join(__dirname, 'processed-images'); fs.mkdir(OUTPUT_DIR, { recursive: true }).catch(console.error); // 自定义存储引擎:在写入磁盘前处理图片 class WebPStorage { constructor(options = {}) { this.options = { maxWidth: 1920, quality: 85, ...options }; } _handleFile(req, file, cb) { // 生成唯一文件名 const filename = `${uuidv4()}-${Date.now()}.webp`; const outputPath = path.join(OUTPUT_DIR, filename); // 创建换流 const transform = sharp() .resize(this.options.maxWidth, null, { withoutEnlargement: true, fit: 'inside' }) .rotate() .webp({ quality: this.options.quality, alphaQuality: 90, smartSubsample: true }); // 创建写入流 const outStream = fs.createWriteStream(outputPath); // 错误处理函数 const handleError = (err) => { // 清理部分写入文件 fs.unlink(outputPath).catch(() => {}); cb(err); }; // 文件头验证 let headerChecked = false; const headerValidator = new stream.PassThrough(); headerValidator.on('data', (chunk) => { if (!headerChecked && chunk.length >= 12) { const header = chunk.slice(0, 12).toString('hex'); const validHeaders = [ 'ffd8ff', // JPEG '89504e47', // PNG '47494638', // GIF '52494646', // WebP '49492a00', // TIFF '4d4d002a' // TIFF (big-endian) ]; if (!validHeaders.some(h => header.startsWith(h))) { return handleError(new Error('无效的图片格式')); } headerChecked = true; } }); // 管道处理 file.stream .on('error', handleError) .pipe(headerValidator) .on('error', handleError) .pipe(transform) .on('error', handleError) .pipe(outStream) .on('error', handleError) .on('finish', () => { cb(null, { destination: OUTPUT_DIR, filename: filename, path: outputPath, size: outStream.bytesWritten, mimetype: 'image/webp' }); }); } _removeFile(req, file, cb) { // 删除文件 fs.unlink(file.path) .then(() => cb(null)) .catch(cb); } } // 文件类型过滤器 const fileFilter = (req, file, cb) => { const allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp', 'image/tiff']; if (allowedTypes.includes(file.mimetype)) { cb(null, true); } else { cb(new Error('只允许上传图片文件 (JPEG, PNG, GIF, WebP, TIFF)'), false); } }; // 初始化Multer使用自定义存储引擎 const upload = multer({ storage: new WebPStorage({ maxWidth: 1920, quality: 85 }), fileFilter, limits: { fileSize: 50 * 1024 * 1024, // 50MB files: 10 // 最多10个文件 } }); // 创建Express应用 const createApp = () => { const app = express(); // 上传路由 app.post('/upload', upload.single('image'), (req, res) => { try { if (!req.file) { return res.status(400).json({ error: '未提供图片文件' }); } res.json({ success: true, message: '图片压缩和换成功', file: { originalname: req.file.originalname, filename: req.file.filename, path: req.file.path, size: req.file.size, url: `/processed/${req.file.filename}` } }); } catch (error) { console.error('上传处理错误:', error); res.status(500).json({ error: '图片处理失败', details: error.message }); } }); // 多文件上传路由 app.post('/upload-multiple', upload.array('images', 10), (req, res) => { try { if (!req.files || req.files.length === 0) { return res.status(400).json({ error: '未提供图片文件' }); } res.json({ success: true, message: `${req.files.length} 张图片处理成功`, files: req.files.map(file => ({ originalname: file.originalname, filename: file.filename, size: file.size, url: `/processed/${file.filename}` })) }); } catch (error) { console.error('多文件处理错误:', error); res.status(500).json({ error: '图片处理失败', details: error.message }); } }); // 提供处理后的图片访问 app.use('/processed', express.static(OUTPUT_DIR)); // 错误处理中间件 app.use((err, req, res, next) => { if (err instanceof multer.MulterError) { // Multer错误处理 let message = '文件上传错误'; if (err.code === 'LIMIT_FILE_SIZE') { message = `文件过大,最大允许 ${err.field} 为 50MB`; } else if (err.code === 'LIMIT_FILE_COUNT') { message = '一次最多上传10个文件'; } return res.status(400).json({ error: message }); } else if (err) { // 其他错误 return res.status(500).json({ error: err.message || '服务器内部错误' }); } next(); }); // 健康检查端点 app.get('/health', (req, res) => { res.json({ status: 'ok', memory: process.memoryUsage(), uptime: process.uptime() }); }); return app; }; // 启动服务器 if (cluster.isMaster) { console.log(`主进程 ${process.pid} 正在运行`); // 创建工作进程 for (let i = 0; i < Math.min(numCPUs, 4); i++) { cluster.fork(); } cluster.on('exit', (worker, code, signal) => { console.log(`工作进程 ${worker.process.pid} 已退出`); // 自动重启 cluster.fork(); }); } else { const app = createApp(); const PORT = process.env.PORT || 3000; app.listen(PORT, () => { console.log(`工作进程 ${process.pid} 启动: http://localhost:${PORT}`); }); } ``` ## 关键实现原理 ### 1. 自定义存储引擎 核心是创建一个自定义的 `WebPStorage` 类,继承自 Multer 的存储引擎: ```javascript class WebPStorage { _handleFile(req, file, cb) { // 1. 创建换流 const transform = sharp() .resize(maxWidth) .webp({ quality }); // 2. 创建写入流 const outStream = fs.createWriteStream(outputPath); // 3. 连接文件流、换流和输出流 file.stream .pipe(transform) .pipe(outStream) .on('finish', () => { cb(null, fileInfo); }); } } ``` ### 2. 流式处理流程 1. **文件输入流**:Multer 提供的文件流 2. **换流**:Sharp 创建的图片处理流 3. **输出流**:写入最终 WebP 文件的流 这种流式处理方式: - 避免内存溢出(大文件处理) - 减少磁盘 I/O(无需临时文件) - 提高处理效率(并行处理) ### 3. 文件头验证 在换前验证文件头,确保是有效的图片格式: ```javascript const headerValidator = new stream.PassThrough(); headerValidator.on('data', (chunk) => { if (chunk.length >= 12) { const header = chunk.slice(0, 12).toString('hex'); const validHeaders = [ /* 支持的图片格式头 */ ]; if (!validHeaders.some(h => header.startsWith(h))) { handleError(new Error('无效的图片格式')); } } }); file.stream.pipe(headerValidator).pipe(transform); ``` ## 高级功能 ### 1. 动态处理选项 支持基于请求参数的动态处理: ```javascript // 在路由中 app.post('/upload-dynamic', (req, res) => { const maxWidth = parseInt(req.query.width) || 1920; const quality = parseInt(req.query.quality) || 85; const upload = multer({ storage: new WebPStorage({ maxWidth, quality }), fileFilter, limits: { fileSize: 50 * 1024 * 1024 } }).single('image'); upload(req, res, (err) => { // 处理结果 }); }); ``` ### 2. 内容感知裁剪 ```javascript const transform = sharp() .resize({ width: 800, height: 600, fit: 'cover', position: 'attention' // 内容感知裁剪 }) .webp({ quality: 85 }); ``` ### 3. 渐进式加载 ```javascript const transform = sharp() .webp({ quality: 85, progressive: true // 渐进式加载 }); ``` ## 内存管理优化 ### 1. 流控机制 ```javascript let bytesProcessed = 0; const MAX_MEMORY = 100 * 1024 * 1024; // 100MB file.stream .on('data', (chunk) => { bytesProcessed += chunk.length; if (bytesProcessed > MAX_MEMORY) { file.stream.pause(); transform.on('drain', () => file.stream.resume()); } }) .pipe(transform); ``` ### 2. 集群模式 ```javascript if (cluster.isMaster) { // 创建多个工作进程 for (let i = 0; i < Math.min(numCPUs, 4); i++) { cluster.fork(); } cluster.on('exit', (worker) => { // 自动重启 cluster.fork(); }); } else { // 工作进程启动服务器 app.listen(PORT); } ``` ### 3. 内存监控 ```javascript setInterval(() => { const memory = process.memoryUsage(); if (memory.rss > 500 * 1024 * 1024) { console.warn('高内存使用警告! 考虑优化图片处理流程'); } }, 10000); ``` ## 安全增强 ### 1. 深度文件验证 ```javascript // 在换流中添加验证 transform.on('info', (info) => { if (info.width > 10000 || info.height > 10000) { handleError(new Error('图片尺寸过大')); } if (info.size > 50 * 1024 * 1024) { handleError(new Error('图片文件大小超过限制')); } }); ``` ### 2. 文件名消毒 ```javascript function sanitizeFilename(name) { return name .replace(/[^a-z0-9_.-]/gi, '_') // 替换特殊字符 .replace(/\.\./g, '') // 防止目录遍历 .substring(0, 100); // 限制长度 } ``` ## 性能优化 ### 1. 并行处理多个换 ```javascript // 创建多个换流 const mainTransform = sharp().webp({ quality: 85 }); const thumbnailTransform = sharp() .resize(300, 300) .webp({ quality: 70 }); // 分流处理 const mainStream = file.stream.pipe(mainTransform); const thumbStream = file.stream.pipe(thumbnailTransform); // 合并写入 const mainWrite = mainStream.pipe(fs.createWriteStream(mainPath)); const thumbWrite = thumbStream.pipe(fs.createWriteStream(thumbPath)); // 等待所有写入完成 await Promise.all([ new Promise(resolve => mainWrite.on('finish', resolve)), new Promise(resolve => thumbWrite.on('finish', resolve)) ]); ``` ### 2. 工作线程池 ```javascript const { Worker } = require('worker_threads'); async function processInWorker(fileStream, options) { return new Promise((resolve, reject) => { const worker = new Worker('./image-worker.js', { workerData: { options } }); // 将文件流传输到工作线程 fileStream.pipe(worker.stdin); worker.stdout.on('data', (data) => { resolve(data); }); worker.on('error', reject); worker.on('exit', (code) => { if (code !== 0) reject(new Error(`Worker stopped with exit code ${code}`)); }); }); } // image-worker.js const { parentPort, workerData } = require('worker_threads'); const sharp = require('sharp'); const stream = require('stream'); // 创建换流 const transform = sharp() .resize(workerData.options.maxWidth) .webp({ quality: workerData.options.quality }); // 处理输入流 process.stdin .pipe(transform) .pipe(process.stdout); ``` ## 部署建议 ### 1. Docker 容器化 ```Dockerfile FROM node:18-slim # 安装必要的依赖 RUN apt-get update && apt-get install -y \ libvips-dev \ && rm -rf /var/lib/apt/lists/* WORKDIR /app COPY package*.json ./ RUN npm install --production COPY . . # 创建存储卷 RUN mkdir -p /app/processed-images VOLUME /app/processed-images EXPOSE 3000 CMD ["node", "app.js"] ``` ### 2. Nginx 反向代理配置 ```nginx server { listen 80; server_name images.example.com; client_max_body_size 50M; location / { proxy_pass http://localhost:3000; proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; } location /processed/ { alias /app/processed-images/; expires 1y; add_header Cache-Control "public"; access_log off; } } ``` ## 解决方案总结 这个实现方案具有以下优势: 1. **零中间文件**:原始文件不会写入磁盘,直接处理为 WebP 格式 2. **流式处理**:使用 Node.js 流实现高效处理,避免内存溢出 3. **高性能**:Sharp 库基于 Libvips,处理速度极快 4. **安全可靠**:包含文件头验证、尺寸检查和错误处理 5. **可扩展**:支持集群模式和工作线程池 6. **生产就绪**:包含健康检查、内存监控和自动重启 核心功能: - 在文件写入磁盘前完成所有处理 - 支持动态调整压缩参数 - 提供单文件和多文件上传 - 包含全面的错误处理 - 优化内存和 CPU 使用
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值