PHP 强制下载 .xlsx 文件损坏

6

我正在处理一个允许老师上传文档以供学生下载的网站。然而,现在出现了一个问题。当下载Microsoft Word (.docx)文件时,一切都很完美;但是当下载Excel(.xlsx)文件时,Excel会弹出“此文件已损坏,无法打开”对话框。非常感谢您提供任何帮助!

我的下载代码如下:

case 'xlsx':

    header('Content-type: application/vnd.openxmlformats-officedocument.spreadsheetml.sheet');
    header('Content-Disposition: attachment; filename="' . $filename . '"');
    header('Content-Transfer-Encoding: binary');
    header('Expires: 0');
    header('Pragma: no-cache');
    readfile('./uploads/resources/courses/' . $filename);

break;

2
输出的内容中是否有任何空格,例如在开标签之前?是否有任何警告/提示,这些会使文件损坏。从FTP源下载文件并打开以验证原始文件未被损坏。 - MrCode
2
空格不会影响服务器上的文件...最有可能导致问题的是空格、BOM 或来自脚本的回显消息/警告/错误,它们会与文件本身一起发送到 php://output。 - Mark Baker
1
现在真的很困惑!Notepad++说这两个文件是匹配的! - cbenjafield
你是如何使用Notepad++对比文件的?我不认为它有二进制比较实用程序。相反,使用diff吧,这至少可以告诉你文件是否不同。 - dualed
我曾经遇到过这个问题,把 readfile(); 后面加上 exit; 就解决了! - Nick Rolando
显示剩余9条评论
6个回答

3

我遇到了一个问题,与BOM有关。

如何发现它

解压缩:使用解压缩命令检查输出文件时,我在第二行看到了一个警告。

$ unzip -l file.xlsx 
Archive:   file.xlsx
warning file:  3 extra bytes at beginning or within zipfile
...

xxd (十六进制查看器):我使用以下命令查看了前5个字节

head -c5 file.xlsx | xxd -g 1
0000000: ef bb bf 50 4b                             PK...

注意前三个字节ef bb bf,它是BOM(字节顺序标记)!

为什么会出现这种情况?

可能是一个包含BOM的php文件或者来自库的以前输出。

你需要找到带有BOM的文件或命令所在的位置。对于我而言,我现在没有时间去找它,但是我通过输出缓冲区解决了这个问题。

<?php
ob_start();

// ... code, includes, etc

ob_get_clean();
// headers ...
readfile($file);

2

就我个人而言,在本地xampp设置中,无论扩展名如何,这都可以正常工作,因此我认为不需要使用case语句,除非我漏掉了什么。

我已经测试过docx、accdb、xlsx、mp3等文件格式...

$filename = "equiv1.xlsx";

header('Content-type: application/octet-stream');
header('Content-Disposition: attachment; filename="' . $filename . '"');
header('Content-Transfer-Encoding: binary');
header('Expires: 0');
header('Pragma: no-cache');

谢谢,但是它给了我一个更长的错误信息,说它不是正确的格式! - cbenjafield
1
你是如何存储文件的?你会更改它们的名称或扩展名吗?如果你尝试打开一个你确定是xlsx格式的文件,但出现错误,那么你在服务器上如何首先保存文件呢? - cristi _b
好的,用户上传文件后,脚本会获取 $_FILES['resource']['name'],移除扩展名,将空格替换为下划线,添加四位数字并添加扩展名。然后使用 move_uploaded_file() 将其从 tmp_name 移动到新路径。但是,当我进入服务器上的上传路径文件夹时,新上传的文件可以正常打开,只有在下载时才会出现问题。 - cbenjafield
我已经删除了文件名的处理,现在它只是将原始文件存储在文件夹中。我删除了开关块,但它已经恢复到短错误消息。 - cbenjafield
新代码就像你写的那样简单,但是我在想实际文件路径并没有被指定。文件名将会是“excel1.xlsx”,但这不是路径... - cbenjafield
显示剩余5条评论

0

尝试添加额外的标题

header('Content-Length: ' . filesize('./uploads/resources/courses/' . $filename));

0

尝试:

<?
//disable gzip
@apache_setenv('no-gzip', 1); 
//set download attachment
header('Content-Disposition: attachment;filename="filename.xlsx"');
//clean the output buffer 
ob_clean(); 
//output file
readfile('filepath/filename.xlsx');
//discard any extra characters after this line
exit; 
?>

0

可能是Windows提供的非常误导性的信息,与代码、Excel库或服务器无关,文件本身是正确的。Windows会阻止打开从互联网下载的某些文件(如.xlsx),而不是询问您是否要打开不安全的文件,它只会写入文件已损坏。在Windows 10中,需要右键单击该文件并选择“取消锁定”(例如,您可以在此处阅读更多信息:https://winaero.com/blog/how-to-unblock-files-downloaded-from-internet-in-windows-10/)。


0

试一下这个:

header("Content-Disposition: attachment; filename=\"$filename\"");
header("Content-Type: application/vnd.ms-excel");

1
那不是 .xlsx 的 MIME 类型。https://dev59.com/bm855IYBdhLWcg3wq2TL#4212908 - gen_Eric
这也没有任何区别。 - cbenjafield

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接