php 大數據excel導出 buffer配置

原理:php開啓緩存區輸出 --> php系統緩衝區(nginx, apache服務器) --> 瀏覽器緩衝區

因此,要實現邊查詢,邊下載效果,php, web服務器, 瀏覽器 三個方面的配置都不能少

apache配置: httpd.conf
    由於默認開啓了mod_cgi.so模塊
    在http.conf配置文件最後一行加上, 表示設置緩衝區的字節大小爲0,一旦有php輸出,就立馬響應瀏覽器輸出
FcgidOutputBufferSize 0

注意,nginx也有設置buffer的大小
<?php
/**
 * 導出大數據抽象類
 */
abstract class LargeExportAbstract
{
    protected $filename = 'export.csv';
    protected $chunkNum = 1000;
    private $fp;
    //是否安全導出
    protected $isSafe = false;

    /**獲取Query查詢對象
     * @return Query
     */
    abstract public function getQuery();

    /**設置安全導出, 既相關數據不可看
     * @param bool|true $safe
     * @return $this
     */
    public function setSafeModel($safe = true)
    {
        $this->isSafe = $safe;
        return $this;
    }

    /**第一行導出
     * @return array
     */
    public function beforeRow()
    {
        return [];
    }

    /**數據導出
     * @param $row
     * @return mixed
     */
    public function formatRow($row)
    {
        return $row;
    }

    protected function outPutStart()
    {
        $this->fp = fopen('php://output', 'a');//打開output流
    }
    protected function outPutEnd()
    {
        fclose($this->fp);
    }

    /**
     * 創建IO
     */
    protected function IOStart()
    {
        header('Content-Description: File Transfer');
        header('Content-Type: application/vnd.ms-excel');
        header('Content-Disposition: attachment; filename="'. $this->filename .'"');
        header('Expires: 0');
        header('Cache-Control: must-revalidate');
        header('X-Accel-Buffering: no');  //適用於Nginx服務器環境
        header('Pragma: public');
        //使用流式輸出
        $this->outPutStart();
        //開啓緩衝區
        @ob_clean();
        //一旦有緩衝,立馬輸出
        ob_implicit_flush(true);
    }

    /**
     * 關閉IO
     */
    protected function IOend()
    {
        @ob_end_clean();
        //關閉流式輸出
        $this->outPutEnd();
        exit();
    }

    /**
     * 清空緩衝區, 內容進入下一層緩衝區
     */
    protected function flush()
    {
        @ob_flush();
        @flush();
    }

    /**向excel輸出一行數據
     * @param $row
     */
    public function put($row)
    {
        if ($this->fp)
            fputcsv($this->fp, $row);
    }

    /**導出
     * @param string $filename 可選,文件名
     */
    public function export($filename = '')
    {
        if (!empty($filename)) {
            $this->filename = $filename;
        }

        //創建一個流式IO
        $this->IOStart();

        //數據查詢前向瀏覽器輸出
        $beforeRow = $this->beforeRow();
        if (!empty($beforeRow)) {
            $this->put($beforeRow);
        }
        //數據分批查詢
        $this->getQuery()->chunk((int)$this->chunkNum, function ($rows) {
            foreach ($rows as $row) {
                $row = $this->formatRow($row);
                //存放php緩存衝區
                $this->put($row);
            }
            unset($rows);
            //php緩衝區釋放, 進入下一層緩衝區
            $this->flush();
        });

        //關閉IO
        $this->IOend();
    }

    /**原樣輸出,防止小數導出時爲空 如數字5.00,本應導出 5.00 ,實際導出了 5
     * @param $value
     * @return string
     */
    public function toText($value)
    {
        return $value."\t";
    }
}
<?php
/*
查詢類服務
*/
class RankStatisticService {    
    protected $name;
    public function __construct($id, $inputs){
         ...
    }

    public function getQuery(){
        return GoodModel::where('name', $this->name);
    }

}

/**統計導出
 * Class RankStatistic
 * @package App\Services\Activity\InviteActivity
 */
class RankStatisticExport extends LargeExportAbstract{
    /**導出統計服務
     * @var \App\Services\CpaData\CpaStatisticService
     */
    protected $staticService;
    //活動數據
    protected $activity;

    protected $chunkNum = 2;
    //導出文件名
    protected $filename = '數據統計(排行榜).csv';
    //是否安全導出
    protected $isSafe = false;

    protected $headerRow = ['活動期數', '會員id', '會員所屬分組', '會員手機號', '邀請人數', '排名', '是否獲得獎勵', '獎品名稱', '收貨地址'];


    public function __construct($id, $inputs)
    {
        $this->staticService = (new RankStatisticService($id, $inputs));
        $this->activity = $this->staticService->activityData();
    }

    public function getQuery()
    {
        return $this->staticService->getQuery();
    }

    public function beforeRow()
    {
        return $this->initRow($this->headerRow);
    }

    public function formatRow($v)
    {
        $row = [
            "第{$this->activity->round}期",                //期數
            $v->user_id,               //會員id
            $v->group_name,             //會員手機號
            $this->toText($v->mobile),          //會員所屬分組
            $v->vip_number,               //開通權益會員數量
            $v->user_rank,                 //排名
            $v->reward_zh,          //是否獲得獎勵
            $v->reward_name,       //獎品名稱
            $v->addressDetail       //收貨地址
        ];
        
        return $this->initRow($row);
    }

    /**格式化導出的數據
     * @param $row
     * @return array
     */
    protected function initRow($row) {
        //安全模式下,不導出手機號
        if ($this->isSafe) {
            unset($row[3]);
            $row = array_values($row);
        }
        return $row;
    }
}

/*
客戶端調用
*/
(new RankStatisticExport(1, [])->export();

利用上面的原理,最終的效果如下面視頻: 

鏈接:https://pan.baidu.com/s/1vA_RnveMIUQO8FAQt774ig 
提取碼:7pdj 
複製這段內容後打開百度網盤手機App,操作更方便哦

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章