本篇内容主要讲解“怎么实现图片上传写入磁盘的接口”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“怎么实现图片上传写入磁盘的接口”吧!
一:开启 Node.js 服务
开启一个 Node.js 服务,指定路由 /upload/image 收到请求后调用 uploadImageHandler 方法,传入 Request 对象。
const http = require('http'); const formidable = require('formidable'); const fs = require('fs'); const fsPromises = fs.promises; const path = require('path'); const PORT = process.env.PORT || 3000; const server = http.createServer(async (req, res) => { if (req.url === '/upload/image' && req.method.toLocaleLowerCase() === 'post') { uploadImageHandler(req, res); } else { res.setHeader('statusCode', 404); res.end('Not found!') } }); server.listen(PORT, () => { console.log(`server is listening at ${server.address().port}`); });
二:处理图片对象
formidable 是一个用来处理上传文件、图片等数据的 NPM 模块,form.parse 是一个 callback 转化为 Promise 便于处理。
Tips:拼接路径时使用 path 模块的 join 方法,它会将我们传入的多个路径参数拼接起来,因为 Linux、Windows 等不同的系统使用的符号是不同的,该方法会根据系统自行转换处理。
const uploadImageHandler = async (req, res) => { const form = new formidable.IncomingForm({ multiples: true }); form.encoding = 'utf-8'; form.maxFieldsSize = 1024 * 5; form.keepExtensions = true; try { const { file } = await new Promise((resolve, reject) => { form.parse(req, (err, fields, file) => { if (err) { return reject(err); } return resolve({ fields, file }); }); }); const { name: filename, path: sourcePath } = file.img; const destPath = path.join(__dirname, filename); console.log(`sourcePath: ${sourcePath}. destPath: ${destPath}`); await mv(sourcePath, destPath); console.log(`File ${filename} write success.`); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ code: 'SUCCESS', message: `Upload success.`})); } catch (err) { console.error(`Move file failed with message: ${err.message}`); res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ code: 'ERROR', message: `${err.message}`})); } }
三:实现 mv 方法
fs.rename 重命名文件
将上传的图片写入本地目标路径一种简单的方法是使用 fs 模块的 rename(sourcePath, destPath) 方法,该方法会异步的对 sourcePath 文件做重命名操作,使用如下所示:
const mv = async (sourcePath, destPath) => { return fsPromises.rename(sourcePath, destPath); };
cross-device link not permitted
在使用 fs.rename() 时还要注意 cross-device link not permitted 错误,参考 rename(2) — Linux manual page:
**EXDEV **oldpath and newpath are not on the same mounted filesystem. (Linux permits a filesystem to be mounted at multiple points, but rename() does not work across different mount points, even if the same filesystem is mounted on both.)
oldPath 和 newPath 不在同一挂载的文件系统上。(Linux 允许一个文件系统挂载到多个点,但是 rename() 无法跨不同的挂载点进行工作,即使相同的文件系统被挂载在两个挂载点上。)
在 Windows 系统同样会遇到此问题,参考 http://errorco.de/win32/winerror-h/error_not_same_device/0x80070011/
winerror.h 0x80070011 #define ERROR_NOT_SAME_DEVICE The system cannot move the file to a different disk drive.(系统无法移动文件到不同的磁盘驱动器。)
此处在 Windows 做下复现,因为在使用 formidable 上传文件时默认的目录是操作系统的默认目录 os.tmpdir(),在我的电脑上对应的是 C 盘下,当我使用 fs.rename() 将其重名为 F 盘时,就出现了以下报错:
C:\Users\ADMINI~1\AppData\Local\Temp\upload_3cc33e9403930347b89ea47e4045b940 F:\study\test\202366 [Error: EXDEV: cross-device link not permitted, rename 'C:\Users\ADMINI~1\AppData\Local\Temp\upload_3cc33e9403930347b89ea47e4045b940' -> 'F:\study\test\202366'] { errno: -4037, code: 'EXDEV', syscall: 'rename', path: 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\upload_3cc33e9403930347b89ea47e4045b940', dest: 'F:\\study\\test\\202366' }
设置源路径与目标路径在同一磁盘分区
设置上传文件中间件的临时路径为最终写入文件的磁盘分区,例如我们在 Windows 测试时将图片保存在 F 盘下,所以设置 formidable 的 form 对象的 uploadDir 属性为 F 盘,如下所示:
const form = new formidable.IncomingForm({ multiples: true }); form.uploadDir = 'F:\\' form.parse(req, (err, fields, file) => { ... });
这种方式有一定局限性,如果写入的位置位于不同的磁盘空间该怎么办呢?
可以看下下面的这种方式。
读取-写入-删除临时文件
一种可行的办法是读取临时文件写入到新的位置,最后在删除临时文件。所以下述代码创建了可读流与可写流对象,使用 pipe 以管道的方式将数据写入新的位置,最后调用 fs 模块的 unlink 方法删除临时文件。
const mv = async (sourcePath, destPath) => { try { await fsPromises.rename(sourcePath, destPath); } catch (error) { if (error.code === 'EXDEV') { const readStream = fs.createReadStream(sourcePath); const writeStream = fs.createWriteStream(destPath); return new Promise((resolve, reject) => { readStream.pipe(writeStream); readStream.on('end', onClose); readStream.on('error', onError); async function onClose() { await fsPromises.unlink(sourcePath); resolve(); } function onError(err) { console.error(`File write failed with message: ${err.message}`); writeStream.close(); reject(err) } }) } throw error; } }
四:测试
方式一:终端调用
curl --location --request POST 'localhost:3000/upload/image' \ --form 'img=@/Users/Downloads/五月君.jpeg'
方式二:POSTMAN 调用
到此,相信大家对“怎么实现图片上传写入磁盘的接口”有了更深的了解,不妨来实际操作一番吧!这里是编程网网站,更多相关内容可以进入相关频道进行查询,关注我们,继续学习!