我正在尝试下载保存在服务器中的文件,并允许用户根据所选日期从前端提取它。该应用程序是使用 codeigniter 框架构建的。 我目前正在使用 codeigniter 的下载助手,它在下载 csv 文件时工作得很好,但 zip 文件似乎总是损坏。我注意到的一件事是原始 zip 大小为 101kb,但下载的 zip 文件大小为 182kb。我已经检查了 mime 类型,确保在发送数据之前清除输出缓冲区,但我无法找出问题所在。我非常感谢您的意见。
API调用:
private function download_get_zip( $year, $month, $day, $type)
{
$date = DateTime::createFromFormat(
'Y-m-d H:i:s', "{$year}-{$month}-{$day} 00:00:00", new DateTimeZone( 'America/New_York' )
);
$archive_file = implode( DIRECTORY_SEPARATOR, [
rtrim( $this->config->item( 'archive_dir' ), '\\/' ),
$date->format( 'Y' ),
$date->format( 'm' ),
$date->format( 'd' ),
'profiles' . ($type === 'legacy' ? '.csv' : '.zip')
] );
if( !file_exists( $archive_file ) ) {
log_message( 'error',
__METHOD__ . ": Could not find '{$archive_file}'"
);
$this->output->set_status_header( 404 );
return;
}
$this->load->helper( 'download' );
header( 'Access-Control-Allow-Origin: *' );
header( 'Access-Control-Allow-Headers: Content-Type' );
force_download( $archive_file, null, true );
}
download_helper.php:
function force_download($filename = '', $data = '', $set_mime = FALSE)
{
if ($filename === '' OR $data === '')
{
return;
}
elseif ($data === NULL)
{
if ( ! @is_file($filename) OR ($filesize = @filesize($filename)) === FALSE)
{
return;
}
$filepath = $filename;
$filename = explode('/', str_replace(DIRECTORY_SEPARATOR, '/', $filename));
$filename = end($filename);
}
else
{
$filesize = strlen($data);
}
// Set the default MIME type to send
$mime = 'application/octet-stream';
$x = explode('.', $filename);
$extension = end($x);
if ($set_mime === TRUE)
{
if (count($x) === 1 OR $extension === '')
{
/* If we're going to detect the MIME type,
* we'll need a file extension.
*/
return;
}
// Load the mime types
$mimes =& get_mimes();
// Only change the default MIME if we can find one
if (isset($mimes[$extension])) {
if ($extension === 'zip') {
// Use the second element of the array, which is 'application/zip'
$mime = is_array($mimes[$extension]) ? $mimes[$extension][1] : $mimes[$extension];
} else {
$mime = is_array($mimes[$extension]) ? $mimes[$extension][0] : $mimes[$extension];
}
}
}
/* It was reported that browsers on Android 2.1 (and possibly older as well)
* need to have the filename extension upper-cased in order to be able to
* download it.
*
* Reference: http://digiblog.de/2011/04/19/android-and-the-download-file-headers/
*/
if (count($x) !== 1 && isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/Android\s(1|2\.[01])/', $_SERVER['HTTP_USER_AGENT']))
{
$x[count($x) - 1] = strtoupper($extension);
$filename = implode('.', $x);
}
if ($data === NULL && ($fp = @fopen($filepath, 'rb')) === FALSE)
{
return;
}
// Clean output buffer
if (ob_get_level() !== 0 && @ob_end_clean() === FALSE)
{
@ob_clean();
}
// Generate the server headers
header('Content-Type: '. $mime);
header('Content-Disposition: attachment; filename="'.$filename.'"');
header('Expires: 0');
header('Content-Transfer-Encoding: binary');
header('Content-Length: '.$filesize);
header('Cache-Control: private, no-transform, no-store, must-revalidate');
// If we have raw data - just dump it
if ($data !== NULL)
{
exit($data);
}
// Flush 1MB chunks of data
while ( ! feof($fp) && ($data = fread($fp, 1048576)) !== FALSE)
{
echo $data;
}
fclose($fp);
exit;
}
}
注意:我对 zip 文件使用 $mimes 的原因是因为 $mimes[$extension][0] 转换为 x-zip 并且我希望保持 mime 尽可能准确。此外,这是我收到的 API 调用结果的响应字符串:
前端导出.js:
const handleZipExport = type => {
message.info('Export started')
menusApi.exportMenus(date).then(
response => {
fileDownload(response, `${date}.zip`)
},
error => {
notification.error({ message: 'Error', description: getErrorMessage(error) })
}
)
}
我得到的响应是一个二进制字符串,我相信我在前端处理数据的方式导致了问题,因为响应似乎是准确的。在前端,我使用“js-file-download”库中的 fileDownload 函数:
declare module 'js-file-download' {
export default function fileDownload(
data: string | ArrayBuffer | ArrayBufferView | Blob,
filename: string,
mime?: string,
bom?: string
): void;
}
我尝试下载的原始 zip 文件可以完美解压,包含正确的内容,但正在下载的 zip 文件已损坏。我尝试切换浏览器以查看是否是与浏览器相关的问题,但这似乎也没有帮助。请让我知道您的意见,我非常感谢您的宝贵时间。
我自己找出了解决方案。我的怀疑是正确的,问题与我在前端处理数据的方式有关。对我有用的方法是使用 xhr 来处理请求并将信息转换为 blob。
const handleZipExport = type => {
message.info('Export started')
const fullURL = `the url where you are requesting zip from`
var xhr = new XMLHttpRequest()
xhr.open('GET', fullURL, true) // Adjust the URL based on your API
xhr.responseType = 'blob'
xhr.onload = function() {
if (xhr.status === 200) {
var blob = xhr.response
fileDownload(blob, `${date}.zip`, 'application/zip')
} else {
notification.error({ message: 'Error', description: 'Failed to export zip' })
}
}
xhr.onerror = function() {
notification.error({ message: 'Error', description: 'Failed to make the zip export request.' })
}
xhr.send()
}