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

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

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);
    }
}

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

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

最近的项目需求

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

 

需求分析

1.文件上传

2.上传后文件名展示

3.上传后按原文件名直接下载

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

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

 

解决方案

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;
}

766a3a1fdb5c71bc0e69f43a7bcdc59e71e.jpg

-----重点来了------

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");

 

转载于:https://my.oschina.net/u/3477605/blog/1810886

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值