网易首页 > 网易号 > 正文 申请入驻

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

0
分享至

作者: 五月君 来源: Nodejs技术栈

一:开启 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 调用

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

特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。

Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.

相关推荐
热点推荐
森林狼为麦克丹尼尔斯标天价:非字母哥约基奇不换

森林狼为麦克丹尼尔斯标天价:非字母哥约基奇不换

甜度百分百21
2026-06-22 00:24:15
我25岁,跟40岁女主管同居三个月后,她拿着验孕棒问我愿意娶她吗

我25岁,跟40岁女主管同居三个月后,她拿着验孕棒问我愿意娶她吗

千秋文化
2026-06-16 19:33:45
演员袁泉与女儿夏哈哈的日常留影,女儿不太像妈妈

演员袁泉与女儿夏哈哈的日常留影,女儿不太像妈妈

娱你同欢
2026-06-21 23:06:52
看到梅西帽子戏法那一刻,我明白了什么叫“为自己踢球”

看到梅西帽子戏法那一刻,我明白了什么叫“为自己踢球”

一隅安稳
2026-06-21 00:41:39
L3智驾强制国标报批稿公示!特斯拉沉默,小鹏忙发声,华为偷着乐

L3智驾强制国标报批稿公示!特斯拉沉默,小鹏忙发声,华为偷着乐

华庭讲美食
2026-06-22 01:49:28
全球狂押500亿赌世界杯!92%胜率踢出0比0,有人百万身家秒归零

全球狂押500亿赌世界杯!92%胜率踢出0比0,有人百万身家秒归零

刘哥谈体育
2026-06-22 05:31:34
白鹿《奔跑吧》收官浓妆翻车?章若楠淡妆反而赢了

白鹿《奔跑吧》收官浓妆翻车?章若楠淡妆反而赢了

情感大头说说
2026-06-22 01:33:40
真的吗?万佩塔:拉菲尼亚陷入家庭和经济困境,期待转会新月

真的吗?万佩塔:拉菲尼亚陷入家庭和经济困境,期待转会新月

懂球帝
2026-06-22 13:14:20
非必要不做CT?医生强调:只要做过CT,患者一定多加关注这4点!

非必要不做CT?医生强调:只要做过CT,患者一定多加关注这4点!

叙说医疗健康
2026-06-16 08:00:21
离谱到家,日本踢世界杯比赛,中国观众超过了日本观众数倍

离谱到家,日本踢世界杯比赛,中国观众超过了日本观众数倍

体坛狗哥
2026-06-21 18:36:47
健身这样穿,效果惊艳全场!

健身这样穿,效果惊艳全场!

独角showing
2026-06-22 14:30:11
中国将迎来前所未有的死亡高峰,专家得出答案:是这些因素导致的

中国将迎来前所未有的死亡高峰,专家得出答案:是这些因素导致的

医学科普汇
2026-06-16 21:50:07
央视曝光!多款水果接连暴雷,滥用甜味剂8000倍甜度、违规防腐剂

央视曝光!多款水果接连暴雷,滥用甜味剂8000倍甜度、违规防腐剂

阿纂看事
2026-06-22 13:44:48
工行、农行、中行、建行、交行,集体宣布→

工行、农行、中行、建行、交行,集体宣布→

城市速递
2026-06-21 20:46:12
耿同学的导师杨昀发声明:她未被处理,高考676分从宁夏考入清华

耿同学的导师杨昀发声明:她未被处理,高考676分从宁夏考入清华

汉史趣闻
2026-06-22 14:49:30
赛力斯,我又没忍住!63元先干为敬,剩下的交给工资和问界M8了!

赛力斯,我又没忍住!63元先干为敬,剩下的交给工资和问界M8了!

沙雕小琳琳
2026-06-22 11:34:08
为何越来越多人搬离“一楼带院”的房子,过来人说出大实话

为何越来越多人搬离“一楼带院”的房子,过来人说出大实话

家居设计师苏哥
2026-06-22 12:38:58
洪秀柱直言敲打:既然不愿扛起统一大旗,何必身居国民党主席之位

洪秀柱直言敲打:既然不愿扛起统一大旗,何必身居国民党主席之位

谁将主宰未来
2026-06-21 09:57:43
A股:紧急提醒2.5亿股民!从今天6月22日起,A股或迎大级别变盘行情?

A股:紧急提醒2.5亿股民!从今天6月22日起,A股或迎大级别变盘行情?

趋势清风侠
2026-06-22 07:29:05
又一金饭碗彻底凉了!当年砸钱挤进银行的富二代,如今集体跑路了

又一金饭碗彻底凉了!当年砸钱挤进银行的富二代,如今集体跑路了

新时代的两性情感
2026-06-21 14:57:32
2026-06-22 15:55:00
Nodejs开发
Nodejs开发
分享只有程序员懂的干货
648文章数 823关注度
往期回顾 全部

科技要闻

智谱盘中狂飙超40%,市值破万亿港元

头条要闻

37万的新车送店贴膜3小时被店员撞损直贬7万 多方回应

头条要闻

37万的新车送店贴膜3小时被店员撞损直贬7万 多方回应

体育要闻

法国球星祝中国队下届世界杯取得好成绩

娱乐要闻

陪睡陪玩是皮毛,向佐揭内娱暗规则

财经要闻

为AI芯片续命 中国人造钻石等来了大机会

汽车要闻

电动MINIJCW缎光特别版藏锋上市尽显低调赛道本色

态度原创

手机
本地
房产
游戏
公开课

手机要闻

高盛将全球智能手机今明两年的出货量预测分别下调4%和3%

本地新闻

龙腾资江 韵动邵阳

房产要闻

商业清零式退潮,大量住宅登场!三亚又要大规模调规!

《拉面模拟器》正式推出 日式拉面制作经营模拟

公开课

李玫瑾:为什么性格比能力更重要?

无障碍浏览 进入关怀版