php 經過header下載中文文件名 壓縮包損壞或文件不存在的問題

開發中你們都是使用的utf8編碼,昨天遇到一個奇坑,本是一件很小的問題,解決也浪費了個吧小時。廢話很少說,植入正題:php

文件下載方式:經過header二進制流文件下載
需求: 文件上傳保留文件名不變
數據字段file_url值:/public/upload/files/2019/04-29/中文測試包.rarlinux

linux(Ubuntu 18.04.2 LTS )文件目錄:/home/wwwroot/web/public/upload/files/2019/04-29
web

windows10文件目錄:D:\web\public\upload\files\2019\04-29\中文測試包.rarwindows

咱們先看下,windows下的文件下載:瀏覽器

<?php
        $file_name = '/public/upload/files/2019/04-29/中文測試包.rar';
        //$file_name = iconv("utf-8","gbk//IGNORE",$file_name); // 特別注意!特別注意!特別注意這裏,windows下必須開轉碼,否則直接文件不存

        $file_path = $_SERVER['DOCUMENT_ROOT'] . $file_name;// 好比windows下這裏個人是 "D:/web/public/upload/files/2019/04-29/中文測試包.rar"
        //判斷若是文件存在,則跳轉到下載路徑
        if (!file_exists($file_path)) {
            die("文件不存在!");
        }

        $fp = fopen($file_path, "r+") or die('打開文件錯誤');   //下載文件必需要將文件先打開。寫入內存
        $file_size = filesize($file_path);
        //返回的文件流
        Header("Content-type:application/octet-stream");
        //按照字節格式返回
        Header("Accept-Ranges:bytes");
        //返回文件大小
        Header("Accept-Length:" . $file_size);
        //彈出客戶端對話框,對應的文件名
        Header("Content-Disposition:attachment;filename=" . substr($file_name, strrpos($file_name, '/') + 1));
        //防止服務器瞬間壓力增大,分段讀取
        $buffer = 1024;
        while (!feof($fp)) {
            $file_data = fread($fp, $buffer);
            echo $file_data;
        }
        fclose($fp);

        die("下載成功!");

?>

文件不存在?神馬玩意?。一樣的代碼ubutun生產環境下:
服務器

文件下載成功。神馬狀況?
緣由:windows 系統默認字符集是gbk,項目採用的是uft8編碼,中文文件名必須轉碼才能使用file_exists檢測文件,否則報找不到文件:app

windows下的解決方式就是上面註釋的那一段開啓:測試

$file_name = iconv("utf-8","gbk//IGNORE",$file_name); // 特別注意!特別注意!特別注意這裏,windows下必須開轉碼,否則直接文件不存

windows下再次執行後發現下載成功:

編碼

那麼問題來了。開啓後的代碼是這樣的:url

<?php
        $file_name = '/public/upload/files/2019/04-29/中文測試包.rar';
        $file_name = iconv("utf-8","gbk//IGNORE",$file_name); // 特別注意!特別注意!特別注意這裏,windows下必須開轉碼,否則直接文件不存
        $file_path = $_SERVER['DOCUMENT_ROOT'] . $file_name;// 好比windows下這裏個人是 "D:/web/public/upload/files/2019/04-29/中文測試包.rar"
        //判斷若是文件存在,則跳轉到下載路徑
        if (!file_exists($file_path)) {
            die("文件不存在!");
        }

        $fp = fopen($file_path, "r+") or die('打開文件錯誤');   //下載文件必需要將文件先打開。寫入內存
        $file_size = filesize($file_path);
        //返回的文件流
        Header("Content-type:application/octet-stream");
        //按照字節格式返回
        Header("Accept-Ranges:bytes");
        //返回文件大小
        Header("Accept-Length:" . $file_size);
        //彈出客戶端對話框,對應的文件名
        Header("Content-Disposition:attachment;filename=" . substr($file_name, strrpos($file_name, '/') + 1));
        //防止服務器瞬間壓力增大,分段讀取
        $buffer = 1024;
        while (!feof($fp)) {
            $file_data = fread($fp, $buffer);
            echo $file_data;
        }
        fclose($fp);

        die("下載成功!");
?>

在ubutun 服務器上咱們執行:

是否是彷彿解決東牆補西牆。ubutun 下字符集能夠經過:

cat /usr/share/i18n/SUPPORTED

說明系統支持中文字符,否則上傳的壓縮包怎麼會顯示:「中文測試包.rar」。
問題描述:linux系統下驗證中文文件file_exists不能是中文,因此不能在上面轉碼成gbk.

那麼問題來了: 如何作到兼容性?
咱們知道PHP_OS是 php自帶的一個內置常量,返回的是服務器端的操做系統標示,值爲(WINNT,WIN32等),好比這樣:

 echo strtoupper(substr(PHP_OS,0,3))==='WIN'?'windows 服務器':'不是 widnows 服務器';

另一種經過系統分隔符DIRECTORY_SEPARATOR ,這個也是php自帶的一個內置常量,用來顯示系統分隔符的命令,

不須要任何定義與包含便可直接使用。在windows下路徑分隔符是\(固然/在部分系統上也是能夠正常運行的),在linux上路徑的分隔符是/,

DIRECTORY_SEPARATOR 這個常量存在的意義就是會根據不一樣的操做系統來顯示不一樣的分隔符。

使用 DIRECTORY_SEPARATOR 判斷操做系統類型好比這樣:

echo DIRECTORY_SEPARATOR=='\\'?'windows 服務器':'不是 widnows 服務器';

還有一種方式:

PATH_SEPARATOR 也是一個常量,在linux系統中是一個" : "號,Windows上是一個";"號。

使用 PATH_SEPARATOR 判斷操做系統類型好比這樣:

echo PATH_SEPARATOR==';'?'windows 服務器':'不是 widnows 服務器';

代碼兼容性咱們能夠驗證系統類型,對windows下作判斷再決定是否轉碼操做。
這裏重點說哈關於下載後文件打開提示「文件損壞」的問題,期初我也遇到。猜想確定是在讀取文件字節流存在數據丟失,也就是沒讀取完整:

下面看下這段有問題的代碼:有興趣的朋友能夠本身思考哈,問題在哪裏?這裏我就不說了,相信不少朋友也能找到問題點:

<?php
        $http_type = ((isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') || (isset($_SERVER['HTTP_X_FORWARDED_PROTO']) && $_SERVER['HTTP_X_FORWARDED_PROTO'] == 'https')) ? 'https://' : 'http://';

        $file_name = '/public/upload/files/2019/04-29/中文測試包.rar';

        //檢測文件是否存在,而且可讀
        if (!is_file($file_name) && is_readable($file_name)) {
            die("文件不存在或不可讀!");
        }

        //判斷若是文件存在,則跳轉到下載路徑
        $file_path = $_SERVER['DOCUMENT_ROOT'] . $file_name;// 好比windows下這裏個人是 "D:/web/public/upload/files/2019/04-29/中文測試包.rar"
        //判斷若是文件存在,則跳轉到下載路徑
        if (!file_exists($file_path)) {
            die("文件不存在!");
        }

        //設置腳本的最大執行時間,設置爲0則無時間限制
        set_time_limit(0);
        ini_set('max_execution_time', '0');
        //經過header()發送頭信息
        //由於不知道文件是什麼類型的,告訴瀏覽器輸出的是字節流
        header('content-type:application/octet-stream');
        //告訴瀏覽器返回的文件大小類型是字節
        header('Accept-Ranges:bytes');

        //得到文件大小
        //$filesize = filesize($filename);//此方法沒法獲取到遠程文件大小

        $header_array = get_headers($http_type . $_SERVER['HTTP_HOST'] . $file_name, true);
        $filesize = $header_array['Content-Length'];
        //告訴瀏覽器返回的文件大小
        header('Accept-Length:' . $filesize);

        //告訴瀏覽器文件做爲附件處理而且設定最終下載完成的文件名稱
        header('content-disposition:attachment;filename=' . substr($file_name, strrpos($file_name, '/') + 1));

        //針對大文件,規定每次讀取文件的字節數爲4096字節,直接輸出數據
        $buffer = 4096;
        $fp = fopen($file_path, 'rb');
        //總的緩衝的字節數
        $sum_buffer = 0;

        //只要沒到文件尾,就一直讀取
        while (!feof($fp) && $sum_buffer < $filesize) {
            echo fread($fp, $buffer);
            $sum_buffer += $buffer;
        }

        //記錄下載
        die("下載成功!");

?>

 

 有興趣的朋友能夠找下bug,哈哈

相關文章
相關標籤/搜索