文件名含有特殊字符、文件名乱码,如何实现下载——PHP、JS

先上代码,看懂的可直接方便复制

public function download(){
    $file_url = $_REQUEST('file_url'); //file_url可能是个url,也可能是文件相对路径
    $file = '...对file_url进行处理找到文件绝对路径';
    $file_realname = base64_decode($_REQUEST('file_realname')); //base64解码
    $file_realname = rawurlencode($file_realname);  //url编码
    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);
    }
}

分割线下面是详细原理和过程

-----------------------------------------------------------------------------------------------------------

最近的项目需求

实现文件上传(文件名为:&ap;你好;123.rar),且上传后要求立刻展示文件名,并可以点击文件名直接进行下载,点击确定提交保存到数据库。(说明:上传后不能立刻刷新页面,因为此时还未确定提交,而且服务器上的文件名已经是时间戳编码后的格式1526019720.rar

需求分析

1.文件上传

2.上传后文件名展示

3.上传后文件直接下载

4.提交后,再次打开页面时的文件名展示

5.提交后,再次打开页面时的文件下载

解决方案

1.文件上传

上传的问题好解决,网上的js插件很多

2.上传后文件名展示

由于我们用的是php公共upload函数,上传后返回的时候直接就把文件名原原本本返回给js了,并没有进行html实体编码,所以文件名中有&特殊字符,直接展示会有乱码,必须对文件名进行实体编码,此时已经请求返回,没有新的请求,没办法执行php代码,只能用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.上传后文件直接下载

由于服务器上的文件名已变成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在上传时重命名变成了时间戳格式),并且还要支持解码,所以就不能用md5这种,而且这里是文件上传后没有刷新页面,直接就可以下载,又不能用PHP的函数,只能是js,所以就想到了base64,参见:https://blog.csdn.net/u011127019/article/details/51673230,为了方便复制,我直接贴出代码:

//1.加密  
var str = '124中文内容';  
var base = new Base64();  
var result = base.encode(str);  
//document.write(result);  
  
//2.解密  
var result2 = base.decode(result);  
document.write(result2);  


function Base64() {

    // private property
    _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

    // public method for encoding
    this.encode = function (input) {
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;
        input = _utf8_encode(input);
        while (i < input.length) {
            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);
            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;
            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }
            output = output +
                _keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
                _keyStr.charAt(enc3) + _keyStr.charAt(enc4);
        }
        return output;
    }

    // public method for decoding
    this.decode = function (input) {
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;
        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
        while (i < input.length) {
            enc1 = _keyStr.indexOf(input.charAt(i++));
            enc2 = _keyStr.indexOf(input.charAt(i++));
            enc3 = _keyStr.indexOf(input.charAt(i++));
            enc4 = _keyStr.indexOf(input.charAt(i++));
            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;
            output = output + String.fromCharCode(chr1);
            if (enc3 != 64) {
                output = output + String.fromCharCode(chr2);
            }
            if (enc4 != 64) {
                output = output + String.fromCharCode(chr3);
            }
        }
        output = _utf8_decode(output);
        return output;
    }

    // private method for UTF-8 encoding
    _utf8_encode = function (string) {
        string = string.replace(/\r\n/g,"\n");
        var utftext = "";
        for (var n = 0; n < string.length; n++) {
            var c = string.charCodeAt(n);
            if (c < 128) {
                utftext += String.fromCharCode(c);
            } else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            } else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        }
        return utftext;
    }

    // private method for UTF-8 decoding
    _utf8_decode = function (utftext) {
        var string = "";
        var i = 0;
        var c = c1 = c2 = 0;
        while ( i < utftext.length ) {
            c = utftext.charCodeAt(i);
            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            } else if((c > 191) && (c < 224)) {
                c2 = utftext.charCodeAt(i+1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            } else {
                c2 = utftext.charCodeAt(i+1);
                c3 = utftext.charCodeAt(i+2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }
        }
        return string;
    }

js生成的下载url大致如下

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

同时download函数中需要解码,最终的download函数为:

public function download(){
    $file_url = $_REQUEST('file_url'); //file_url可能是个url,也可能是文件相对路径
    $file = '...对file_url进行处理找到文件绝对路径';
    $file_realname = base64_decode($_REQUEST('file_realname')); //base64解码
    $file_realname = rawurlencode($file_realname);  //url编码
    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);
    }
}

如果是POST,最好也用js进行base64编码,这样一个download函数就都支持了。

关于中文乱码,这里强烈建议前后端统一使用UTF8

4.提交后,再次打开页面时的文件名展示

数据库中我设置了两个字段,file_url和file_realname,这样就可以将文件路径和文件名分开保存,直接将原始文件名存入file_realname,因为是重新打开的页面,所以就可以使用php的htmlspecialchars函数进行实体编码了(如果要使用htmlspecialchars过滤单引号和双引号,还需加上第二个参数ENT_QUOTES)

5.提交后,再次打开页面时的文件下载

file_realname直接用base64_encode编码即可

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

终于写完了,文字纯手打+代码复制,如果觉得对你有些许帮助,求手抖给个赞

总结一下用到的编码

1.js 实体编码

2.php 实体编码(htmlspecialchars)

3.js base64编码

4.php base64编码+解码(base64_encode)

5.php url编码(rawurlencode)

6.本篇最关键的:

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

猜你喜欢

转载自my.oschina.net/u/3477605/blog/1810886