node.js express框架實(shí)現(xiàn)文件上傳與下載功能實(shí)例詳解
本文實(shí)例講述了node.js express框架實(shí)現(xiàn)文件上傳與下載功能。分享給大家供大家參考,具體如下:
背景
昨天吉視傳媒的客戶對(duì)IPS信息發(fā)布系統(tǒng)又提了一個(gè)新需求,就是發(fā)布端發(fā)送消息時(shí)需要支持附件的上傳,而接收端可以對(duì)發(fā)布端上傳的附件進(jìn)行下載;接收端回復(fù)消息時(shí)也需要支持上傳附件,發(fā)布端可以對(duì)所有接收端上傳的附件進(jìn)行打包下載。
功能實(shí)現(xiàn)
- 前臺(tái)部分
前臺(tái)使用webUploader插件即可,這是百度開發(fā)的一款文件上傳組件,具體使用查看它的API即可。這個(gè)項(xiàng)目之前開發(fā)的時(shí)候前臺(tái)使用了angular.js。
$scope.fileName = ""; //創(chuàng)建上傳附件的對(duì)象 var $list = $("#thelist"); var uploader = WebUploader.create({ // 選完文件后,是否自動(dòng)上傳。 auto: false, // swf文件路徑 swf: '../../../lib/webUploader/Uploader.swf', // 文件接收服務(wù)端。 server: '/publishUploadFile', // 內(nèi)部根據(jù)當(dāng)前運(yùn)行是創(chuàng)建,可能是input元素,也可能是flash. pick : { id : '#filePicker', //只能選擇一個(gè)文件上傳 multiple: false }, // pick :'#filePicker', method: 'POST', }); uploader.on('fileQueued', function (file) { $scope.fileName = file.name; $list.html(""); $list.html(file.name); });
當(dāng)用戶選擇文件的時(shí)候我創(chuàng)建了文件上傳的對(duì)象,而在用戶真正發(fā)送消息的時(shí)候我添加了相應(yīng)的參數(shù)并將附件真正的上傳上去,符合我這個(gè)項(xiàng)目的業(yè)務(wù)邏輯。
if($scope.fileName){ //添加參數(shù) uploader.options.formData.fileId = fileId; uploader.options.formData.fileName = $scope.fileName; uploader.upload(); }
- 后臺(tái)部分
路由就不詳細(xì)說明了,主要注意的是下載的接口我都是使用的get請(qǐng)求,這樣前臺(tái)在請(qǐng)求的時(shí)候直接新打開一個(gè)窗口拼接了相應(yīng)的參數(shù)就能下載文件了。下面貼一下action層的代碼:
//發(fā)布端上傳附件 exports.publishUploadFile = function (req, res) { messageMng.publishUploadFile(req, function (err, datas) { res.json(datas); }); }; //下載發(fā)布端上傳的附件 exports.exportPublishFile = function (req, res) { messageMng.exportPublishFile(req, function (err, datas) { if (err) { res.set({ "Content-Disposition": "attachment;filename=" + encodeURI("error.txt") }); res.write(err.message); res.end(); } else { res.download(datas.path, encodeURI(datas.name)); } }); }; //接收端上傳附件 exports.uploadFile = function (req, res) { messageMng.uploadFile(req, function (err, datas) { res.json(datas); }); }; //發(fā)布端導(dǎo)出附件 exports.exportFile = function (req, res) { messageMng.exportFile(req, function (err, datas) { if (err) { res.set({ "Content-Disposition": "attachment;filename=" + encodeURI("error.txt") }); res.write(err.message); res.end(); } else { //第一種方式 下載完的zip解壓報(bào)錯(cuò) // res.download(datas.path, datas.name + ".zip"); //第二種方式 // var path="D:/maven介紹.ppt"; var f = fs.createReadStream(datas.path); res.writeHead(200, { 'Content-Type': 'application/force-download', 'Content-Disposition': 'attachment; filename='+ encodeURI(datas.name) + '.zip' }); f.pipe(res); } }); };
這里著重說一下下載zip時(shí)使用download下載完的壓縮包解壓會(huì)報(bào)錯(cuò),使用第二種方法完美解決。
然后是service層的代碼:
/** * 發(fā)布端上傳附件 * @param req * @param fn */ MessageManager.prototype.publishUploadFile = function (req, fn) { try { //消息ID var fileId = req.body.fileId; var file = req.file; //文件上傳的目錄 var uploadFolder = path.join(__dirname, '../../upload/publishUploadFile/' + fileId); //判斷文件夾是否存在 不存在則創(chuàng)建 toolUtil.mkdirSync(uploadFolder); //將上傳的文件從臨時(shí)目錄拷貝到指定的目錄下 var fileReadStream = fs.createReadStream(file.path); var fileWriteStream = fs.createWriteStream(uploadFolder + "/" + file.originalname); fileReadStream.pipe(fileWriteStream); fileWriteStream.on('close', function () { // 刪除臨時(shí)目錄下面的文件 toolUtil.emptyDir(file.destination); }); fn(null, {"data": "", "message": "上傳成功", "error_code": 200}); } catch (e) { fn(e, {"data": "", "message": "上傳失敗", "error_code": e.message}); } }; /** * 下載發(fā)布端上傳的附件 * @param req * @param fn */ MessageManager.prototype.exportPublishFile = function (req, fn) { try { //附件ID var id = req.query.id; //附件名稱或標(biāo)題 var name = req.query.name; if (id && name) { //名稱過長的話,截取前25個(gè)字符 if (name.length > 25) { name = name.substr(0, 24); } //將要壓縮得文件夾路徑 var filePath = path.join(__dirname, '../../upload/publishUploadFile/' + id + '/' + name); if (!fs.existsSync(filePath)) { fn(new Error("沒有附件!"), null); } else { fn(null, {"name": name, "path": filePath}); } } else { fn(new Error("id或name不能為空"), null); } } catch (e) { fn(new Error(e.message), null); } }; /** * 接收端上傳附件 * @param req * @param fn */ MessageManager.prototype.uploadFile = function (req, fn) { try { //消息ID var msgId = req.body.msgId; //消息發(fā)送的時(shí)間 var msgSendTime = req.body.msgSendTime.slice(0, 10); //消息的標(biāo)題 var title = req.body.title; var replyId = req.body.replyId; var replyName = req.body.replyName; var file = req.file; //文件上傳的目錄 var uploadFolder = path.join(__dirname, '../../upload/messages/' + msgId + '/' + replyName); //判斷文件夾是否存在 不存在則創(chuàng)建 toolUtil.mkdirSync(uploadFolder); //組裝文件的名稱 原名稱+消息發(fā)送時(shí)間 var index = file.originalname.lastIndexOf("."); var fileName = file.originalname.substr(0, index) + '-' + msgSendTime + ""; var suffix = file.originalname.substr(index, file.originalname.length - 1); //將上傳的文件從臨時(shí)目錄拷貝到指定的目錄下 var fileReadStream = fs.createReadStream(file.path); var fileWriteStream = fs.createWriteStream(uploadFolder + "/" + fileName + "." + suffix); fileReadStream.pipe(fileWriteStream); fileWriteStream.on('close', function () { //刪除臨時(shí)目錄下面的文件 toolUtil.emptyDir(file.destination); }); fn(null, {"data": "", "message": "上傳成功", "error_code": 200}); } catch (e) { fn(e, {"data": "", "message": "上傳失敗", "error_code": e.message}); } }; /** * 導(dǎo)出消息的附件文件 * @param req * @param fn */ MessageManager.prototype.exportFile = function (req, fn) { try { //消息ID var id = req.query.id; //消息名稱或標(biāo)題 var name = req.query.name; if (id && name) { //名稱過長的話,截取前25個(gè)字符 if (name.length > 25) { name = name.substr(0, 24); } //將要壓縮得文件夾路徑 var messagePath = path.join(__dirname, '../../upload/messages/' + id); if (!fs.existsSync(messagePath)) { fn(new Error("沒有附件!"), null); } else { //生成得臨時(shí)zip文件目錄 var zipPath = path.join(__dirname, '../../upload/temp.zip'); var archive = archiver('zip', { // Sets the compression level. zlib: {level: 9} }); //創(chuàng)建臨時(shí)zip文件 var output = fs.createWriteStream(zipPath); archive.pipe(output); //設(shè)置需要壓縮得文件夾目錄 以及替換得名稱 archive.directory(messagePath, name); archive.finalize(); archive.on('end', function (err) { fn(null, {"name": name, "path": zipPath}); }); archive.on('error', function (err) { fn(new Error("壓縮文件異常"), null); }); } } else { fn(new Error("id或name不能為空"), null); } } catch (e) { fn(new Error(e.message), null); } };
最后是提出的公共方法toolUtil的代碼,這個(gè)單獨(dú)做為一個(gè)js文件維護(hù)。
const path = require('path'); const fs = require('fs'); /** * 創(chuàng)建目錄 * @param dirpath */ exports.mkdirSync = function (dirpath){ if (!fs.existsSync(dirpath)) { var pathtmp; dirpath.split(path.sep).forEach(function(dirname) { if (pathtmp) { pathtmp = path.join(pathtmp, dirname); } else { pathtmp = dirname; } if (!fs.existsSync(pathtmp)) { fs.mkdirSync(pathtmp); } }); } }; //刪除所有的文件(將所有文件夾置空) exports.emptyDir = function(dirpath){ var self = this; //讀取該文件夾 var files = fs.readdirSync(dirpath); files.forEach(function(file){ var filePath = dirpath + '/' + file; var stats = fs.statSync(filePath); if(stats.isDirectory()){ self.emptyDir(filePath); }else{ fs.unlinkSync(filePath); } }); };
希望本文所述對(duì)大家node.js程序設(shè)計(jì)有所幫助。
相關(guān)文章
搭建一個(gè)Koa后端項(xiàng)目腳手架的方法步驟
這篇文章主要介紹了搭建一個(gè)Koa后端項(xiàng)目腳手架的方法步驟,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-05-05nodejs的http和https下載遠(yuǎn)程資源post數(shù)據(jù)實(shí)例
這篇文章主要為大家介紹了nodejs的http和https下載遠(yuǎn)程資源post數(shù)據(jù)實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-09-09node實(shí)現(xiàn)mock-plugin中間件的方法
這篇文章主要介紹了node實(shí)現(xiàn)mock-plugin中間件的方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12Node 創(chuàng)建第一個(gè)服務(wù)器應(yīng)用的操作方法
Node.js是一個(gè)基于Chrome V8引擎的JavaScript運(yùn)行環(huán)境,可以用于構(gòu)建高性能的網(wǎng)絡(luò)應(yīng)用程序,它采用事件驅(qū)動(dòng)、非阻塞I/O模型,使得程序可以以高效地方式處理并發(fā)請(qǐng)求,這篇文章主要介紹了Node 創(chuàng)建第一個(gè)服務(wù)器應(yīng)用,需要的朋友可以參考下2024-02-02node.js中module模塊的功能理解與用法實(shí)例分析
這篇文章主要介紹了node.js中module模塊的功能理解與用法,結(jié)合實(shí)例形式分析了node.js module模塊的基本功能、原理、用法及相關(guān)操作注意事項(xiàng),需要的朋友可以參考下2020-02-02