文件上传功能往往是web应用非常重要的功能之一,使用express框架可以简单调用模块实现这一点。
文件上传
客户端上传文件:bootstrap-fileinput插件
插件安装
bootstrap-fileinput插件是基于jQuery和bootstrap的一款集合了文件上传功能优化和界面美化的插件,支持bootstrap3.x和4.x。其包含的css和js文件,需要在bootstrap的css和js文件后引入。
官方下载:https://plugins.krajee.com/file-input
另外需要再引入一个汉化js文件:https://github.com/kartik-v/bootstrap-fileinput/tree/master/js/locales
插件使用
在html中,可以将上传栏嵌套在bootstrap的折叠中
<button type="button" class="btn btn-secondary" data-toggle="collapse" data-target="#downblock">上传文件</button> <div id="downblock" class="collapse"> <!--上传框--> <input type="file" name="txt_file" id="txt_file" multiple class="file-loading" /> </div>
然后需要调用脚本初始化上传功能
$(document).ready(() => { //文档加载完后执行 //新建上传对象 $(function () { var oFileInput = new FileInput(); oFileInput.Init("txt_file", "/file/upload");//上传时发送Post请求的地址 }); //初始化上传对象 var FileInput = function () { var oFile = new Object(); oFile.Init = function(ctrlName, uploadUrl) { var control = $('#' + ctrlName); //设置上传框的选项 control.fileinput({ language: 'zh', //语言 uploadUrl: uploadUrl, //地址 showUpload: true, //是否显示上传按钮 showCaption: false,//是否显示标题 browseClass: "btn btn-info", //按钮样式 //maxFileSize: 0, //上传最大文件大小(kb),0则无上限 //minFileCount: 0, //同时上传最小文件数 maxFileCount: 5, //同时上传最大文件数 enctype: 'multipart/form-data', validateInitialCount: true, previewFileIcon: "<i class='glyphicon glyphicon-king'></i>", msgFilesTooMany: "选择上传的文件数量({n}) 超过允许的最大数值{m}!", }); //文件上传完成后触发 $("#txt_file").on("fileuploaded", function (event, data, previewId, index) { window.location.reload(true); alert("上传成功!"); }); } return oFile; }; });
服务端接受文件:formidable模块
首先在express项目所在文件夹安装好模块
npm install formidable --save
接着在路由中调用formidable模块以及fs模块并接收文件
var express = require('express'); var router = express.Router(); var fs = require("fs"); var formidable = require("formidable"); //上传文件 router.post('/upload', function(req, res) { var form = new formidable.IncomingForm(); form.encoding = 'utf-8'; form.uploadDir =__dirname.slice(0, -6) + "uploadfiles/tmp"; // 临时文件夹 form.keepExtensions = true; // 文件扩展名 // 读取登录用户所在的班级,便于分文件夹存储 var currentClass = req.session.class || '0'; form.parse(req, function(err, fields, files) { var filepath = ''; for (var key in files) { if (files[key].path && filepath == '') { filepath = files[key].path; break; } } var targetDir = __dirname.slice(0, -6) + "uploadfiles/" + currentClass + "/"; // 创建存放文件的目录 if (!fs.existsSync(targetDir)) { fs.mkdir(targetDir, { recursive: true }, (err) => { if (err) throw err; }); } var fileExt = filepath.substring(filepath.lastIndexOf(".")); //上传的原文件信息都在field中 var originName = fields.fileId.slice(fields.fileId.indexOf("_") + 1, fields.fileId.lastIndexOf(".")); //重新命名文件 var filename = originName + "-" + getdate() + "-" + new Date().getTime() + fileExt; var targetFile = targetDir + filename; fs.rename(filepath, targetFile, err => { if (err) { console.info(err); res.json({ code: -1, message: "操作失败" }); } else { // 上传成功后触发 var fileUrl = __dirname.slice(0, -6) + 'uploadfiles/' + currentClass + "/" + filename; res.json({ code: 200, fileUrl: fileUrl }); console.log("UPLOAD 用户 " + req.session.username + " 上传了文件: " + fileUrl); } }); }); })
这样便完成了文件从客户端发送到服务端存储至本地的操作
文件列表推送
客户端主动获取文件列表:fs模块
为了减少一次获取的数据量,仅当用户点击“内容发布”时,向服务端发送get请求,利用AJAX显示文件的列表
var FileFull; //读取文件列表 function read() { $.get('/file/list', (data) => { $("#filelist").children().remove(); // 移除原有列表 FileFull = data; // 存储新的文件列表 data.forEach((item, index, array) => { // 调节文件名的现显示格式,并且将节点加入表中 showName = item.slice(0, item.lastIndexOf('-')) + item.substring(item.lastIndexOf('.')) $("#filelist").append('<li href="#" value="' + index + '" class="list-group-item list-group-item-action file" οnclick="showFile(this.innerText, this.value)">' + showName + '</li>'); }); }, "json") }
//读取文件列表 router.get('/list', function(req, res) { var currentClass = req.session.class || "0"; fs.readdir(__dirname.slice(0, -6) + "uploadfiles/" + currentClass + "/", (err, files) => { res.json(files); res.end(); }) })
服务端只要简单的利用fs模块读取对应文件夹里的文件列表即可
服务端主动推送文件列表:socket.io模块
首先,socket.io基于http,http模块是在bin/www下导入并生成server,因此需要在此文件内引入该模块:
var app = require('../app'); var http = require('http'); var port = normalizePort(process.env.PORT || '3000'); app.set('port', port); var server = http.createServer(app); var io = require('socket.io')(server); app.io(io);
然后便可以在路由中使用socket.io模块
var IO; router.io = function(io) { io.on('connection', (socket) => { socket.on('message', (data) => { console.log(data); }); }); IO = io; return io; }
定义io函数,将路由绑定上使用Websocket发送信息的功能。
接下来,就可以在需要使用Websocket的地方调用io,例如在用户上传了文件之后,给其他用户发送更新文件列表的请求。
socket.broadcast.emit('updateFileList', { code: 200, data: '请立即更新文件列表' });
为了避免代码重复,服务端主动发送的内容仅仅是触发客户端,使其再次发送获得最新文件列表的请求。于是,其他用户也能实时看见最新的文件列表。
文件下载、删除等操作
只需要很简单地在客户端发送请求,服务端接收后利用fs模块进行删除,或者利用res.download()发送文件即可。
function downloadFile() { let filefullname = FileFull[$("#operateFile").val()]; window.open('/file/download?fileName=' + filefullname ); }
需要注意的是,如果直接发送get请求,浏览器将不会对返回的文件进行下载,仅仅保留在response里面。为了下载文件,需要另外打开窗口来替代get请求。