对于许多的从事数据智能开发的同僚来说,从库中提取出数据后进行数据整理并且导出csv文件的功能是很常见的,导出一个csv文件方便用其他的数据工具进行分析。所以在这里分享一下我在工作过程中实现导出csv文件功能的历程与所获。
前言
首先,我被告知需要在laravel框架中实现下载接口这个任务时,整个人是懵逼的,完全不知道怎么着手去实现这个功能,但是对于一个理工科生来说,碰到问题并不可怕,剥丝抽茧,一步一步来。我分析,既然要实现下载功能接口,首先需要做的就是提供一个接口,而如何做一个接口我在<<Laravel使用心得--简易路由操作>>中已介绍,向前端提供一个URI即达到了接口的意义,其次是如何实现下载,最后是如何写入一个csv文件,本篇文章就从后两个方向介绍,并且最后附带PHP中文件打包功能的实现介绍
本来想打包功能单独写一篇博客的,后来发现这个功能实现比较简单,而深层次的我也暂时不会,就附带本篇文章最后了
下载
一、通过传递HTTP报头实现下载
首先在度娘上找到的实现下载的方式之一:是通过向浏览器传递HTTP报头,告诉浏览器这个URI的相关动作让浏览器去实现。
HTTP报头是HTTP协议的一个部分,一般上用于客户端和服务端之间握手时的通信,通俗的理解就是 http服务器和客户端(一般为浏览器)之间数据传输之前的对话,告诉浏览器你想干什么。
而在PHP中实现HTTP报头参数传递功能的是header()方法,header() 函数向客户端发送原始的 HTTP 报头。其中认识到一点很重要,即必须在任何实际的输出被发送之前调用 header() 函数,例如在调用header()函数前不要写print_r()或var_dump()等函数。
传递报头参数的代码:
header("Content-type:text/csv");
header("Content-Disposition:attachment;filename=" . $start_date . '~' . $end_date . '_fare.csv');
header('Cache-Control:must-revalidate,post-check=0,pre-check=0');
header('Expires:0');
header('Pragma:public');
其中第一行是告诉浏览器我需要导出文件,格式是csv,在Content-type
这个参数类型中可以指定许多的导出文本的格式,例如rar、zip这样的压缩包格式
传递这样的报头后,导出的文件的内容将是你在调用该header()函数的方法内的return值,例如return 123;
则csv文件中就是123。
这种方式可以实现下载,但是总归看上去不太好看,如此优秀的laravel框架怎么可能会不涉及到下载方法的封装呢,于是后来使用了另一种方法。
二、通过response方法实现下载
在看了其他前辈写的代码中,我发现有一行代码
return reponse()->download($file)
看单词意思也知道这行代码是起什么作用的。Response是laravel框架中的门面(facade),在这个框架中是可以直接引用调用的功能
例如:
//响应重定向
Route::get('example/test24', function(){
return Redirect::to('example/test21')->with('username', 'xiaoming');
});
//定制HTTP响应
Route::get('example/test21', function(){
return Response::make('内容不存在', 404);
});
//响应视图
Route::get('example/test22', function(){
return Response::view('test22');
});
以上的例子是response在封闭函数中的直接调用,在其他的地方自然也是可以直接使用的,而下载文件就可以使用Response::download()
方法
我们先看一下这个的源代码:
/**
* Create a new file download response.
*
* @param \\SplFileInfo|string $file
* @param string $name
* @param array $headers
* @param string|null $disposition
* @return \\Symfony\\Component\\HttpFoundation\\BinaryFileResponse
*/
public function download($file, $name = null, array $headers = [], $disposition = 'attachment');
可以看到这个下载方法的参数,有$file, $name = null, array $headers = [], $disposition = 'attachment'
,后面都有默认值,可以不传递,也可以用于参数扩展,利用这个方法就可以实现下载
例如:
public function getDownload()
{
//PDF file is stored under project/public/download/info.pdf
$file= public_path(). "/download/info.pdf";
$headers = array(
'Content-Type: application/pdf',
);
return Response::download($file, 'filename.pdf', $headers);
}
由此处的header()可以看出是要求下载一个pdf文件,而在laravel 5框架中使用此功能还可以使用
return response()->download($file, 'filename.pdf', $headers);
这种方式,功能是一样的,其中也可以只指定第一个参数,这样下载的文件就是你的文件之前指定好的类型。
写入csv文件方法
介绍了如何实现下载的两种方法,现在来说一下如何将数据写入csv文件
字符串连接方法
首先要知道,csv文件的内容其实就是一串拼接起来的字符串,起始指定好表头字符串,后面就以该表头的顺序依次拼接数据即可,只是在表头和每一行数据的末尾都添加一个换行符\\n
来达到表格对齐的效果即可。
例如:
$head_str = "日期,姓名,年龄,学校\\n";
$cnt = count($data);
for ($i =0;$i<$cnt;$i++) {
$tmp = implode(",",$data[$i]);
$head_str .= $tmp."\\n";
}
其中$data
是从库中取出的数据的二维数组,而每一个第一层索引指向的就是对应的每一行数据,然后利用for
循环遍历取出每一行数据进行拼接。
这一种方法是和传递HTTP报头实现下载的方法配合使用效果更好,因为在拼接完成后直接在方法内return $head_str
,就能将整个数据内容读入到了下载的csv文件中。当然,也可以使用fwrite()
方法写入一个新文件$file
,然后利用response->download($file)
方法下载该文件即可。
export()方法
后来发现,每次这样拼接数据非常的麻烦,可以写一个公共的方法,以便在其他地方实现类似的功能时可以直接调用
public static function exportData($data = array(), $title = [])
{
$new_data = [];
if (!empty($data)) {
if(empty($title))
{
foreach ($data as $key => $val) {
$new_data[$key] = isset($val) ? mb_convert_encoding($val, 'gbk', 'utf-8') : '';
}
} else {
foreach ($title as $key => $val) {
$new_data[$key] = isset($data[$key]) ? mb_convert_encoding($data[$key], 'gbk', 'utf-8') : '';
}
}
$str = implode(',', $new_data);
fwrite(self::$fp, $str."\\n");
}
}
这个方法的实现原理是将数据进行转码处理然后利用fwrite()
方法写入一个新文件。其中self::$fp
是指定的文件的路径,这个php手册上看一下fwrite()
方法的介绍就能晓得参数的意思。写入了新的文件后就可以再通过reponse->download()
方法来下载了。
php文件打包教程
在数据量非常庞大时,一次性取出大量的数据然后写入csv文件再下载的这个流程是不适用的,因为数据量庞大会导致取数时间很长,命令运行超出内存。此时可以采用的方法就是将大量的数据按时间维度写入多个csv文件,然后再根据需要的时间区间将多个csv文件打包下载即可,所以在这也讲一下我如何实现文件打包。
在php中,利用的是ZipArchive()
类,通过这个类的实例化来实现打包。
依然是感兴趣的同学自行在php手册上学习
代码:
//获取zip包名
$zip_file = $save_path . '/' . $start_date . '-' . $end_date . '.zip';
if (file_exists($zip_file)) {
return response()->download($zip_file);
}
//文件打包
$zip = new ZipArchive();
if ($zip -> open($zip_file, ZipArchive::CREATE) == true) {
foreach ($file_dir as $file) {
if (file_exists($file)) {
$zip -> addFile($file, basename($file));
}
}
}
$zip -> close();
这样就实现了打包,其中$file_dir
变量是你要打包的文件的路径数组,里面包含所有你想打包的文件路径,$zip_file
变量是你想打包成zip文件的包的路径+名称。
有的同学在使用此方法时有时会不管用,以我的经验,一般都是文件的路径不正确,或者是没有指定绝对路径
注意:在$zip -> addFile()
方法中不要使用路径变量拼接,最好在使用该方法前就写好路径。使用了拼接不会报错,但是依然会文件添加不进去,这里是一个大坑,我找了好久才发现。
最后:本人新手程序员,一起进步!!!