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);
        }
    });
};

你可能感兴趣的:(Node.js)