最近在項目中用到了文件上傳和下載,遇到了一些問題。例如利用組件上傳文件時,同時提交文件和表單數據;下載時用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再發送到前端,前端就實現和下載相同的操作。