<?php  
  
/**  
 * Created by : PhpStorm 
 * User: Chen 
 * Date: 2022/5/11 
 * Time: 15:58 
 * */  
namespace app\common\util;  
  
use DateTime;  
use Exception;  
use PHPMailer\PHPMailer\PHPMailer;  
use PhpOffice\PhpSpreadsheet\Cell\DataType;  
use PhpOffice\PhpSpreadsheet\Spreadsheet;  
use PhpOffice\PhpSpreadsheet\Writer\Xlsx;  
use RecursiveDirectoryIterator;  
use RecursiveIteratorIterator;  
use SplFileInfo;  
use think\facade\Env;  
use ZipArchive;  
  
/**  
 * 工具类  
 */  
class ChenUtils  
{  
    /**  
     * 将天数换算成年月日  
     */  
    public static function convert($sum)  
    {  
        $years = floor($sum / 365);  
        $months = floor(($sum - ($years * 365)) / 30);  
        $days = ($sum - ($years * 365) - ($months * 30));  
        $str = '';  
  
        if ($years != 0) {  
            $str .= $years . '年';  
        }  
        if ($months != 0) {  
            $str .= $months . '月';  
        }  
        if ($days != 0) {  
            $str .= $days . '天';  
        }  
        return $str;  
    }  
  
  
    public static function isEmail($email)  
    {  
        $pattern = '/^[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$/';  
        if (preg_match($pattern, $email)) {  
            return true;  
        } else {  
            return false;  
        }  
    }  
  
    // 传时间戳返回周几  
    function getWeek($time)  
    {  
        $week = date('w', $time);  
        switch ($week) {  
            case 1:  
                $week = '周一';  
                break;  
            case 2:  
                $week = '周二';  
                break;  
            case 3:  
                $week = '周三';  
                break;  
            case 4:  
                $week = '周四';  
                break;  
            case 5:  
                $week = '周五';  
                break;  
            case 6:  
                $week = '周六';  
                break;  
            case 0:  
                $week = '周日';  
                break;  
        }  
        return $week;  
    }  
  
    /**  
     * 传html过来返回第一张图片src  
     */    public static function getFirstImg($html)  
    {  
        $img = '';  
        $pattern = '/<img.*?src="(.*?)".*?>/';  
        preg_match($pattern, $html, $match);  
        if (isset($match[1])) {  
            $img = $match[1] ?? '';  
        }  
        return $img;  
    }  
  
    /**  
     * 传html返回所有图片src  
     */    public static function getAllImg($html)  
    {  
        $img = [];  
        $pattern = '/<img.*?src="(.*?)".*?>/';  
        preg_match_all($pattern, $html, $match);  
        if (isset($match[1])) {  
            $img = $match[1] ?? [];  
        }  
        return $img;  
    }  
  
    /**  
     * 传富文本过来返回第一段文字  
     */  
    public static function getFirstText($html)  
    {  
        $text = '';  
        /*        $pattern = '/<p.*?><span.*?>(.*?)<\/span><\/p>/';*/  
        $pattern = '/<p.*?>(.*?)<\/p>/';  
        preg_match($pattern, $html, $match);  
        if (isset($match[1])) {  
            $text = $match[1];  
        }  
        // 判断是否有文字, 没有则尝试截取p标签内的中文  
        if ($text == '') {  
            $pattern = '/<p.*?>[\u4e00-\u9fa5]<\/p>/';  
            preg_match($pattern, $html, $match);  
            if (isset($match[1])) {  
                $text = $match[1];  
            }  
        }  
        return $text;  
    }  
  
    /**  
     * 时间字段处理  
     *  
     * @param $time  
     * @return string  
     */    public static function timeTran($time)  
    {  
        $nowTime = time();  
        $showTime = strtotime($time);  
        $difference = $nowTime - $showTime;  
        if ($difference < 0) {  
            return $time;  
        }  
        if ($difference < 60) {  
            return $difference . '秒前';  
        }  
        if ($difference < 3600) {  
            return floor($difference / 60) . '分钟前';  
        }  
        if ($difference < 86400) {  
            return floor($difference / 3600) . '小时前';  
        }  
        if ($difference < 2592000) {  
            return floor($difference / 86400) . '天前'; //30天内  
        }  
        if ($difference < 31104000) {  
            return floor($difference / 2592000) . '个月前'; //12个月内  
        }  
        return floor($difference / 31536000) . '年前';  
    }  
  
    /**  
     * 检查身份证号码是否正确  
     * @param $id_card  
     * @return boolean  
     */    public static function idCardCheck($id_card)  
    {  
        if (empty($id_card)) {  
            return false;  
        }  
        $id_card = strtoupper($id_card);  
        $regx = "/(^\d{15}$)|(^\d{17}([0-9]|X)$)/";  
        $arr_split = array();  
        if (!preg_match($regx, $id_card)) {  
            return false;  
        }  
        //检查15位  
        if (15 == strlen($id_card)) {  
            $regx = "/^(\d{6})+(\d{2})+(\d{2})+(\d{2})+(\d{3})$/";  
            @preg_match($regx, $id_card, $arr_split);  
            //检查生日日期是否正确  
            $dtm_birth = "19" . $arr_split[2] . '/' . $arr_split[3] . '/' . $arr_split[4];  
            if (!strtotime($dtm_birth)) {  
                return false;  
            } else {  
                return true;  
            }  
        } else {  
            //检查18位  
            $regx = "/^(\d{6})+(\d{4})+(\d{2})+(\d{2})+(\d{3})([0-9]|X)$/";  
            @preg_match($regx, $id_card, $arr_split);  
            $dtm_birth = $arr_split[2] . '/' . $arr_split[3] . '/' . $arr_split[4];  
            if (!strtotime($dtm_birth)) {  
                return false;  
            } else {  
                //检验18位身份证的校验码是否正确。  
                //校验位按照ISO 7064:1983.MOD 11-  
                //数字代码:012345678910X98765432  
                $arr_int = array(7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2);  
                $arr_ch = array('1', '0', 'X', '9', '8', '7', '6', '5', '4', '3', '2');  
                $sign = 0;  
                for ($i = 0; $i < 17; $i++) {  
                    $b = (int)$id_card[$i];  
                    $w = $arr_int[$i];  
                    $sign += $b * $w;  
                }  
                $n = $sign % 11;  
                $val_num = $arr_ch[$n];  
                if ($val_num != substr($id_card, 17, 1)) {  
                    return false;  
                } else {  
                    return true;  
                }  
            }  
        }  
    }  
  
  
    /**  
     * 检查营业执照(社会统一信用代码)是否正确  
     * @param $id_card  
     * @return bool  
     */    public static function businessLicenseCheck($business_license)  
    {  
        if (empty($business_license)) {  
            return false;  
        }  
        $business_license = strtoupper($business_license);  
        $regx = "/^[0-9A-Z]{15}$|^[0-9A-Z]{18}$|^[0-9A-Z]{20}$/";  
        if (!preg_match($regx, $business_license)) {  
            return false;  
        }  
        return true;  
    }  
  
    /**  
     * 生成唯一订单号  
     * @form 表名  
     * @order_no 订单号字段名  
     */  
    public static function getOrderSn($form, $order_no)  
    {  
        $order_sn = ChenUtils . phpdate('Ymd') . substr(implode(NULL, array_map('ord', str_split(substr(uniqid(), 7, 13), 1))), 0, 8);  
        $form = db($form)->where($order_no, $order_sn)->find();  
        if ($form) {  
            return self::getOrderSn($form, $order_no);  
        } else {  
            return $order_sn;  
        }  
    }  
  
    /**  
     * [生成随机字符串]  
     * @param integer $length [生成的长度]  
     * @param integer $type [生成的类型]  
     * @php 随机码类型:0,数字+大写字母;1,数字;2,小写字母;3,大写字母;4,特殊字符;-1,数字+大小写字母+特殊字符  
     */  
    public static function randCode($length = 5, $type = 0)  
    {  
        $arr = array(1 => "0123456789", 2 => "abcdefghijklmnopqrstuvwxyz", 3 => "ABCDEFGHIJKLMNOPQRSTUVWXYZ", 4 => "~@#$%^&*(){}[]|");  
        if ($type == 0) {  
            array_pop($arr);  
            $string = implode("", $arr);  
        } else if ($type == "-1") {  
            $string = implode("", $arr);  
        } else {  
            $string = $arr[$type];  
        }  
        $count = strlen($string) - 1;  
        for ($i = 0; $i < $length; $i++) {  
            $str[$i] = $string[rand(0, $count)];  
            $code .= $str[$i];  
        }  
        return $code;  
    }  
  
    /**  
     * 生成唯一字符串  
     * @param string $prefix [前缀]  
     * @param int $type [字符串的类型]  
     * @param int $length [字符串的长度]  
     * @param int $time [是否带时间1-带,0-不带]  
     * @return string  
     */    public static function randSole(string $prefix, int $type = 0, int $length = 18, int $time = 0, $dao = null): string  
    {  
        $str = $time == 0 ? '' : date('YmdHis', time());  
        $charSets = [  
            '0123456789', // 数字  
            'abcdefghijklmnopqrstuvwxyz', // 小写字母  
            'ABCDEFGHIJKLMNOPQRSTUVWXYZ', // 大写字母  
            '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', // 数字 + 小写 + 大写  
            '!@#$%^&*()_+=-~`', // 特殊字符  
            '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*()_+=-~`' // 数字 + 小写 + 大写 + 特殊字符  
        ];  
  
        if (isset($charSets[$type])) {  
            $rand = $charSets[$type];  
            $randLength = strlen($rand);  
            for ($i = mb_strlen($str); $i < $length; $i++) {  
                $str .= $rand[mt_rand(0, $randLength - 1)];  
            }  
        }  
  
        $cacheKey = $prefix . $str;  
        if (cache($cacheKey)) {  
            return self::randSole($prefix, $type, $length, $time);  
        } else {  
            cache($cacheKey, $str, 3600);  
            return $str;  
        }  
    }  
  
    /**  
     * 加密解密方法  
     * @param string $string 需要加密的字段  
     * @param string $code 约定的密钥  
     * @param bool $operation 默认false表示加密,传入true表示解密  
     * @return false|string  
     */    public static function secret(string $string, string $code, bool $operation = false)  
    {  
        $code = md5($code);  
        $iv = substr($code, 0, 16);  
        $key = substr($code, 16);  
        if ($operation) {  
            return openssl_decrypt(base64_decode($string), "AES-128-CBC", $key, OPENSSL_RAW_DATA, $iv);  
        }  
        return base64_encode(openssl_encrypt($string, "AES-128-CBC", $key, OPENSSL_RAW_DATA, $iv));  
    }  
  
    /**  
     * 压缩指定文件夹并解压到指定文件夹  
     * @param string $path 压缩文件夹路径  
     * @param string $outputPath 解压文件夹路径  
     * @param string $filename 压缩文件名  
     * @return void  
     */    public function zip(string $path, string $outputPath, string $filename)  
    {  
        $rootPath = $path;  
        $zip = new ZipArchive();  
        $zip->open($outputPath . $filename . '.zip', ZipArchive::CREATE | ZipArchive::OVERWRITE);  
        /** @var SplFileInfo[] $files */  
        $files = new RecursiveIteratorIterator(  
            new RecursiveDirectoryIterator($rootPath),  
            RecursiveIteratorIterator::LEAVES_ONLY  
        );  
        foreach ($files as $name => $file) {  
            if (!$file->isDir()) {  
                $filePath = $file->getRealPath();  
//                $relativePath = substr($filePath, strlen($rootPath) + 1);  
                $relativePath = substr($filePath, strlen($rootPath));  
                $zip->addFile($filePath, $relativePath);  
            }  
        }  
  
        $zip = new ZipArchive();  
        $zip->open($outputPath . $filename . '.zip');  
        $zip->extractTo($outputPath . $filename . '/');  
        $zip->close();  
    }  
  
  
    /**  
     * 删除文件夹or文件  
     * @param string $path 删除文件夹路径  
     * @return void  
     */public static function delDirAndFile(string $path)  
    {  
        if (is_dir($path)) {  
            $dh = opendir($path);  
            while ($file = readdir($dh)) {  
                if ($file != "." && $file != "..") {  
                    $fullpath = $path . "/" . $file;  
                    if (!is_dir($fullpath)) {  
                        unlink($fullpath);  
                    } else {  
                        // 递归  
                        self::delDirAndFile($fullpath);  
                    }  
                }  
            }  
            closedir($dh);  
            rmdir($path);  
        } else {  
            if (file_exists($path)) {  
                unlink($path);  
            }  
        }  
    }
  
    /**  
     * 分组排序  
     * @param array $data 需要分组的数据  
     * @param string $field 分组字段  
     * @return array  
     */    public static function group(array $data, string $field)  
    {  
        $result = array();  
        foreach ($data as $key => $value) {  
            $result[$value[$field]][] = $value;  
            usort($result[$value[$field]], function ($a, $b) {  
                if (isset($a['one_ear']) && isset($b['one_ear'])) {  
                    return $a['one_ear'] > $b['one_ear'];  
                } else {  
                    return isset($a['one_ear']) ? -1 : 1;  
                }  
            });  
        }  
        return $result;  
    }  
  
  
    /**  
     * 计算指定日期的一周开始及结束日期  
     * @param DateTime $date 日期  
     * @param Int $start 周几作为一周的开始 1-6为周一~周六,0为周日,默认0  
     * @retrun Array  
     */    public static function getWeekRange($date, $start = 0)  
    {  
  
        // 将日期转时间戳  
        $dt = new DateTime($date);  
        $timestamp = $dt->format('U');  
  
        // 获取日期是周几  
        $day = (new DateTime('@' . $timestamp))->format('w');  
  
        // 计算开始日期  
        if ($day >= $start) {  
            $startdate_timestamp = mktime(0, 0, 0, date('m', $timestamp), date('d', $timestamp) - ($day - $start), date('Y', $timestamp));  
        } elseif ($day < $start) {  
            $startdate_timestamp = mktime(0, 0, 0, date('m', $timestamp), date('d', $timestamp) - 7 + $start - $day, date('Y', $timestamp));  
        }  
  
        // 结束日期=开始日期+6  
        $enddate_timestamp = mktime(0, 0, 0, date('m', $startdate_timestamp), date('d', $startdate_timestamp) + 6, date('Y', $startdate_timestamp));  
  
        $startdate = (new DateTime('@' . $startdate_timestamp))->format('Y-m-d');  
        $enddate = (new DateTime('@' . $enddate_timestamp))->format('Y-m-d');  
  
        return array($startdate, $enddate);  
    }  
  
  
    /**  
     * 生成图片
     * @param array $config 参数,包括图片和文字
     * @param string $filename 生成海报文件名,不传此参数则不生成文件,直接输出图片  
     * @return false|string|void [type] [description]  
     */    public static function createPoster(array $config = array(), string $filename = "")  
    {  
        //要看报什么错注释掉这个  
//        if(empty($filename)) header("content-type: image/png");  
        $imageDefault = array(  
            'left' => 0,  
            'top' => 0,  
            'right' => 0,  
            'bottom' => 0,  
            'width' => 100,  
            'height' => 100,  
            'opacity' => 100  
        );  
        $textDefault = array(  
            'text' => '',  
            'left' => 0,  
            'top' => 0,  
            'fontSize' => 32,       //字号  
            'fontColor' => '255,255,255', //字体颜色  
            'angle' => 0,  
        );  
        $background = $config['background'];//海报最底层得背景  
        //背景方法  
        $backgroundInfo = getimagesize($background);  
        $backgroundFun = 'imagecreatefrom' . image_type_to_extension($backgroundInfo[2], false);  
        $background = $backgroundFun($background);  
        $backgroundWidth = imagesx($background);  //背景宽度  
        $backgroundHeight = imagesy($background);  //背景高度  
        $imageRes = imageCreatetruecolor($backgroundWidth, $backgroundHeight);  
        $color = imagecolorallocate($imageRes, 0, 0, 0);  
        imagefill($imageRes, 0, 0, $color);  
        // imageColorTransparent($imageRes, $color);  //颜色透明  
        imagecopyresampled($imageRes, $background, 0, 0, 0, 0, imagesx($background), imagesy($background), imagesx($background), imagesy($background));  
        //处理了图片  
        if (!empty($config['image'])) {  
            foreach ($config['image'] as $key => $val) {  
                $val = array_merge($imageDefault, $val);  
                $info = getimagesize($val['url']);  
                $function = 'imagecreatefrom' . image_type_to_extension($info[2], false);  
                if ($val['stream']) {   //如果传的是字符串图像流  
                    $info = getimagesizefromstring($val['url']);  
                    $function = 'imagecreatefromstring';  
                }  
                $res = $function($val['url']);  
                $resWidth = $info[0];  
                $resHeight = $info[1];  
                //建立画板 ,缩放图片至指定尺寸  
                $canvas = imagecreatetruecolor($val['width'], $val['height']);  
  
                imagesavealpha($canvas, true);//不要丢了$imageRes图像的透明色;  
                imagealphablending($canvas, false);//不合并颜色,直接用$canvas图像颜色替换,包括透明色;  
                imagesavealpha($imageRes, true);//不要丢了$imageRes图像的透明色;  
  
                imagefill($canvas, 0, 0, $color);  
                //关键函数,参数(目标资源,源,目标资源的开始坐标x,y, 源资源的开始坐标x,y,目标资源的宽高w,h,源资源的宽高w,h)  
                imagecopyresampled($canvas, $res, 0, 0, 0, 0, $val['width'], $val['height'], $resWidth, $resHeight);  
                $val['left'] = $val['left'] < 0 ? $backgroundWidth - abs($val['left']) - $val['width'] : $val['left'];  
                $val['top'] = $val['top'] < 0 ? $backgroundHeight - abs($val['top']) - $val['height'] : $val['top'];  
                //放置图像  
//                imagecopymerge($imageRes, $canvas, $val['left'], $val['top'], $val['right'], $val['bottom'], $val['width'], $val['height'], $val['opacity']);//左,上,右,下,宽度,高度,透明度  
                imagecopy($imageRes, $canvas, $val['left'], $val['top'], $val['right'], $val['bottom'], $val['width'], $val['height']);  
            }  
        }  
        //处理文字  
        if (!empty($config['text'])) {  
            foreach ($config['text'] as $key => $val) {  
                $val = array_merge($textDefault, $val);  
                list($R, $G, $B) = explode(',', $val['fontColor']);  
                $fontColor = imagecolorallocate($imageRes, $R, $G, $B);  
                $val['left'] = $val['left'] < 0 ? $backgroundWidth - abs($val['left']) : $val['left'];  
                $val['top'] = $val['top'] < 0 ? $backgroundHeight - abs($val['top']) : $val['top'];  
  
                // 是否添加阴影  
                if (isset($val['shadow']) && $val['shadow']) {  
                    $fontColorHui = imagecolorallocate($imageRes, 120, 120, 120);  
                    //为文字添加阴影  
                    imagettftext($imageRes, $val['fontSize'], $val['angle'], (int)$val['left'] + 10, (int)$val['top'] + 10, $fontColorHui, $val['fontPath'], $val['text']);  
                }  
  
                //添加文本  
                imagettftext($imageRes, $val['fontSize'], $val['angle'], $val['left'], $val['top'], $fontColor, $val['fontPath'], $val['text']);  
            }  
        }  
        //生成图片  
        if (!empty($filename)) {  
//            $res = imagejpeg($imageRes, $filename, 90); //保存到本地  
            $res = imagepng($imageRes, $filename); //保存到本地  
            imagedestroy($imageRes);  
            if (!$res) return false;  
            return $filename;  
        } else {  
            ob_end_clean();  
            header("Content-type:image/png");  
//            imagejpeg($imageRes);     //在浏览器上显示  
            imagepng($imageRes);     //在浏览器上显示  
            imagedestroy($imageRes);  
        }  
    }  
  
    // 按字符串长度分割字符串为数组  
    public static function mbStrSplit($string, $len = 1)  
    {  
        $start = 0;  
        $strlen = mb_strlen($string);  
        $array = [];  
        while ($strlen) {  
            $array[] = mb_substr($string, $start, $len, "utf8");  
            $string = mb_substr($string, $len, $strlen, "utf8");  
            $strlen = mb_strlen($string);  
        }  
        return $array;  
    }  
  
    /**  
     * 海报调用实例  
     */  
    public function getPoster()  
    {
        $file = Env::get('root_path') . 'public' . DIRECTORY_SEPARATOR . 'uploads/';  
        $config = array(  
            'text' => array(  
                array(  
                    'text' => '我是小红',  
                    'left' => 40,  
                    'top' => 40,  
                    'fontPath' => $file . 'ueditor/font/zkklt.ttf',  
                    'fontSize' => 18,  
                    'fontColor' => '0,221,34',  
                    'angle' => 0,  
                ),  
                array(  
                    'text' => '好吃的小红',  
                    'left' => 20,  
                    'top' => 280,  
                    'fontPath' => $file . 'ueditor/font/zkklt.ttf',  
                    'fontSize' => 18,  
                    'fontColor' => '0,0,238',  
                    'angle' => 0,  
                )  
            ),  
            'image' => array(  
                array(  
                    'url' => $file . 'ueditor/chishi/xiaohoshu.png',  
                    'left' => 130,  
                    'top' => 10,  
                    'stream' => 0,  
                    'right' => 0,  
                    'bottom' => 0,  
                    'width' => 40,  
                    'height' => 40,  
                    'opacity' => 100  
                ),  
                array(  
                    'url' => $file . 'ueditor/chishi/xiaohoshu.png',  
                    'left' => 135,  
                    'top' => 250,  
                    'stream' => 0,  
                    'right' => 0,  
                    'bottom' => 0,  
                    'width' => 40,  
                    'height' => 40,  
                    'opacity' => 100  
                ),  
                array(  
                    'url' => $file . 'ueditor/chishi/xiaohoshu.png',  
                    'left' => 30,  
                    'top' => 100,  
                    'right' => 0,  
                    'stream' => 0,  
                    'bottom' => 0,  
                    'width' => 150,  
                    'height' => 150,  
                    'opacity' => 100  
                ),  
            ),  
            'background' => $file . 'ueditor/chishi/chishibeijing.jpg',  
        );  
        $filename = $file . 'images/' . time() . '.jpg';  
//        return utils::createPoster($config, $filename);  
        return ChenUtils::createPoster($config);  
    }  
  
    /**  
     * 生成二维码  
     * @param string $code 二维码内容  
     * @param string $path 二维码保存路径  
     * @param string|boolean $QrCode 二维码保存图片路径 false则直接输出二维码  
     * @param string|boolean $logoQrCode 二维码带logo保存图片路径 false则直接输出带logo二维码  
     * @param string $logo logo图片路径  
     * @param string $errorCorrectionLevel 容错级别  
     * @param int $matrixPointSize 生成图片大小  
     * @return string  
     */    public static function qrCode(string $code, string $path, $QrCode, $logoQrCode = '', string $logo = '', string $errorCorrectionLevel = 'M', int $matrixPointSize = 10)  
    {  
        // 导入二维码类  
        $QrCode_temp = $QrCode;  
        include_once(dirname($_SERVER["DOCUMENT_ROOT"]) . '\extend\org\phpqrcode.php');  
  
        // 直接输出二维码  
        if (!$QrCode) {  
            ob_end_clean();  
            header("Content-type:image/png");  
            return QRcode::png($code, $QrCode, $errorCorrectionLevel, $matrixPointSize, 2);  
        }  
  
        // 生成二维码图片返回路径  
        QRcode::png($code, $QrCode, $errorCorrectionLevel, $matrixPointSize, 2);  
        //生成中间带logo的二维码  
        if ($logo != '') {  
            $QrCode = imagecreatefromstring(file_get_contents($QrCode));//获取已经生成的原始二维码  
            $logo = imagecreatefromstring(file_get_contents($logo));//获取二维码中间的logo图片  
            $QR_width = imagesx($QrCode);    //二维码图片宽度  
            $QR_height = imagesy($QrCode);        //二维码图片高度  
            $logo_width = imagesx($logo);   //logo图片宽度  
            $logo_height = imagesy($logo);  //logo图片高度  
//            $logo_qr_width = $QR_width / 5;  
            $logo_qr_width = $QR_width / 4;  
            $scale = $logo_width / $logo_qr_width;  
            $logo_qr_height = $logo_height / $scale;  
//            $from_width = ($QR_width - $logo_qr_width) / 2;  
            $from_width = ($QR_width - $logo_qr_width) / 2;  
            //重新组合图片并调整大小  
            imagecopyresampled($QrCode, $logo, $from_width, $from_width, 0, 0, $logo_qr_width, $logo_qr_height, $logo_width, $logo_height);  
            // 不保存直接输出  
            if (!$logoQrCode) {  
                //输出图片  
                ob_end_clean();  
                header("Content-type:image/png");  
                imagepng($QrCode);  
                imagedestroy($QrCode);  
                unlink($QrCode_temp);  
                return;  
            } else {  
                //保存图片  
                imagepng($QrCode, $logoQrCode);  
                // 删除不带logo的二维码  
                unlink($QrCode_temp);  
            }  
        }  
        return $logoQrCode;  
    }  
  
  
    /**
     * 图片转base64
     * @param string $file 图片路径
     * @return string
     */
    public static function base64EncodeImage(string $file)
    {
        if (!file_exists($file)) {
            return false;
        }
        $mimeType = mime_content_type($file);
        return 'data:' . $mimeType . ';base64,' . base64_encode(file_get_contents($file));
    } 
  
  
    /**  
     * 携带参数发送get/post请求  
     * @param string $url 请求地址  
     * @param array $data 请求参数  
     * @param string $method 请求方式  
     * @param array $header 请求头  
     * @return bool|string  
     */    public static function curlRequest(string $url, $data = [], string $method = 'get', array $header = [], $timeout = 30)  
    {  
        $ch = curl_init();  
        if (substr($url, 0, 5) == 'https') {  
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);  
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);  
        }  
        if ($method == 'post') {  
            curl_setopt($ch, CURLOPT_POST, 1);  
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);  
        } else {  
            if (!empty($data)) {  
                $url .= '?' . http_build_query($data);  
            }  
        }  
        curl_setopt($ch, CURLOPT_URL, $url);  
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  
        curl_setopt($ch, CURLOPT_HEADER, 0);  
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);  
        curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);  
        curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false);  
  
        if (!empty($header)) {  
            curl_setopt($ch, CURLOPT_HTTPHEADER, $header);  
        }  
  
        $output = curl_exec($ch);  
        curl_close($ch);  
        return $output;  
    }

    /**
     * 携带参数发送get/post请求
     * @param string $url 请求地址
     * @param array|string $data 请求参数
     * @param string $method 请求方式
     * @param array $header 请求头
     * @return string
     */
    public static function clientRequest(string $url, $data, string $method = 'GET', array $header = [], $timeout = 90): string
    {
        try {
            $client = new Client([
                'timeout' => $timeout, // 默认超时时间
                'verify' => false,
                'proxy' => false,
                RequestOptions::HEADERS => $header,
            ]);
            $params = [];
            if ($method === 'GET') {
                if (!empty($data)) {
                    $params = [
                        RequestOptions::QUERY => $data,
                    ];
                }
            } else {
                if (isset($header['Content-Type']) && $header['Content-Type'] === 'application/json') {
                    $params = [
                        RequestOptions::JSON => $data,
                    ];
                } else {
                    $params = [
                        RequestOptions::FORM_PARAMS => $data,
                    ];
                }
            }
            $response = $client->request($method, $url, $params);
            return $response->getBody();
        } catch (GuzzleException|RequestException $e) {
            throw new \Exception("HTTP Request Failed: " . $e->getMessage());
        }
    }
  
    /**  
     * 每次生成文件时检测是否达到上限,是则删除  
     */  
    public static function checkFile($file_path = '')  
    {  
        $file = Env::get('root_path') . 'public' . DIRECTORY_SEPARATOR . 'chanyu/chanyu.json';  
        // 检测存储文件是否存在,不存在则创建  
        if (!file_exists($file)) {  
            $img = array('file' => []);  
            file_put_contents($file, json_encode($img));  
        }  
        // 读取文件内容  
        $chanyu = json_decode(file_get_contents($file), true);  
        // 检测文件数量是否达到某个数量,达到则删除  
        if (count($chanyu['file']) >= config('code.chanyu_max')) {  
            foreach ($chanyu['file'] as $k => $v) {  
                if (file_exists($v)) {  
                    unlink($v);  
                }  
                unset($chanyu['file'][$k]);  
            }  
        }  
        $chanyu['file'][] = $file_path;  
        file_put_contents($file, json_encode($chanyu));  
    }  
  
    /**  
     * 判断图片路径是否为网络图片,是则下载到本地  
     */  
    public static function downloadImage($url, $save_dir = '', $filename = '', $type = 0)  
    {  
        if (!preg_match('/^(http|https):\/\/.*/', $url)) {  
            return false;  
        }  
        if (trim($save_dir) == '') {  
            $save_dir = './';  
        }  
        if (0 !== strrpos($save_dir, '/')) {  
            $save_dir .= '/';  
        }  
        //创建保存目录  
        if (!file_exists($save_dir) && !mkdir($save_dir, 0777, true)) {  
            return false;  
        }  
        //获取远程文件所采用的方法  
        if ($type) {  
            $ch = curl_init();  
            $timeout = 5;  
            curl_setopt($ch, CURLOPT_URL, $url);  
            curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);  
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, $timeout);  
            $img = curl_exec($ch);  
            curl_close($ch);  
        } else {  
            ob_start();  
            readfile($url);  
            $img = ob_get_contents();  
            ob_end_clean();  
        }  
        //$size=strlen($img);  
        // 获取文件后缀  
        $ext = strrchr($url, '.');  
        //文件大小  
        $fp2 = @fopen($save_dir . $filename . $ext, 'a');  
        fwrite($fp2, $img);  
        fclose($fp2);  
        unset($img, $url);  
        return $save_dir . $filename . $ext;  
    }  
  
    /**  
     * 發送郵件  
     * @param array $config 配置  
     * [  
     * 'CharSet' => 'UTF-8', //设定邮件编码  
     * 'Host' => '',  // SMTP服务器  
     * 'Username' => '', // 邮箱账号  
     * 'Password' => '', // 邮箱密码  
     * 'SMTPSecure' => 'ssl', // 允许 TLS 或者ssl协议  
     * 'Port' => 465, // 服务器端口 25 或者465 具体要看邮箱服务器支持  
     * ];  
     * @param array $from 發件人  
     * [  
     * 'name' => 'ccc',  
     * 'email' => '123456789@qq.com',  
     * ]     * @param array $content 邮件内容  
     * [  
     * 'title' => '標題', // 邮件标题  
     * 'isHtml' => true, // 是否是html  
     * 'body' => '<h1>这里是邮件内容</h1>', // 邮件内容  
     * 'altBody' => '测试邮件内容', // 如果邮件客户端不支持html则显示此内容  
     * ]  
     * @param string $cc 抄送  
     * @param string $bcc 密送  
     * @param array $attachment 附件  
     * [  
     * [     * 'path' => '..\1.jpg',     * 'newName' => '2.jpg', // 带newName就重命名  
     * ],  
     * [     * 'path' => '..\1.jpg',     * ]     * ]     * @return array  
     */    public static function PHPMailer($config = [], $from = [], $address = [], $content = [], $cc = '', $bcc = '', $attachment = [])  
    {  
        // composer require phpmailer/phpmailer  
        $mail = new PHPMailer(true);  
        try {  
            //服务器配置  
            $mail->CharSet = $config['CharSet'];                     //设定邮件编码  
            $mail->SMTPDebug = 0;                        // 调试模式输出  
            $mail->isSMTP();                              // 使用SMTP  
            $mail->Host = $config['Host'];                // SMTP服务器  
            $mail->SMTPAuth = true;                      // 允许 SMTP 认证  
            $mail->Username = $config['Username'];                // SMTP 用户名  即邮箱的用户名  
            $mail->Password = $config['Password'];             // SMTP 密码  部分邮箱是授权码(例如163邮箱)  
            $mail->SMTPSecure = $config['SMTPSecure'];                    // 允许 TLS 或者ssl协议  
            $mail->Port = $config['Port'];                            // 服务器端口 25 或者465 具体要看邮箱服务器支持  
  
            $mail->setFrom($from['email'], $from['name']);  //发件人  
  
            foreach ($address as $key => $val) {  
                $mail->addAddress($val['email'], $val['name']);  // 收件人  
            }  
  
            $mail->addReplyTo($from['email'], $from['email']); //回复的时候回复给哪个邮箱 建议和发件人一致  
  
            if (!empty($cc)) {  
                $mail->addCC($cc);  // 抄送  
            }  
            if (!empty($bcc)) {  
                $mail->addBCC($bcc);  // 密送  
            }  
  
            //发送附件  
            foreach ($attachment as $key => $val) {  
                if (empty($val['newName'])) {  
                    $mail->addAttachment($val['path']);         // 添加附件  
                } else {  
                    $mail->addAttachment($val['path'], $val['newName']);    // 发送附件并且重命名  
                }  
            }  
  
            //Content  
            $mail->isHTML($content['isHtml']);    // 是否以HTML文档格式发送  发送后客户端可直接显示对应HTML内容  
            $mail->Subject = $content['title'];  
            $mail->Body = $content['body'];  
            $mail->AltBody = $content['altBody'];  
            $mail->send();  
            return ['code' => 1, 'msg' => '发送成功'];  
        } catch (Exception $e) {  
            return ['code' => 0, 'msg' => "发送失败: {$mail->ErrorInfo}", 'data' => $e->getMessage()];  
        }  
    }  
  
    /**  
     * 读取本地视频文件,获取视频第一帧作为封面返回  
     */  
    public static function getVideoCover($filePath)  
    {  
        if (!file_exists($filePath)) {  
            return json([  
                'success' => false,  
                'msg' => '视频文件不存在'  
            ]);  
        }  
        // TODO 安装php-fmpeg:composer require php-ffmpeg/php-ffmpeg  
  
        // 判断当前是linux还是window环境  
        if (DIRECTORY_SEPARATOR == '/') {  
            // linux环境  
            // TODO linux安装ffmpeg  
            // 地址:https://cloud.tencent.com/developer/article/1985211  
  
            $ffmpeg = FFMpeg::create([  
                'ffmpeg.binaries' => '/usr/bin/ffmpeg',  
                'ffprobe.binaries' => '/usr/bin/ffprobe',  
                'timeout' => 3600, // The timeout for the underlying process  
                'ffmpeg.threads' => 12,   // The number of threads that FFMpeg should use  
            ]);  
        } else {  
            // window环境  
            $ffmpeg = FFMpeg::create([  
                'ffmpeg.binaries' => Env::get('root_path') . 'public' . DIRECTORY_SEPARATOR . 'ffmpeg' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'ffmpeg.exe',  
                'ffprobe.binaries' => Env::get('root_path') . 'public' . DIRECTORY_SEPARATOR . 'ffmpeg' . DIRECTORY_SEPARATOR . 'bin' . DIRECTORY_SEPARATOR . 'ffprobe.exe',  
                'timeout' => 3600, // The timeout for the underlying process  
                'ffmpeg.threads' => 12,   // The number of threads that FFMpeg should use  
            ]);  
        }  
  
  
        $video = $ffmpeg->open($filePath);  
        $frame = $video->frame(TimeCode::fromSeconds(1));  
        $path = Env::get('root_path') . 'public' . DIRECTORY_SEPARATOR . 'uploads' . DIRECTORY_SEPARATOR . 'video' . DIRECTORY_SEPARATOR . 'cover';  
        // 判断文件夹是否存在  
        if (!file_exists($path)) {  
            //检查是否有该文件夹,如果没有就创建,并给予最高权限  
            mkdir($path, 0700, true);  
        }  
        $frame->save($path . DIRECTORY_SEPARATOR . md5($filePath) . '.jpg');  
  
        return json([  
            'success' => true,  
            'data' => '/uploads/video/cover/' . md5($filePath) . '.jpg'  
        ]);  
    }  
  
    /**
     * 导出excel(多sheet版)
     * composer require phpoffice/phpspreadsheet
     * @param string $path 路径
     * @param array $data 数据 [ 'Sheet1' => [ ... ] ]
     * @param string $filename 文件名
     * @param array $cellname 列定义 [ [字段名, 显示标题, 宽度, 对齐方式], ... ]
     * @param callable $fun 回调函数用于自定义处理数据
     * @return array
     */
    public static function exportExcel(string $path, array $data, string $filename, array $cellname, callable $fun): array
    {
        try {
            $spreadsheet = new Spreadsheet();
            $spreadsheet->removeSheetByIndex(0);
            $i = 0;
            foreach ($data as $sheetName => $rows) {
                $sheet = $spreadsheet->createSheet($i);
                $sheet->setTitle((string)$sheetName);
                $j = 1;
                foreach ($cellname as $col) {
                    $column_name = $sheet->getColumnDimensionByColumn($j)->getColumnIndex();
                    $sheet->setCellValue($column_name . '1', $col[1]); // 标题
                    $sheet->getColumnDimensionByColumn($j)->setWidth($col[2]); // 宽度
                    $alignment = $col[3] ?? 'CENTER';
                    $sheet->getStyle($column_name . '1')
                        ->getAlignment()
                        ->setHorizontal($alignment);
                    $j++;
                }
                foreach ($rows as $k => $row) {
                    $l = 1;
                    foreach ($cellname as $col) {
                        $field = $col[0];
                        $column_name = $sheet->getColumnDimensionByColumn($l)->getColumnIndex();
                        $item = '';
                        $value = self::getNestedValue($row, $field);

                        if (is_array($value)) {
                            foreach ($value as $rk => $rv) {
                                $item .= $rk . ':' . $rv . "\n";
                            }
                        } else {
                            $item = $value ?? '';
                        }
                        $sheet->setCellValueExplicit($column_name . ($k + 2), $item, DataType::TYPE_STRING);
                        $l++;
                    }
                }
                $fun($sheet, $rows);
                $i++;
            }
            $writer = new Xlsx($spreadsheet);
            $writer->save($path . $filename);
            return [
                'code' => 1,
                'msg' => '导出成功',
                'data' => $filename,
                'filename' => $filename,
            ];
        } catch (Exception $e) {
            return [
                'code' => 0,
                'msg' => '导出失败',
                'data' => $e->getMessage(),
                'line' => $e->getLine(),
                'file' => $e->getFile(),
            ];
        }
    }

    /**
     * 通过点语法获取嵌套数组的值
     * @param array $array 原始数组
     * @param string $key 点分隔的键路径
     * @return mixed
     */
    private static function getNestedValue(array $array, string $key)
    {
        if (strpos($key, '.') === false) {
            return $array[$key] ?? null;
        }

        $keys = explode('.', $key);
        $value = $array;

        foreach ($keys as $nestedKey) {
            if (!is_array($value) || !isset($value[$nestedKey])) {
                return null;
            }
            $value = $value[$nestedKey];
        }

        return $value;
    }

  
    public static function readExcel($filePath)  
    {  
        // 判断文件是否存在  
        if (!file_exists($filePath)) {  
            return [  
                'code' => 100,  
                'msg' => '文件不存在'  
            ];  
        }  
        try {  
            // 使用指定的读取器  
            $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();  
            if ($reader->canRead($filePath)) {  
                $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xlsx();  
            } else {  
                $reader = new \PhpOffice\PhpSpreadsheet\Reader\Xls();  
            }  
            // 关键:需要读取样式与格式,才能识别日期  
            $reader->setReadDataOnly(false);  
      
            // 读取 Excel 文件  
            $spreadsheet = $reader->load($filePath);  
      
            // 获取第一个工作表  
            $sheet = $spreadsheet->getActiveSheet();  
      
            // 获取最大行和列  
            $maxRow = $sheet->getHighestDataRow();  
            $maxCol = $sheet->getHighestDataColumn();  
      
            // 将字母列号转换为数字列号  
            $maxColIndex = \PhpOffice\PhpSpreadsheet\Cell\Coordinate::columnIndexFromString($maxCol);  
      
            // 读取数据  
            $data = [];  
            for ($row = 1; $row <= $maxRow; $row++) {  
                $rowData = [];  
      
                for ($col = 1; $col <= $maxColIndex; $col++) {  
                    $column_name = $sheet->getColumnDimensionByColumn($col)->getColumnIndex();  
                    $cell = $sheet->getCell($column_name . $row);  
                    // 若是日期,且为数值序列,统一转成标准格式;否则使用格式化后的显示值  
                    if (\PhpOffice\PhpSpreadsheet\Shared\Date::isDateTime($cell)) {  
                        $raw = $cell->getValue();  
                        if (is_numeric($raw)) {  
                            $cellValue = \PhpOffice\PhpSpreadsheet\Shared\Date::excelToDateTimeObject($raw)->format('Y-m-d H:i:s');  
                        } else {  
                            $cellValue = $cell->getFormattedValue();  
                        }  
                    } else {  
                        $cellValue = $cell->getFormattedValue();  
                    }  
                    $cellValue = ChenUtils::removeBlank($cellValue);  
                    $rowData[] = $cellValue;  
                }  
                $data[] = $rowData;  
            }  
      
            foreach ($data as $k => $v) {  
                foreach ($v as $kk => $vv) {  
                    if (is_string($vv)) {  
                        $data[$k][$kk] = trim($vv);  
                    }  
                }  
            }  
      
            return [  
                'code' => 200,  
                'data' => $data  
            ];  
        } catch (\PhpOffice\PhpSpreadsheet\Exception $e) {  
            return [  
                'code' => 100,  
                'msg' => $e->getMessage()  
            ];  
        }  
    }
  
  
    /**  
     * 指定年月,返回开始跟结束时间  
     * @param $year  
     * @param $month  
     * @return array  
     */    function getMonthRange($year, $month)  
    {  
        $start = strtotime($year . '-' . $month);  // 月份开始时间  
        $end = strtotime(date('Y-m-t', strtotime($year . '-' . $month)));  // 月份结束时间  
        return array(  
            'start' => date('Y-m-d H:i:s', $start),  
            'end' => date('Y-m-d H:i:s', $end)  
        );  
    }  
  
    /**  
     * 指定年月,返回月每个周的开始跟结束时间  
     * @param $year  
     * @param $month  
     * @return array  
     */    function getWeeksInMonth($year, $month)  
    {  
        $firstDayOfMonth = date("N", strtotime("$year-$month-01"));  
        $lastDayOfMonth = date("t", strtotime("$year-$month-01"));  
        $daysInFirstWeek = 7 - ($firstDayOfMonth - 1) % 7;  
        $weeksInMonth = ceil(($lastDayOfMonth - $daysInFirstWeek) / 7) + 1;  
        $weeks = array();  
  
        //处理第一周  
        $firstWeekStart = date("Y-m-d", strtotime("$year-$month-01 -" . ($firstDayOfMonth - 1) . " days"));  
        $firstWeekEnd = date("Y-m-d", strtotime("$year-$month-01 +" . ($daysInFirstWeek - 1) . " days"));  
        if ($daysInFirstWeek < 7 && $month > 1) {  
            $lastMonthDays = date("t", strtotime("$year-" . ($month - 1) . "-01"));  
            $daysInLastMonth = $firstDayOfMonth - 1;  
            $firstWeekStart = date("Y-m-d", strtotime("$year-" . ($month - 1) . "-01 +" . ($lastMonthDays - $daysInLastMonth) . " days"));  
        }  
        $weeks[] = array("start" => $firstWeekStart, "end" => $firstWeekEnd);  
  
        //处理其它周  
        for ($i = 2; $i <= $weeksInMonth; $i++) {  
            $weekStart = date("Y-m-d", strtotime("$year-$month-01 +" . (($i - 1) * 7 - $daysInFirstWeek) . " days"));  
            $weekEnd = date("Y-m-d", strtotime("$weekStart +6 days"));  
            $weeks[] = array("start" => $weekStart, "end" => $weekEnd);  
        }  
  
        return $weeks;  
    }  
  
    /**  
     * 将图片的白色部分设为透明
     * @param $sourceImage string 源图像  
     * @param $outputImage string 输出图像(后缀png)  
     * @return void  
     */    public  
    static function transparentWhite($sourceImage, $outputImage)  
    {  
        // 创建源图像资源  
        $source = imagecreatefrompng($sourceImage);  
        // 获取源图像的宽度和高度  
        $width = imagesx($source);  
        $height = imagesy($source);  
        // 创建目标图像资源,使用真彩色图像  
        $target = imagecreatetruecolor($width, $height);  
        // 定义白色的RGB值  
        $white = imagecolorallocate($target, 255, 255, 255);  
        // 设置白色为透明  
        imagecolortransparent($target, $white);  
        // 在目标图像上绘制源图像  
        imagecopy($target, $source, 0, 0, 0, 0, $width, $height);  
        // 保存目标图像  
        imagepng($target, $outputImage);  
        // 释放资源  
        imagedestroy($source);  
        imagedestroy($target);  
    }  
  
    /**  
     * tree数据排序
     * @param array $data 线性结构数组  
     * @param string $symbol 名称前面加符号  
     * @param string $name 名称  
     * @param string $id_name 数组id名  
     * @param string $parent_id_name 数组祖先id名  
     * @param int $level 此值请勿给参数  
     * @param int $parent_id 此值请勿给参数  
     * @return array  
     */    public static function linear_to_tree($data, $sub_key_name = 'sub', $id_name = 'id', $parent_id_name = 'pid', $parent_id = 0): array  
    {  
        $tree = [];  
        foreach ($data as $row) {  
            if ($row[$parent_id_name] == $parent_id) {  
                $temp = $row;  
                $child = self::linear_to_tree($data, $sub_key_name, $id_name, $parent_id_name, $row[$id_name]);  
                if ($child) {  
                    $temp[$sub_key_name] = $child;  
                }  
                $tree[] = $temp;  
            }  
        }  
        return $tree;  
    }  
  
    /**  
     * csv字符串,转数组  
     */  
    public  
    static function csvToArray($csv)  
    {  
        $csv = explode("\n", $csv);  
        $csv = array_filter($csv);  
        $csv = array_map(function ($v) {  
            return explode(',', $v);  
        }, $csv);  
        foreach ($csv as $k => $v) {  
            foreach ($v as $kk => $vv) {  
                $csv[$k][$kk] = str_replace("\r", '', $vv);  
            }  
        }  
        return $csv;  
    }  
  
    /**  
     * 读取csv文件  
     */  
    public static function readCsvFile($filePath, $code = 'auto'): array  
    {  
        // 判断文件是否存在  
        if (!file_exists($filePath)) {  
            return [  
                'code' => 100,  
                'msg' => '文件不存在'  
            ];  
        }  
        try {  
            $data = [];  
            if (($handle = fopen($filePath, "r")) !== false) {  
                // 设置CSV文件编码  
                $originalEncoding = mb_internal_encoding();  
                mb_internal_encoding('UTF-8');  
  
                while (($row = fgetcsv($handle, 0, ",")) !== false) {  
                    // 清除每一行数据的乱码字符  
                    foreach ($row as &$cell) {  
                        $cell = mb_convert_encoding($cell, 'UTF-8', $code);  
                    }  
                    $data[] = $row;  
                }  
                // 恢复原始编码设置  
                mb_internal_encoding($originalEncoding);  
                fclose($handle);  
            }  
            foreach ($data as $k => $v) {  
                foreach ($v as $kk => $vv) {  
                    if (is_string($vv)) {  
                        $data[$k][$kk] = trim($vv);  
                    }  
                }  
            }  
            return [  
                'code' => 200,  
                'data' => $data  
            ];  
        } catch (\Exception $e) {  
            return [  
                'code' => 100,  
                'msg' => $e->getMessage(),  
                'line'=> $e->getLine(),  
                'file' => $e->getFile()  
            ];  
        }  
    }  
  
    /**  
     * 移除空白字符  
     */  
    public static function removeBlank($str): array|string|null  
    {  
        // 移除控制字符  
        $str = preg_replace('/[[:cntrl:]]/', '', $str);  
        // 移除高位字符  
        $str = preg_replace('/[^\PC\s]/u', '', $str);  
        // 移除BOM头  
        $str = preg_replace('/\x{FEFF}/u', '', $str);  
        return $str;  
    }  
  
    /**  
     * 计算文件夹大小  
     */  
    public static function getDirSize(string $string)  
    {  
        $size = 0;  
        foreach (new RecursiveIteratorIterator(new RecursiveDirectoryIterator($string)) as $file) {  
            $size += $file->getSize();  
        }  
        return $size;  
    }

    /**
     * 流(接收所有后返回)
     * @param string $url 请求的 URL
     * @return array 解析后的 JSON 数据,失败返回 null
     * @throws Exception
     */
    public static function fetchStreamJson(string $url, array $header = [], string $method = 'GET', array $postData = []): array
    {
        $client = new Client();
        try {
            $options = [
                'stream' => true,
                'verify' => false,
                'proxy' => false,
                RequestOptions::HEADERS => $header,
            ];
            if ($method === 'POST') {
                $options['json'] = $postData;
            }
            $response = $client->request($method, $url, $options);
            $body = $response->getBody();
            $buffer = '';
            $results = [];
            while (!$body->eof()) {
                $chunk = $body->read(1024);
                $buffer .= $chunk;
                $lines = explode("\n", $buffer);
                $buffer = array_pop($lines);
                foreach ($lines as $line) {
                    if (str_starts_with(trim($line), 'data:')) {
                        $jsonData = trim(substr($line, 5));
                        if ($jsonData === '') continue;
                        $decoded = json_decode($jsonData, true);
                        if (json_last_error() === JSON_ERROR_NONE) {
                            $results[] = $decoded;
                        } else {
                            error_log("JSON decode error in line: " . $jsonData);
                        }
                    }
                }
            }
            if (!empty($buffer)) {
                $jsonData = trim($buffer);
                if (str_starts_with($jsonData, '{') || str_starts_with($jsonData, '[')) {
                    $decoded = json_decode($jsonData, true);
                    if (json_last_error() === JSON_ERROR_NONE) {
                        $results[] = $decoded;
                    }
                }
            }
            return $results;
        } catch (GuzzleException $e) {
            throw new Exception("Guzzle request error: " . $e->getMessage());
        }
    }

    /**
     * 流(控制器使用)
     * @throws GuzzleException
     *
     * 示例:
     * header('Content-Type: text/event-stream');
     * header('Cache-Control: no-cache');
     * header('X-Accel-Buffering: no'); // 防止 Nginx 缓存
     * Utils::streamJsonResponse(
     * function($output) {
     * echo $output;
     * },
     * 'http://xxj.lygzh.com/v1/workflows/run',
     * $header,
     * 'POST',
     * $postData
     * );
     */
    public static function streamJsonResponse(callable $outputCallback, string $url, array $header = [], string $method = 'GET', array $postData = [])
    {
        $client = new \GuzzleHttp\Client();
        try {
            $options = [
                'stream' => true,
                'verify' => false,
                'proxy' => false,
                'headers' => $header,
            ];
            if ($method === 'POST') {
                $options['json'] = $postData;
            }
            $response = $client->request($method, $url, $options);
            $body = $response->getBody();
            $buffer = '';
            while (!$body->eof()) {
                $chunk = $body->read(1024);
                $buffer .= $chunk;
                $lines = explode("\n", $buffer);
                $buffer = array_pop($lines); // 保留不完整的一行
                foreach ($lines as $line) {
                    $line = trim($line);
                    if (str_starts_with($line, 'data:')) {
                        $jsonData = trim(substr($line, 5));
                        if (!empty($jsonData)) {
                            $outputCallback("data: $jsonData\n\n");
                        }
                    }
                }
            }
            // 处理剩余 buffer
            if (!empty($buffer)) {
                $line = trim($buffer);
                if (str_starts_with($line, 'data:')) {
                    $jsonData = trim(substr($line, 5));
                    if (!empty($jsonData)) {
                        $outputCallback("data: $jsonData\n\n");
                    }
                }
            }
            // 发送完成信号
            $outputCallback("data: [DONE]\n\n");
        } catch (\Exception $e) {
            $outputCallback("data: [ERROR] " . $e->getMessage() . "\n\n");
        }
    }

}