文件名含有特殊字符、文件名亂碼,如何實現下載——PHP、JS

先上代碼,看懂的可直接方便複製php

public function download(){
    $file_url = $_REQUEST('file_url'); //file_url參數多是個url,也多是文件相對路徑
    $file = '...對$file_url進行處理找到文件絕對路徑';
    $file_realname = $_REQUEST('file_realname'); //file_realname參數是js通過encodeURIComponent()函數編碼過的,提交以後,nginx通常都自動對url進行urldecode()解碼,因此直接接收就OK(若是發現沒自動解碼,就須要咱們手動urldecode)
    $file_realname = rawurlencode($file_realname);  //由於file_realname要傳回瀏覽器,因此還須要url編碼,才能保證不亂嗎,且這裏應該用rawurlencode()而不是urlencode()
    if(!file_exists($file)){
        echo "<script>alert('文件不存在')</script>";
        return;
    }else{
        header("Content-type:application/octet-stream");
        header("Content-Disposition:attachment; filename = $file_realname; 
                filename*=utf-8''$file_realname");
        header("Accept-ranges:bytes");
        header("Accept-length:".filesize($file));
        readfile($file);
    }
}

分割線下面是詳細原理和過程html

-----------------------------------------------------------------------------------------------------------html5

最近的項目需求

實現文件上傳(文件名爲:&ap;你好;123.rar),且上傳後要求馬上展現文件名(js插件在選擇完文件後默認會馬上調用文件上傳接口),並能夠點擊文件名直接進行下載,點擊肯定提交保存到數據庫。(說明:上傳後不能馬上刷新頁面,由於此時還未點擊肯定提交,並且服務器上的文件名已是時間戳編碼後的格式1526019720.rarnginx

 

需求分析

1.文件上傳數據庫

2.上傳後文件名展現後端

3.上傳後按原文件名直接下載瀏覽器

4.提交後,再次打開頁面時的原文件名展現服務器

5.提交後,再次打開頁面時按原文件名下載app

 

解決方案

1.文件上傳

上傳的問題好解決,網上的js插件不少,這裏用的是Layui函數

2.上傳後文件名展現

因爲咱們項目裏用的是Layui的文件上傳插件,因此展現文件名須要本身處理,該插件中沒有對file對象的name屬性進行html實體編碼,因此若是文件名中有&等特殊字符(若是使用原生的<input type='file'>就不會有這個問題),直接展現會有亂碼,所以必須先對文件名file.name進行實體編碼(並且file對象是js生成的,沒有通過後端php,因此也只能用js對其進行html實體編碼),網上找到了js的html編碼相關函數:http://www.jb51.net/article/93623.htm,爲了方便複製,我直接貼出代碼:

function HTMLEncode ( input )
{
    var converter = document.createElement("DIV");
    converter.innerText = input;
    var output = converter.innerHTML;
    converter = null;
    return output;
}

function HTMLDecode ( input )
{
    var converter = document.createElement("DIV");
    converter.innerHTML = input;
    var output = converter.innerText;
    converter = null;
    return output;
}

-----重點來了------

3.js插件上傳後按原文件名直接下載

因爲服務器上的文件名已變成1526019720.rar,因此要使用url直接下載的話,下載後文件名也會是1526019720.rar,因此只能去請求PHP文件,用PHP設置header的方式改變文件名,並把文件路徑和文件名經過參數傳給PHP文件(此時還沒辦法只傳個id,經過id查詢數據庫獲取文件路徑和文件名,由於尚未提交到數據庫!!!),參數傳遞一種是GET,一種是POST(強烈建議使用POST方式,這樣就省去了對url進行編碼,若是使用GET方式...大家懂的,下面會講到使用GET方式)

 

先講一下使用php的header方式進行文件下載,通常相似以下這種方式

<?php
public function download(){
    $file = '....具體的文件路徑';
    $file_realname = '....具體的文件名'; 
    header("Content-type:application/octet-stream");
    header("Content-Disposition:attachment; filename = $file_realname;)
    header("Accept-ranges:bytes");
    header("Accept-length:".filesize($file));
    readfile($file);
}

可是使用這種寫法,會發現若是文件名中有特殊字符(好比;等),則下載後的文件名會顯示不正確,由於「;」分號是 header("Content-Disposition:attachment; filename = $file_realname;)的分隔符

通過一番查找,終於找到一篇博客解惑了:https://www.cnblogs.com/hihtml5/p/7220188.html?utm_source=itdadao&utm_medium=referral

最終肯定使用php的自帶函數rawurlencode(),並把Content-Disposition:attachment改成:

$file_realname = rawurlencode($file_realname);
header("Content-Disposition:attachment; filename = $file_realname; filename*=utf-8''$file_realname");

 

接下來就是使用GET方式傳遞文件路徑和文件名給這個PHP函數,由於文件名中有「&」符號,url中&是有特殊意義的,因此必須進行url編碼(這裏只須要對file_realname編碼便可,file_url在上傳時重命名變成了時間戳格式),並且這裏是文件上傳後沒有刷新頁面,直接就能夠下載,所以不能用PHP的函數,只能是js端使用encodeURIComponent()進行編碼(由於是url局部編碼,不能用encodeURI(),參見:http://www.w3school.com.cn/jsref/jsref_encodeURIComponent.asp

特別說明:以前剛開始寫這篇博客的時候,說的是js端用base64編碼方式對file_realname編碼,後來發現是有瑕疵的,由於base64編碼後有可能會產生+等特殊符號,url提交後得到的參數是空格,所以,在這裏特別更正一下,仍是要用標準的url局部編碼encodeURIComponent()

js生成的下載url大體以下

$('#download').attr('href','www.xxx.com/download?file_url='+res.data.src+'&file_realname='+new Base64().encode($('#file_realname').val()));

通常狀況下,nginx等Web服務器都會自動對url解碼(不管是get仍是post),因此php端無需手動解碼,最終的download函數爲:

public function download(){
    $file_url = $_REQUEST('file_url'); //file_url參數多是個url,也多是文件相對路徑
    $file = '...對$file_url進行處理找到文件絕對路徑';
    $file_realname = $_REQUEST('file_realname'); //file_realname參數是js通過encodeURIComponent()函數編碼過的,提交以後,nginx通常都自動對url進行urldecode()解碼,因此直接接收就OK(若是發現沒自動解碼,就須要咱們手動urldecode)
    $file_realname = rawurlencode($file_realname);  //由於file_realname要傳回瀏覽器,因此還須要url編碼,才能保證不亂嗎,且這裏應該用rawurlencode()而不是urlencode()
    if(!file_exists($file)){
        echo "<script>alert('文件不存在')</script>";
        return;
    }else{
        header("Content-type:application/octet-stream");
        header("Content-Disposition:attachment; filename = $file_realname; 
                filename*=utf-8''$file_realname");
        header("Accept-ranges:bytes");
        header("Accept-length:".filesize($file));
        readfile($file);
    }
}

關於中文亂碼,這裏強烈建議先後端統一使用UTF8

 

4.提交後,再次打開頁面時的原文件名展現

數據庫中我設置了兩個字段,file_url和file_realname,這樣就能夠將文件路徑和文件名分開保存,直接將原始文件名存入file_realname,由於是從新打開的頁面,因此就可使用php的htmlspecialchars函數進行實體編碼了(若是要使用htmlspecialchars過濾單引號和雙引號,還需加上第二個參數ENT_QUOTES)

 

5.提交後,再次打開頁面時按原文件名下載

file_realname直接用rawurlencode編碼便可

<a style="color:#0b76e8" id="download" href="<?php echo (!empty($file_url)?"www.xxx.com/download?file_url=$file_url&file_realname=".rawurlencode($file_realname):'');?>"><?php echo htmlspecialchars($file_realname);?></a>

 

終於寫完了,文字純手打+代碼複製,若是以爲對你有些許幫助,求手抖給個贊

總結一下用到的編碼

1.js 實體編碼

2.php 實體編碼(htmlspecialchars)

3.js url編碼(encodeURIComponent())

4.php url編碼(rawurlencode)

5.本篇最關鍵的:

$file_realname = rawurlencode($file_realname);
header("Content-Disposition:attachment; filename = $file_realname; filename*=utf-8''$file_realname");
相關文章
相關標籤/搜索