node.js express框架下實現文件上傳與下載的功能

背景

昨天吉視傳媒的客戶對IPS信息發佈系統又提了一個新需求,就是發佈端發送消息時需要支持附件的上傳,而接收端可以對發佈端上傳的附件進行下載;接收端回覆消息時也需要支持上傳附件,發佈端可以對所有接收端上傳的附件進行打包下載。

功能實現

  • 前臺部分
    前臺使用webUploader插件即可,這是百度開發的一款文件上傳組件,具體使用查看它的API即可。這個項目之前開發的時候前臺使用了angular.js。
    $scope.fileName = "";
    //創建上傳附件的對象
    var $list = $("#thelist");
    var uploader = WebUploader.create({
        // 選完文件後,是否自動上傳。
        auto: false,
        // swf文件路徑
        swf: '../../../lib/webUploader/Uploader.swf',
        // 文件接收服務端。
        server: '/publishUploadFile',
        // 內部根據當前運行是創建,可能是input元素,也可能是flash.
        pick : {
            id : '#filePicker',
            //只能選擇一個文件上傳
            multiple: false
        },
        // pick :'#filePicker',
        method: 'POST',
    });
    uploader.on('fileQueued', function (file) {
        $scope.fileName = file.name;
        $list.html("");
        $list.html(file.name);
    });

當用戶選擇文件的時候我創建了文件上傳的對象,而在用戶真正發送消息的時候我添加了相應的參數並將附件真正的上傳上去,符合我這個項目的業務邏輯。

if($scope.fileName){
    //添加參數
    uploader.options.formData.fileId = fileId;
    uploader.options.formData.fileName = $scope.fileName;
    uploader.upload();
}
  • 後臺部分
    路由就不詳細說明了,主要注意的是下載的接口我都是使用的get請求,這樣前臺在請求的時候直接新打開一個窗口拼接了相應的參數就能下載文件了。下面貼一下action層的代碼:
//發佈端上傳附件
exports.publishUploadFile = function (req, res) {
    messageMng.publishUploadFile(req, function (err, datas) {
        res.json(datas);
    });
};


//下載發佈端上傳的附件
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);
    });
};

//發佈端導出附件
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解壓報錯
            // 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時使用download下載完的壓縮包解壓會報錯,使用第二種方法完美解決。
然後是service層的代碼:

/**
 * 發佈端上傳附件
 * @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);

        //判斷文件夾是否存在 不存在則創建
        toolUtil.mkdirSync(uploadFolder);

        //將上傳的文件從臨時目錄拷貝到指定的目錄下
        var fileReadStream = fs.createReadStream(file.path);
        var fileWriteStream = fs.createWriteStream(uploadFolder + "/" + file.originalname);
        fileReadStream.pipe(fileWriteStream);
        fileWriteStream.on('close', function () {
            // 刪除臨時目錄下面的文件
            toolUtil.emptyDir(file.destination);
        });

        fn(null, {"data": "", "message": "上傳成功", "error_code": 200});
    } catch (e) {
        fn(e, {"data": "", "message": "上傳失敗", "error_code": e.message});
    }
};

/**
 * 下載發佈端上傳的附件
 * @param req
 * @param fn
 */
MessageManager.prototype.exportPublishFile = function (req, fn) {
    try {
        //附件ID
        var id = req.query.id;
        //附件名稱或標題
        var name = req.query.name;

        if (id && name) {
            //名稱過長的話,截取前25個字符
            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;
        //消息發送的時間
        var msgSendTime = req.body.msgSendTime.slice(0, 10);
        //消息的標題
        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);

        //判斷文件夾是否存在 不存在則創建
        toolUtil.mkdirSync(uploadFolder);

        //組裝文件的名稱 原名稱+消息發送時間
        var index = file.originalname.lastIndexOf(".");
        var fileName = file.originalname.substr(0, index) + '-' + msgSendTime + "";
        var suffix = file.originalname.substr(index, file.originalname.length - 1);

        //將上傳的文件從臨時目錄拷貝到指定的目錄下
        var fileReadStream = fs.createReadStream(file.path);
        var fileWriteStream = fs.createWriteStream(uploadFolder + "/" + fileName + "." + suffix);
        fileReadStream.pipe(fileWriteStream);
        fileWriteStream.on('close', function () {
            //刪除臨時目錄下面的文件
            toolUtil.emptyDir(file.destination);
        });

        fn(null, {"data": "", "message": "上傳成功", "error_code": 200});
    } catch (e) {
        fn(e, {"data": "", "message": "上傳失敗", "error_code": e.message});
    }
};

/**
 * 導出消息的附件文件
 * @param req
 * @param fn
 */
MessageManager.prototype.exportFile = function (req, fn) {
    try {
        //消息ID
        var id = req.query.id;
        //消息名稱或標題
        var name = req.query.name;

        if (id && name) {
            //名稱過長的話,截取前25個字符
            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 {
                //生成得臨時zip文件目錄
                var zipPath = path.join(__dirname, '../../upload/temp.zip');

                var archive = archiver('zip', {
                    // Sets the compression level.
                    zlib: {level: 9} 
                });
                //創建臨時zip文件
                var output = fs.createWriteStream(zipPath);
                archive.pipe(output);

                //設置需要壓縮得文件夾目錄 以及替換得名稱
                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的代碼,這個單獨做爲一個js文件維護。

const path = require('path');
const fs = require('fs');

/**
 * 創建目錄
 * @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);
        }
    });
};
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章