文件名含有特殊字符、文件名乱码,如何实现下载——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");
相关文章
相关标签/搜索