文件上传的原理
原理很简单,就是根据 http 协议的规范和定义,完成请求消息体的封装和消息体的解析,然后将二进制内容保存到文件。
如果要上传一个文件,需要把 form 标签的enctype设置为multipart/form-data,同时method必须为post方法。
例如:
<form method="post" action="http://localhost:8100" enctype="multipart/form-data">
解释如下:
MIME规定了每一个资源的类型,我们通过MIME判断资源类型。
multipart/form-data
我们通过Content-Type HTTP Header 头来知晓每种资源的MIME类型。当客户端发送了一个 “Content-Type: multipart/form-data;”,代表客户端要上传一个附件。也就是说 Content-Type 后面的值就是一个 MIME 类型。
含义:multipart互联网上的混合资源,就是资源由多种元素组成,form-data表示可以使用HTML Forms 和 POST 方法上传文件。
消息体什么意思呢,如果你自行想使用代码实现文件上传,要根据定义自行封装HTTP消息,接下去我们简单描述一下。
请求头
Content-Type: multipart/form-data; boundary=——FormBoundary 表示要上传附件,其中boundary表示分隔符,如果要上传多个表单项,就要使用boundary分割,每个表单项由———FormBoundary开始,以———FormBoundary结尾。每一个表单项又由Content-Type和Content-Disposition组成。
例如:
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDCntfiXcSkPhS4PN 表示本次请求要上传文件,其中boundary表示分隔符,如果要上传多个表单项,就要使用boundary分割,每个表单项由———XXX开始,以———XXX结尾。
Content-Type:表示当前的内容的 MIME 类型,是图片还是文本还是二进制数据。
Content-Disposition HTTP 消息头:其中第一个参数总是固定不变的form-data,name表示表单元素属性名,回车换行符后面的内容就是元素的值。
客户端发送请求到服务器后,服务器会收到请求的消息体,然后对消息体进行解析,解析出哪是普通表单哪些是附件。一般情况下不需要自行解析,目前已经有很成熟的三方库可以使用。
最初进行文件上传的方式是:
1:表单上传:
<form name="form名称" action="请求地址" method="请求类型" enctype ="multipart/form-data">
<input type="file" name="">
<input type="text" name="">
<input type="submit" value="提交">
</form>
2:使用使用FormData对象 配合ajax
let form_data = new FormData();
$.ajax({
url: url,
type: method,
data: formData,
processData: false,
contentType: false,
xhrFields: {
withCredentials: true
},
crossDomain: true,
success: function (obj) {
// ...
},
error: function (obj) {
// alert('服务器请求失败');
}
})
3,使用表单插件,例如jquery.form.js
// ajaxSubmit提交form表单
function updateUserInfo() {
$('#表单ID').ajaxSubmit({
url: url,
type: 'POST',
//data: $('表单ID').serialize(),
//processData: false,
//contentType: false,
xhrFields: {
withCredentials: true
},
crossDomain: true,
success: function (obj) {
// ...
},
error: function (obj) {
// alert('服务器请求失败');
}
});
}
使用el-upload实现文件上传
原理:
请求头中加token令牌
:headers="authHeaders"
data中声明
authHeaders: {},
//在请求头中加token,放在created中
this.accessToken = this.$store.getters.accessToken; this.authHeaders["Authorization"] = "Bearer " + this.accessToken;
在action中设置接受url地址。
:action="uploadUrlReferrer({type:'附件证明'})"
uploadUrlReferrer (row) {
let url = `${this.expertApiUrl}/experts/${this.expert.referrerId}/expert/${
row.type
}`;
return url;
},
至此,上传功能已经完成了。文件在点击上传之后,会自动获取本地文件列表,选择文件之后会上传至数据库。当然,这需要后端的配合,我只写了前端的部分。前端和后端的分界线就是url。
如果要用axios实现文件上传,需要使用before-upload 钩子, 用before-upload 自己上传 想怎么传都可以
删除文件
需要使用on-remove钩子
:on-remove="handleRemoveTwo"
钩子绑定函数,函数实现功能,因为项目比较大,其他地方也需要使用重复获取文件的功能,所以为了实现复用,将这部分放到了函数getExpertDocsFile()中。
调用deleteDoc接口,提供文件id,实现删除,删除完成之后,再重新获取文件列表。
handleRemoveTwo (file, fileListTwo) {
deleteDoc(this.expert.referrerId, file.id).then(res => {
if (res.status == 200) {
this.$message({
message: "操作成功",
type: "success"
});
this.getExpertDocsFile();
}
})
},
getExpertDocsFile () {
let that = this;
that.fileListTwo = [];
//getExpertDocs是获取包含附件的表的一个接口,接口传入的参数是专家的referrerId值,这个函数的目的是将数据库的获取到的函数push到fileListTwo中,使得附件显示出来,这里的fileListTwo也是一个钩子对应的值。`:file-list="fileListTwo"`这个钩子就是el-upload要显示在页面的文件列表,需要和数据库做对应。
getExpertDocs(that.expert.referrerId).then(res => {
that.docs = res.data || [];
if (that.docs.length > 0) {
_.map(that.docs, doc => {
if (doc.type == '附件证明') {
that.fileListTwo.push(doc);
}
});
}
});
var referrerExpert = that.expertsSelect.filter(a => {
return a.id == that.expert.referrerId
});
if (referrerExpert && referrerExpert.length > 0) {
that.referrerPaid = referrerExpert[0].referrerPaid;
}
},
预览(下载)文件
使用on-preview这个钩子,使用window.open打开文件,url是根路径和文件路径拼接而成。
:on-preview="handlePreview"
handlePreview (file, fileListTwo) {
let base = this.expertFileUrl;
let docUrl = base + file.path;
window.open(docUrl);
},
多文件上传
在组件中加一个multiple属性
禁止操作
disabled
我这里限制了referrerPaid为true时限制上传操作,但此时按钮并不会变灰,若想要按钮变灰无法操作,需要在按钮上也加一个disabled
:disabled="referrerPaid"
限制文件上传个数
imit限制数量为3,超出限制数量之后触发handleExceed函数
:limit="3"
:on-exceed="handleExceed"
handleExceed (files, fileList) {
this.$message.warning(
`当前限制选择 3 个文件,本次选择了 ${
files.length
} 个文件,共选择了 ${files.length + fileList.length} 个文件`
);
},
其他优化——类型大小限制
(1)一种方式是,加accpet属性
<el-upload class="upload-demo" :multiple="true" :action="action" accept="image/jpeg,image/gif,image/png,image/bmp"
:file-list="fileList" :before-upload="beforeAvatarUpload" :on-success="handleAvatarSuccess">
(2)另一种方式是在上传前的触发函数里面去判断
beforeUpload钩子中触发的函数
beforeAvatarUpload(file) {
const isJPG = file.type === 'image/jpeg';
const isGIF = file.type === 'image/gif';
const isPNG = file.type === 'image/png';
const isBMP = file.type === 'image/bmp';
const isLt2M = file.size / 1024 / 1024 < 2;
if (!isJPG && !isGIF && !isPNG && !isBMP) {
this.common.errorTip('上传图片必须是JPG/GIF/PNG/BMP 格式!');
}
if (!isLt2M) {
this.common.errorTip('上传图片大小不能超过 2MB!');
}
return (isJPG || isBMP || isGIF || isPNG) && isLt2M;
},