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

域名2025-11-05 06:33:115259

一:开启 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:UsersADMINI~1AppDataLocalTempupload_3cc33e9403930347b89ea47e4045b940,   dest: F:studytest202366 } 

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

设置上传文件中间件的临时路径为最终写入文件的免费源码下载磁盘分区,例如我们在 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 调用

Reference

https://github.com/andrewrk/node-mv/blob/master/index.js https://stackoverflow.com/questions/43206198/what-does-the-exdev-cross-device-link-not-permitted-error-mean/43206506#43206506 https://nodejs.org/api/fs.html#fs_fs_rename_oldpath_newpath_callback

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

香港云服务器
本文地址:http://www.bzuk.cn/html/3e33999657.html
版权声明

本文仅代表作者观点,不代表本站立场。
本文系作者授权发表,未经许可,不得转载。

全站热门

如何恢复Win10系统到出厂设置(利用电脑恢复Win10系统出厂设置,轻松实现系统重置)

别在Java代码里乱打日志了,这才是正确的打日志姿势!

你必须懂的前端性能优化

ARM创始人谈华为被禁:长期将伤害ARM、谷歌及美国工业

电脑赛博朋克素材教程(探索赛博朋克风格的电脑设计与创作技巧)

掌握这十大机器学习方法,你就是圈子里最靓的崽

使用Go处理每分钟百万请求

开发 | 一文读懂微服务监控之分布式追踪

友情链接

滇ICP备2023006006号-33