最近在项目中用到了文件上传和下载,遇到了一些问题。例如利用组件上传文件时,同时提交文件和表单数据;下载时用post方法;利用a标签download属性失效等问题,查看了一些资料,觉得有必要系统的总结一下,当是梳理一下关于文件操作零碎的知识点。
一,文件上传
1,基础知识:
1,form表单提交文件
enctype 属性:
application/x-www-form-urlencoded:只处理表单域中的value属性值,采用这种编码方式的表单会将表单域的值处理成url编码方式,默认方式
multipart/form-data:这种编码方式将表单中的数据变成二进制数据进行上传,不对字符编码,可以实现多种类型的文件上传
text/plain:纯文本传输,不含任何控件和格式字符,这种方式主要适用于直接通过表单发送邮件的方式
MIME类型:MIME类型就是设定某种扩展名的文件用一种应用程序来打开的方式类型,当该扩展名文件被访问的时候,浏览器会自动使用指定应用程序来打开。
多用于指定一些客户端自定义的文件名,以及一些媒体文件打开方式
文本文件:由可见字符组成的文件。所谓可见字符是指ASCII码为32到126的字符、回车符(ASCII码13)、换行符(ASCII码10)、制表符(ASCII码9)、以及所有汉字字符 (当然也包括其他字符集如韩文、日文、阿拉伯文等等)。如果是Unicode文本,则还包括ASCII码0
二进制文件::以0和1的形式存储在硬盘的所有电脑文件,可以说所有的存储在电脑上的文件均为二进制文件
以上两种文件只是再逻辑上区分,物理上其实都属于二进制文件
文件上传:设置enctype="multipart/form-data",method为post。当提交表单后浏览器将表单数据封装成http请求格式发送到服务器,服务器端收到"multipart/form-data"类型的 http请求消息,读取这个请求消息里面的实体内容
2,传统表单上传文件
<form action="文件上传的服务器地址" method="post" enctype="multipart/form-data">
<input type="file" name="file" value="选择jar包"/>
<input id="submit_form" type="submit" class="btn btn-success save" value="保存"/>
</form>
1.服务器端程序收到"multipart/form-data"类型的http请求消息
2.读取这个请求消息里面的实体内容
3.解析每个分区的数据
4.从每个分区中解析出描述头和主体内容部分
表单上传注意点:
1, method="post": 采用post方式提交数据
2, enctype="multipart/form- data":采用multipart格式上传文件,此时request头会显示
Content-Type:multipart/form-data; boundary=— WebKitFormBoundaryzr34cwJ67R95KQC9
3, action:标明上传的服务端处理地址
4, type="file":使用input的file控件上传
5, 如果是多文件批量上传,将input加上 multiple属性(支持按住shift键多选文件)
6, accept属性是HTML5的新属性,它规定了可通过文件上传提交的文件类型
7, 上传的触发事件可以是:input[type=”file”]的onChange触发,也可以由一个独立的按钮的onClick使整个表单提交,此时还可以用input[type="hidden"]带一些其它的参数,比如 Token来源验证等等。
3,AJAX无刷新上传
<form>
<input id="file" name="file" type="file" />
<input id="token" name="token" type="hidden" />
</form>
$("#file").on("change", function(){
var formData = new FormData();
formData.append("file", $("#file")[0].files);
formData.append("token", $("#token").val());
$.ajax({
url: "http://uploadUrl",
type: "POST",
data: formData,
processData: false, // 不要对data参数进行序列化处理,默认为true
contentType: false, // 不要设置Content-Type请求头,因为文件数据是以 multipart/form-data 来编码
success: function(response){
// 根据返回值来操作
}
});
});
4,在form中使用antd的Upload组件和其他表单数据一起提交(借鉴网上的代码)
思路:在Upload组件中定义beforeUpload方法并且返回false拦截文件的自动上传,同时把文件信息添加到state中,由于文件列表可以删除影响state,所以需要在Upload组件中添加onRemove方法用于在删除列表时候实时更新state
//这个是监听文件变化的 fileChange=(params)=>{ const {file,fileList}=params; if(file.status==='uploading'){ setTimeout(()=>{ this.setState({ percent:fileList.percent }) },1000) } } // 拦截文件上传 beforeUploadHandle=(file)=>{ this.setState(({fileData})=>({ fileData:[...fileData,file], })) return false; } // 文件列表的删除 fileRemove=(file)=>{ this.setState(({fileData})=>{ const index=fileData.indexOf(file); const newFileList=fileData.slice(); newFileList=splice(index,1); return { fileData:newFileList } }) } render(){ <FormItem labelCol={{span:5}} wrapperCol={{span:15}} label='文件上传'> {getFieldDecorator(form,settings,formName,'name',values)( <Upload action='路径' beforUpload={this.beforeUploadHandle} onChange={this.fileChange} onRemove={this.fileRemove} fileList={this.state.fileData}> <Button><Icon type='upload' />上传文件</Button> </Upload> )} </FormItem> 通过append方法将数据逐条添加到formData中 const {fileData}=this.state; const formData=new formData(); fileData.forEach((file)=>{ formData.append('files',file); }) // fields为表单其他项的数据,在antd-pro中是fileds Object.keys(fields).map((item)=>{ formData.append(item,fields[item]); }) this.props.dispatch({ type:'pluginInfo/upload', payload:formData, callback:()=>{ resetFields(); this.setState({ fileData:[], }) this.props.handleModalVisible(false)//这个是项目中的关闭弹窗方法,可以无视 } })
二,文件下载
首先,要清楚实现文件下载有哪几种方式,自己总结了几种在前后端分离的项目中用过方式,通过查资料书面的整理了如下:
1,后端给了一个下载的api请求接口,该接口返回文件的内容(流的形式),前端拿到文件的内容然后写进文件,实现下载。
fetch('/api/resource/exportExcelResource', {
// body: JSON.stringify({...param}), 如果需要可以向后台传参,此时method为'POST'
method: 'GET',
}).then(response => {
return response.blob(); //请求接口返回文件二进制流,转成blob对象,并将内容写入文件
}).then(blob => {
const url = window.URL.createObjectURL(blob);//创建一个存储在内存中的新的url
const a = document.createElement('a');
a.href = url;
a.download = 'XXX文件.xls';
a.click();
})
2,后端直接给了文件在服务器上的绝对地址,根据该地址直接下载
<a href="http://172.XX.XX.XX/文件1.xls"></a>
也可以通过发送请求,来获取二进制文件流,然后按照方法一,前端写入文件进行下载操作
fetch('http://172.XX.XX.XX/文件1.xls', {
// body: JSON.stringify({...param}), 如果需要可以向后台传参,此时method为'POST'
method: 'GET',
}).then(response => {
return response.blob(); //请求接口返回文件二进制流,转成blob对象,并将内容写入文件
}).then(blob => {
const url = window.URL.createObjectURL(blob);//创建一个存储在内存中的新的url
const a = document.createElement('a');
a.href = url;
a.download = 'XXX文件.xls'; //文件名可以后端给出,前端用response.headers.get('Content-Disposition')从响应头的Content-Disposition中拿到filename
a.click();
})
另外还可通过iframe来实现下载:
let elem = document.createElement('iframe');
elem.src = url;
elem.style.display = 'none';
document.body.appendChild(elem);
以上是我用到的两种文件下载方式,此处存在的问题就是:当文件下载地址和当前应用地址不是同源URL时,a标签的download属性会失效,因为download属性是告诉浏览器将要下载该文件,而不只是访问文件,所以必须该属性必须在同源URL下才生效。同时当http头中content-disposition设置了文件下载名称时,会覆盖a标签download属性设置的下载名称
当使用到a标签的download属性失效时解决办法有两种:
1,在服务器上配置nginx代理,通过同源URL重定向到文件地址。
2,让后端获取文件,直接将文件内容通过接口发送给前端,前端再使用方式1实现下载
至于文件的导出,其实和文件下载有相同之处,只不过文件导出需要访问后台接口进行筛选出需要导出的数据,生成Excel再发送到前端,前端就实现和下载相同的操作。