文件上傳與下載


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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM