PHP导出大量数据,保存为CSV文件

PHP技术
377
0
0
2022-04-11
几乎每个项目有大量导出数据的需求,一口气全部导出消耗服务器资源,甚至可以导致服务器崩溃.

摘要

每个项目架构都不一样,有的一抹(ma)胡,一个服务器什么都装mysql, redis, memcached,这样很省钱但是横向扩展的时候就很头疼.有的分布式部署,包括redis, mysql都是集群方式,比如reids有很多集群节点,mysql集群读写分离,都可以根据自己需求横向扩展.

CSV导出大量数据限制瓶颈有很多,比如项目架构,服务器配置,mysql配置,代码质量,sql语句等等.一抹(ma)胡式部署,本文可能改善不是很大,毕竟mysql还是要占用服务器资源,大家可以相互学习,共通进步.

代码部分

/**
* @description导出CSV文件
* @return
*/
public function exportCsv()
{ 
   /**
    * 第一步打开一个文件下载流
    * 这一步很重要,很多人都不知道这样
    */
    $strFileName = date('Ymd');// 文件名 可以随意ob_clean();//清理缓冲区 避免开头出现空行
    $fp = fopen('php://output', 'a'); //打开output流header('X-Accel-Buffering: no'); //直接下载文件,这行可以不配置,会默认为4096k才会下载,header('Content-Type: application/octet-stream');header($this->changeFileNameWithAgent($strFileName)); //设置头部文件名 解决非英文乱码(中文/日文测试都可以)header("Content-type:text/csv;");

    /**
    * 第二步导出数据,很简单也很重要
    * 代码很简单,第一反应 就这~~~~
    * 实际写错很容易服务器崩溃
    */// 输出第一行标题 (根据实际情况,可有可无)
    $arrayHeader = ["标题1", "标题2", "标题3", "标题4", "标题5"];

    // 转义为预定字符集, 根据需求转字符集(不需要转化可以忽略该行代码),// 将内部字符集转为SJIS-win,一般项目为UTF-8字符集// mb_internal_encoding() 设置或获取内部字符集,有兴趣的同学可以去看看mb_convert_variables('SJIS-win', mb_internal_encoding(), $arrayHeader);fputcsv($fp, $arrayHeader, ","); 
    //初始化必要参数
    $intOffset = 0; // mysql中offset参数
    $intPageSize = 20; // 查询条数 根据实际情况更换
    $blnFinishedFlg = false;do {// 根据 $intOffset $intPageSize查询数据库,少量多次查询
        $arrayData = [['内容1-1', '内容1-2', '内容1-3', '内容1-4', ...],['内容2-1', '内容2-2', '内容2-3', '内容2-4', ...],['内容3-1', '内容3-2', '内容3-3', '内容3-4', ...],...];// 输出到csv文件foreach($arrayData as $item) {mb_convert_variables('SJIS-win', mb_internal_encoding(), $item);fputcsv($fp, $item, ",");ob_flush();flush();}if ($intPageSize === count($arrayData)) {// 获取到的结果数量等于查询条数,认为未取完数据继续查询, 修改offset值
            $intOffset = $intOffset + $intPageSize;
            $blnFinishedFlg = false;} else {//小于查询条数,数据已取完
            $blnFinishedFlg = true;}} while (false === $blnFinishedFlg);

    /**
    * 第三步关闭文件流
    */ob_end_flush();fclose($fp);
}

/**
* @description 对应不同浏览器、输出汉字名出问题的Bug
* 由于IE Edge 包含Chrome等信息、所以只能第一个判断
* @param string $strFileName 文件名
* @return string Head信息
*/
private function changeFileNameWithAgent($strFileName)
{
    $filename = preg_replace('/[\?\*\|\\/\:"><]/', '', $strFileName);
    $agent = $_SERVER['HTTP_USER_AGENT'];
    $strHeader = '';if (strpos($agent, "Edge")) {
        $filename = urlencode($filename);
        $filename = str_replace("+", "%20", $filename);
        $strHeader = "Content-Disposition: attachment; filename=" . $filename;} else if (strpos($agent, "Firefox")) {
        $filename = urlencode($filename);
        $filename = str_replace("+", "%20", $filename);
        $strHeader = 'Content-Disposition: attachment; filename*="utf8\'\'' . $filename . '"';} else if (strpos($agent, "Chrome")) {
        $strHeader = "Content-Disposition: attachment; filename=" . $filename;} else if (strpos($agent, "Safari")) {
        $strHeader = 'Content-Disposition: attachment;filename*=UTF-8\'\'' . rawurlencode($filename);} else {
        $filename = urlencode($filename);
        $filename = str_replace("+", "%20", $filename);
        $strHeader = "Content-Disposition: attachment; filename=" . $filename;}return $strHeader;
}

拓展

拿着导出的csv文件,这个时候有的人就要问了,为什么我用excel打开乱七八糟.

如果是开发人员问,wo giao!!,新员工可以理解,老员工可以劝退了.其实我也不知道为什么会乱码,可能跟那些奇怪的字符集有关系.作为一个正常的开发人员会用notepad++等编辑器打开,查看有没有问题.一般不会有问题,如果这个有乱码问题就需要修改字符集了.

但是有的人就开始犟嘴了,那客户不懂代码啊,我又不能要求人家安装编辑器呀!wo giao,内部出现奸细了.这种奸细行为的需求,你就可以理直气壮的告诉他,满足不了,因为你的字符集决定了他永远都有可能乱码,用UTF-8字符集,才有可能不乱码.除非导出包含bom的UTF-8格式,用excel打开不会乱码.

// BOM header UTF-8 
// 加在第一步后面
echo pack('C*',0xEF,0xBB,0xBF);

// 更换这个为UTF-8, 如果是uft8字符集可以删除这两行代码
mb_convert_variables('UTF-8', mb_internal_encoding(), $arrayHeader);