文章详情

短信预约-IT技能 免费直播动态提醒

请输入下面的图形验证码

提交验证

短信预约提醒成功

Node.js 小知识 — 实现图片上传写入磁盘的接口

2024-12-03 14:16

关注

一:开启 Node.js 服务

开启一个 Node.js 服务,指定路由 /upload/image 收到请求后调用 uploadImageHandler 方法,传入 Request 对象。

  1. const http = require('http'); 
  2. const formidable = require('formidable'); 
  3. const fs = require('fs'); 
  4. const fsPromises = fs.promises; 
  5. const path = require('path'); 
  6. const PORT = process.env.PORT || 3000; 
  7. const server = http.createServer(async (req, res) => { 
  8.   if (req.url === '/upload/image' &&  req.method.toLocaleLowerCase() === 'post') { 
  9.     uploadImageHandler(req, res); 
  10.   } else { 
  11.    res.setHeader('statusCode', 404); 
  12.    res.end('Not found!'
  13.   } 
  14. }); 
  15. server.listen(PORT, () => { 
  16.   console.log(`server is listening at ${server.address().port}`); 
  17. }); 

二:处理图片对象

formidable 是一个用来处理上传文件、图片等数据的 NPM 模块,form.parse 是一个 callback 转化为 Promise 便于处理。

Tips:拼接路径时使用 path 模块的 join 方法,它会将我们传入的多个路径参数拼接起来,因为 Linux、Windows 等不同的系统使用的符号是不同的,该方法会根据系统自行转换处理。

  1. const uploadImageHandler = async (req, res) => { 
  2.   const form = new formidable.IncomingForm({ multiples: true });   
  3.   form.encoding = 'utf-8';   
  4.   form.maxFieldsSize = 1024 * 5;   
  5.   form.keepExtensions = true
  6.  
  7.   try { 
  8.     const { file } = await new Promise((resolve, reject) => {   
  9.       form.parse(req, (err, fields, file) => {   
  10.         if (err) {   
  11.           return reject(err);   
  12.         } 
  13.  
  14.          return resolve({ fields, file });   
  15.       });   
  16.     }); 
  17.     const { name: filename, path: sourcePath } = file.img; 
  18.     const destPath = path.join(__dirname, filename); 
  19.     console.log(`sourcePath: ${sourcePath}. destPath: ${destPath}`); 
  20.     await mv(sourcePath, destPath); 
  21.     console.log(`File ${filename} write success.`); 
  22.     res.writeHead(200, { 'Content-Type''application/json' }); 
  23.     res.end(JSON.stringify({ code: 'SUCCESS', message: `Upload success.`})); 
  24.   } catch (err) { 
  25.     console.error(`Move file failed with message: ${err.message}`); 
  26.     res.writeHead(200, { 'Content-Type''application/json' }); 
  27.     res.end(JSON.stringify({ code: 'ERROR', message: `${err.message}`})); 
  28.   } 

三:实现 mv 方法

fs.rename 重命名文件

将上传的图片写入本地目标路径一种简单的方法是使用 fs 模块的 rename(sourcePath, destPath) 方法,该方法会异步的对 sourcePath 文件做重命名操作,使用如下所示:

  1. const mv = async (sourcePath, destPath) => { 
  2.  return fsPromises.rename(sourcePath, destPath); 
  3. }; 

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 盘时,就出现了以下报错:

  1. C:\Users\ADMINI~1\AppData\Local\Temp\upload_3cc33e9403930347b89ea47e4045b940 F:\study\test\202366 
  2. [Error: EXDEV: cross-device link not permitted, rename 'C:\Users\ADMINI~1\AppData\Local\Temp\upload_3cc33e9403930347b89ea47e4045b940' -> 'F:\study\test\202366'] { 
  3.   errno: -4037, 
  4.   code: 'EXDEV'
  5.   syscall: 'rename'
  6.   path: 'C:\\Users\\ADMINI~1\\AppData\\Local\\Temp\\upload_3cc33e9403930347b89ea47e4045b940'
  7.   dest: 'F:\\study\\test\\202366' 

设置源路径与目标路径在同一磁盘分区

设置上传文件中间件的临时路径为最终写入文件的磁盘分区,例如我们在 Windows 测试时将图片保存在 F 盘下,所以设置 formidable 的 form 对象的 uploadDir 属性为 F 盘,如下所示:

  1. const form = new formidable.IncomingForm({ multiples: true });   
  2. form.uploadDir = 'F:\\' 
  3. form.parse(req, (err, fields, file) => {   
  4.   ... 
  5. }); 

这种方式有一定局限性,如果写入的位置位于不同的磁盘空间该怎么办呢?

可以看下下面的这种方式。

读取-写入-删除临时文件

一种可行的办法是读取临时文件写入到新的位置,最后在删除临时文件。所以下述代码创建了可读流与可写流对象,使用 pipe 以管道的方式将数据写入新的位置,最后调用 fs 模块的 unlink 方法删除临时文件。

  1. const mv = async (sourcePath, destPath) => { 
  2.   try { 
  3.     await fsPromises.rename(sourcePath, destPath); 
  4.   } catch (error) { 
  5.     if (error.code === 'EXDEV') { 
  6.       const readStream = fs.createReadStream(sourcePath);   
  7.       const writeStream = fs.createWriteStream(destPath); 
  8.       return new Promise((resolve, reject) => { 
  9.         readStream.pipe(writeStream); 
  10.         readStream.on('end', onClose); 
  11.         readStream.on('error', onError); 
  12.         async function onClose() { 
  13.           await fsPromises.unlink(sourcePath); 
  14.           resolve(); 
  15.         } 
  16.         function onError(err) { 
  17.           console.error(`File write failed with message: ${err.message}`);   
  18.           writeStream.close(); 
  19.           reject(err) 
  20.         } 
  21.       }) 
  22.     } 
  23.  
  24.     throw error; 
  25.   } 

四:测试

方式一:终端调用

  1. curl --location --request POST 'localhost:3000/upload/image' \ 
  2. --form 'img=@/Users/Downloads/五月君.jpeg' 

方式二:POSTMAN 调用

Reference

本文转载自微信公众号「 Nodejs技术栈  」,可以通过以下二维码关注。转载本文请联系 Nodejs技术栈公众号。

 

来源: Nodejs技术栈内容投诉

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

软考中级精品资料免费领

  • 历年真题答案解析
  • 备考技巧名师总结
  • 高频考点精准押题
  • 2024年上半年信息系统项目管理师第二批次真题及答案解析(完整版)

    难度     813人已做
    查看
  • 【考后总结】2024年5月26日信息系统项目管理师第2批次考情分析

    难度     354人已做
    查看
  • 【考后总结】2024年5月25日信息系统项目管理师第1批次考情分析

    难度     318人已做
    查看
  • 2024年上半年软考高项第一、二批次真题考点汇总(完整版)

    难度     435人已做
    查看
  • 2024年上半年系统架构设计师考试综合知识真题

    难度     224人已做
    查看

相关文章

发现更多好内容

猜你喜欢

AI推送时光机
位置:首页-资讯-后端开发
咦!没有更多了?去看看其它编程学习网 内容吧
首页课程
资料下载
问答资讯