<?php
namespace Api\PhpUtils\Http;

use Api\PhpUtils\Log\FileLog;
use Api\PhpUtils\Log\Tracer;
use Api\PhpUtils\Mon\MonUtil;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Exception\TransferException;
use GuzzleHttp\Pool;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\TransferStats;

class Request
{
    private $client = null;
    //最大的并发数量
    private $concurrency_num = 5;

    private $success_code = 0;
    private $fail_code = 999999;

    private $result = [];
    protected $con_result = [];

    private $log = [
        'url' => [],
        'params' => [],
        'response' => []
    ];

    public function __construct()
    {
    }

    /**
     * @param string $url 请求url
     * @param array $params 请求参数
     * @param int $timeout 自定义超时毫秒数
     * @param array $headers 自定义headers
     * @param int $retry 重试次数（默认不重试）
     * @param bool $use_mon 是否使用监控打点（默认是）
     * @param string $proxy 代理（比如http://192.11.222.124:8000）
     * @return array
     * @throws
     */
    public function get($url, $params = [], $timeout = 0, $headers = [], $retry = 0, $use_mon = true, $proxy = '')
    {
        try {
            //验参
            if (empty($url) || !is_array($params)) {
                throw new \Exception('params error');
            }
            $this->log['url'] = $url;
            $this->log['params'] = $params;
            //构造client
            $config = [];
            if (!empty($retry) && is_int($retry)) {
                $config['retry'] = $retry;
            }
            $this->client = (new Base())->initClient($config, $url, $params);
            //构造配置
            if (!empty($params)) {
                $options['query'] = $params;
            }
            if (!empty($timeout)) {
                $options['timeout'] = $timeout / 1000;
            }

            // 增加traceId
            if(is_array($headers)) {
                $headers['X-TRACE-ID'] = Tracer::getTraceId();
            }

            if (!empty($headers)) {
                $options['headers'] = $headers;
            }
            if (!empty($proxy)) {
                $options['proxy'] = $proxy;
            }
            $options['on_stats'] = function (TransferStats $stats) use ($use_mon, $url) {
                $this->result['http_code'] = $stats->getHandlerStat('http_code');
                if (!empty($use_mon)) {
                    MonUtil::proxyMon($url, $stats->getHandlerStat('http_code'), '',round($stats->getHandlerStat('total_time'),4) * 1000);
                }
            };
            //异步get请求
            $promise = $this->client->getAsync($url, $options);
            $promise->then(
                function (Response $resp) {
                    $ret = $this->log['response'] = $resp->getBody()->getContents();
                    $ret = empty(json_decode($ret, true)) ? $ret : json_decode($ret, true);
                    $this->result['code'] = $this->success_code;
                    $this->result['response'] = $ret;
                },
                function (RequestException $e) {
                    $this->log['response'] = $e->getMessage();
                    throw new \Exception($e->getMessage());
                }
            );
            $promise->wait();
        } catch (\Exception $e) {
            $this->result['code'] = $this->fail_code;
            $this->result['msg'] = $e->getMessage();
        } finally {
            $this->addLog('get');
            return $this->result;
        }
    }
    /**
     * @param $url
     * @param array $params 请求参数
     * @param int $timeout 自定义超时毫秒数
     * @param string $content_type 默认form_params|json|multipart（文件上传）
     * @param array $headers 自定义headers
     * @param int $retry 重试次数
     * @param bool $use_mon 是否使用监控打点（默认是）
     * @param string $module 模块名
     * @param string $proxy 代理（比如http://192.11.222.124:8000）
     * @return array
     * @throws
     */
    public function post($url, $params = [], $timeout = 0, $content_type = '', $headers = [], $retry = 0, $use_mon = true,$module ='', $proxy = '')
    {
        try {
            //验参
            if (empty($url) || !is_array($params)) {
                throw new \Exception('params error');
            }
            $this->log['url'] = $url;
            $this->log['params'] = $params;
            //构造client
            $config = [];
            if (!empty($retry) && is_int($retry)) {
                $config['retry'] = $retry;
            }
            $this->client = (new Base())->initClient($config, $url, $params);
            //构造配置
            if (!empty($params)) {
                if ($content_type == 'json') {
                    $options['json'] = $params;
                } elseif ($content_type == 'multipart') {
                    $options['multipart'] = $params;
                } else {
                    $options['form_params'] = $params;
                }
            }
            if (!empty($timeout)) {
                $options['timeout'] = $timeout / 1000;
            }

            // 增加traceId
            if(is_array($headers)) {
                $headers['X-TRACE-ID'] = Tracer::getTraceId();
            }

            if (!empty($headers)) {
                $options['headers'] = $headers;
            }
            if (!empty($proxy)) {
                $options['proxy'] = $proxy;
            }
            $options['on_stats'] = function (TransferStats $stats) use ($use_mon, $url,$module) {
                $this->result['http_code'] = $stats->getHandlerStat('http_code');
                if ($use_mon) {
                    MonUtil::proxyMon($url, $stats->getHandlerStat('http_code'), $module,round($stats->getHandlerStat('total_time'),4) * 1000);
                }
            };
            //异步post请求
            $promise = $this->client->postAsync($url, $options);
            $promise->then(
                function (Response $resp) {
                    $ret = $this->log['response'] = $resp->getBody()->getContents();
                    $ret = empty(json_decode($ret, true)) ? $ret : json_decode($ret, true);
                    $this->result['code'] = $this->success_code;
                    $this->result['response'] = $ret;
                },
                function (RequestException $e) {
                    $this->log['response'] = $e->getMessage();
                    throw new \Exception($e->getMessage());
                }
            );
            $promise->wait();
        } catch (\Exception $e) {
            $this->result['code'] = $this->fail_code;
            $this->result['msg'] = $e->getMessage();
        } finally {
            $this->addLog('post');
            return $this->result;
        }
    }

    /**
     * 上传文件
     *
     */
    public function uploadPut($url, $post = [], $timeout = 0,$content_type = '', $headers = [])
    {

        $ch = self::CURL($url, $timeout, $headers, $post, false, $has_curl_file=false, "PUT");
        if (is_resource($ch) === true)
        {


        }
        $response = curl_exec($ch);
        if ($error = curl_error($ch))
        {
            $this->result['code'] = $this->fail_code;
            $this->result['msg'] = $error;
        }
        else
        {
            $this->result['code'] = $this->success_code;
            $this->result['response'] = $response;
        }

        curl_close($ch); // 关闭 cURL 释放资源
        return $response;
    }

    static function CURL($url, $timeout, $headers = false, $post = false, $proxy = false, $has_curl_file = null, $method='GET', $curl_opts = array())
    {
        $ch = curl_init($url);

        if (is_resource($ch) === true)
        {
            curl_setopt($ch, CURLOPT_FAILONERROR,           true);
            curl_setopt($ch, CURLOPT_FOLLOWLOCATION,        true);
            curl_setopt($ch, CURLOPT_RETURNTRANSFER,        true);
            curl_setopt($ch, CURLOPT_TCP_NODELAY,           true);
            curl_setopt($ch, CURLOPT_NOSIGNAL,              true);
            curl_setopt($ch, CURLOPT_SSL_VERIFYHOST,        false);
            curl_setopt($ch, CURLOPT_SSL_VERIFYPEER,        false);
            curl_setopt($ch, CURLOPT_BUFFERSIZE,            10240);
            curl_setopt($ch, CURLOPT_TIMEOUT_MS,            $timeout);
            curl_setopt($ch, CURLOPT_CONNECTTIMEOUT_MS,     $timeout);
            curl_setopt($ch, CURLOPT_HTTP_VERSION,          CURL_HTTP_VERSION_1_0);

            if ($headers !== false)
            {
                curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
            }
            if ($proxy !== false)
            {
                curl_setopt($ch, CURLOPT_PROXY, $proxy);
            }
            if (!empty($curl_opts))
            {
                foreach ($curl_opts as $opt => $val)
                {
                    curl_setopt($ch, $opt, $val);
                }
            }
            switch ($method) {
                case 'HEAD':
                    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'HEAD');
                    break;
                case 'GET':
                    break;
                case 'POST':
                    curl_setopt($ch, CURLOPT_POST, true);
                    curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
                    if($has_curl_file !== null)
                    {
                        curl_setopt($ch, CURLOPT_SAFE_UPLOAD, $has_curl_file);
                    }
                    break;
                case 'DELETE':
                    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE');
                    break;
                case 'PUT':
                    curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT');
                    curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
                    break;
                default:
                    die('httpUtil bad method');
                    break;
            }
        }
        return $ch;
    }

    static function NEW_CURL($url, $timeout, $retries, $headers, $post = false, $proxy = false, $has_curl_file = null, $method = 'GET', $curl_opts = array())
    {
        $use_mon = false;   //是否使用监控打点服务标志
        //$proxy 取值为true，表示依赖第三方服务，要进行监控打点
        if($proxy == true){
            $use_mon = true;
            $proxy = false;
        }

        $ch = self::CURL($url, $timeout, $headers, $post, $proxy, $has_curl_file, $method, $curl_opts);
        $result = false;

        if (is_resource($ch) === true)
        {
            while (($result === false) && ($retries-- > 0))
            {
                $result = curl_exec($ch);
            }
            if (curl_errno($ch) !== 0)
            {
                $GLOBALS['ERROR_MESSAGE'] = curl_error($ch);
            }
            $GLOBALS['HTTP_CODE'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
            if( isset($GLOBALS['ENABLE_HEADER_REQUEST_TIME']) )
            {
                $GLOBALS['BELENDER_CURL_INFO'] = curl_getinfo($ch);
                //在返回header中添加blender响应时间
                $GLOBALS['DEPENDENT-REQUEST-TIME'] = round(curl_getinfo($ch, CURLINFO_TOTAL_TIME),4).'|'.round(curl_getinfo($ch, CURLINFO_NAMELOOKUP_TIME),4).'|'.round(curl_getinfo($ch, CURLINFO_CONNECT_TIME),4);
                $GLOBALS['DEPENDENT-REQUEST'] = array('url'=>$url, 'method'=>$method, 'body'=>$post);
                $GLOBALS['DEPENDENT-URI'] = parse_url($url, PHP_URL_PATH);
                if ($GLOBALS['HTTP_CODE'] === 0)
                {
                    $GLOBALS['DEPENDENT-STATUS'] = 'timeout';
                }
                else if ($GLOBALS['HTTP_CODE'] !== 200)
                {
                    $GLOBALS['DEPENDENT-STATUS'] = '5xx';
                }else
                {
                    $GLOBALS['DEPENDENT-STATUS'] = 'normal';
                }
            }

//            //监控打点，模块名默认为第三方服务url，指标为请求返回的HTTP_code与总响应时间
//            //HTTP_CODE如果未设置，默认为-999
//            if($use_mon){
//                MonUtil::proxy_mon($url,
//                    isset($GLOBALS['HTTP_CODE']) ? $GLOBALS['HTTP_CODE'] : -999,
//                    false,
//                    round(curl_getinfo($ch, CURLINFO_TOTAL_TIME),4) * 1000
//                );
//            }

            unset($GLOBALS['ENABLE_HEADER_REQUEST_TIME']);
//            if ($post === false)
//            {
//                write_log($url, curl_errno($ch), curl_error($ch), __LINE__, __FILE__, '', $result);
//            }
//            else
//            {
//                write_log($url, curl_errno($ch), curl_error($ch), __LINE__, __FILE__, 'POST body '.var_export($post, true), $result);
//            }
            curl_close($ch);
        }

        return $result;
    }


    public function put($url, $params = [], $timeout = 0,$content_type = '', $headers = [], $retry = 0, $use_mon = true, $proxy = '')
    {
        try {
            //验参
            if (empty($url) || !is_array($params)) {
                throw new \Exception('params error');
            }


            $this->log['url'] = $url;
            $this->log['params'] = $params;

            //构造client
            $config = [];
            if (!empty($retry) && is_int($retry)) {
                $config['retry'] = $retry;
            }


            $client = (new Base())->initClient($config, $url, $params);

            //构造配置
            if (!empty($params)) {
                if ($content_type == 'json') {
                    $options['json'] = $params;
                } elseif ($content_type == 'multipart') {
                    $options['multipart'] = $params;
                } else {
                    $options['form_params'] = $params;
                }
            }
            if (!empty($timeout)) {
                $options['timeout'] = $timeout / 1000;
            }
            if (!empty($headers)) {
                $options['headers'] = $headers;
            }
            if (!empty($proxy)) {
                $options['proxy'] = $proxy;
            }

            $options['on_stats'] = function (TransferStats $stats) use ($use_mon, $url) {
                $this->result['http_code'] = $stats->getHandlerStat('http_code');
                if (!empty($use_mon)) {
                    //MonUtil::proxyMon($url, $stats->getHandlerStat('http_code'), round($stats->getHandlerStat('total_time'),4) * 1000);
                }
            };
            //put请求
            //@method ResponseInterface put(string|UriInterface $uri, array $options = [])
            $promise = $client->put($url, $options);
            $promise->then(
                function (Response $resp) {
                    //$ret = $this->log['response'] = $resp->getBody()->getContents();
                    $ret = empty(json_decode($ret, true)) ? $ret : json_decode($ret, true);
                    $this->result['code'] = $this->success_code;
                    $this->result['response'] = $ret;
                },
                function (RequestException $e) {
                    $this->log['response'] = $e->getMessage();
                    throw new \Exception($e->getMessage());
                }
            );
            $promise->wait();
        } catch (\Exception $e) {
            $this->result['code'] = $this->fail_code;
            $this->result['msg'] = $e->getMessage();
        } finally {
            $this->addLog('put');
            return $this->result;
        }
    }

    public function head($url, $params = [], $timeout = 0, $content_type = '',$headers = [], $retry = 0, $use_mon = true, $proxy = '')
    {
        try {
            //验参
            if (empty($url) || !is_array($params)) {
                throw new \Exception('params error');
            }
            $this->log['url'] = $url;
            $this->log['params'] = $params;
            //构造client
            $config = [];
            if (!empty($retry) && is_int($retry)) {
                $config['retry'] = $retry;
            }
            $this->client = (new Base())->initClient($config, $url, $params);
            //构造配置
            if (!empty($params)) {
                if ($content_type == 'json') {
                    $options['json'] = $params;
                } elseif ($content_type == 'multipart') {
                    $options['multipart'] = $params;
                } else {
                    $options['form_params'] = $params;
                }
            }
            if (!empty($timeout)) {
                $options['timeout'] = $timeout / 1000;
            }
            if (!empty($headers)) {
                $options['headers'] = $headers;
            }
            if (!empty($proxy)) {
                $options['proxy'] = $proxy;
            }

            $options['on_stats'] = function (TransferStats $stats) use ($use_mon, $url) {
                $this->result['http_code'] = $stats->getHandlerStat('http_code');
                if (!empty($use_mon)) {
                    //MonUtil::proxyMon($url, $stats->getHandlerStat('http_code'), round($stats->getHandlerStat('total_time'),4) * 1000);
                }
            };
            //异步head请求
            // @method ResponseInterface head(string|UriInterface $uri, array $options = [])
            $promise = $this->client->head($url, $options);
            $promise->then(
                function (Response $resp) {
                    $ret = $this->log['response'] = $resp->getBody()->getContents();
                    $ret = empty(json_decode($ret, true)) ? $ret : json_decode($ret, true);
                    $this->result['code'] = $this->success_code;
                    $this->result['response'] = $ret;
                },
                function (RequestException $e) {
                    $this->log['response'] = $e->getMessage();
                    throw new \Exception($e->getMessage());
                }
            );
            $promise->wait();
        } catch (\Exception $e) {
            $this->result['code'] = $this->fail_code;
            $this->result['msg'] = $e->getMessage();
        } finally {
            $this->addLog('head');
            return $this->result;
        }
    }

    public function CURL_POST($url, $post, $timeout = 10000, $retries = 1, $headers = false, $proxy = false, $curl_opts = array())
    {
        return self::NEW_CURL($url, $timeout,$retries, $headers, $post, $proxy, null, 'POST', $curl_opts);
    }

    /**
     * 并发执行get请求
     * @param array $urls [0=>xxx,1=>xxx]
     * @param array $params [
     *                          0=> [
     *                                  'key1' => val1,
     *                                  'key2' => val2,
     *                              ],
     *                          1=> [
     *                                  'key1' => val1,
     *                                  'key2' => val2,
     *                              ]
     *                       ]
     * @param array $timeouts [0=>xxx,1=>xxx]
     * @param int $retry 重试次数
     * @param array $use_mons [0=>false,1=>false] 默认是true
     * @param array $proxys [0=>'http://192.11.222.124:8000', 1=>'http://192.11.222.124:8000']
     * @return array
     */
    public function concurrencyGet($urls, $params = [], $timeouts = [], $retry = 0, $use_mons = [], $proxys = [])
    {
        try {
            //验参
            if (!is_array($urls) || empty($urls) || !is_array($params)) {
                throw new \Exception('params error');
            }
            //构造client
            $config = [
                'concurrent' => true
            ];
            if (!empty($retry) && is_int($retry)) {
                $config['retry'] = $retry;
            }
            $this->client = (new Base())->initClient($config, $urls, $params);
            //构造配置
            $options = [];
            foreach ($urls as $k => $url) {
                $this->log['url'][$k] = $url;
                $options[$k]['on_stats'] = function (TransferStats $stats) use ($k, $url, $use_mons) {
                    $this->con_result[$k]['http_code'] = $stats->getHandlerStat('http_code');
                    if (!isset($use_mons[$k]) || !empty($use_mons[$k])) {
                        //MonUtil::proxyMon($url, $stats->getHandlerStat('http_code'), round($stats->getHandlerStat('total_time'),4) * 1000);
                    }
                };
            }
            if (!empty($params)) {
                foreach ($params as $k => $v) {
                    $this->log['params'][$k] = $v;
                    $options[$k]['query'] = $v;
                }
            }
            if (!empty($timeouts)) {
                foreach ($timeouts as $k => $v) {
                    $options[$k]['timeout'] = $v / 1000;
                }
            }
            if (!empty($proxys)) {
                foreach ($proxys as $k => $v) {
                    $options[$k]['proxy'] = $v;
                }
            }
            //异步并发get请求
            $client = $this->client;
            $requests = function ($urls, $options) use ($client) {
                for ($i = 0; $i < count($urls); $i++) {
                    if (!isset($urls[$i]) || !isset($options[$i])) {
                        continue;
                    }
                    $each_url = $urls[$i];
                    $each_options = $options[$i];
                    yield function () use ($client, $each_url, $each_options) {
                        return $client->getAsync($each_url, $each_options);
                    };
                }
            };
            $pool = new Pool($client, $requests($urls, $options), [
                'concurrency' => $this->concurrency_num,
                'fulfilled' => function (Response $response, $index) {
                    $ret = $response->getBody()->getContents();
                    $this->log['response'][$index] = $ret;
                    $ret = empty(json_decode($ret, true)) ? $ret : json_decode($ret, true);
                    $this->con_result[$index]['code'] = $this->success_code;
                    $this->con_result[$index]['response'] = $ret;
                },
                'rejected' => function (TransferException $te, $index) {
                    $this->con_result[$index]['code'] = $this->fail_code;
                    $this->con_result[$index]['msg'] = $te->getMessage();
                    $this->log['response'][$index] = $te->getMessage();
                },
            ]);
            $pool->promise()->wait();
        } catch (\Exception $e) {
            $this->con_result[]['code'] = $this->fail_code;
            $this->con_result[]['msg'] = $e->getMessage();
            $this->log['response'][] = $e->getMessage();
        } finally {
            $this->addLog('concurrencyGet');
            return $this->con_result;
        }
    }

    /**
     * 并发执行post请求
     * @param array $urls [ 0=>'url1', 1=>'url2']
     * @param array $params [ 0=>[k1=>v1], 1=>[k2=>v2]]
     * @param array $timeouts [ 0=>0.5, 1=>2]
     * @param array $content_types  [ 0=>'json']
     * @param array $headers [
     *                          0 => [
     *                          'Content-type' => 'application/json',
     *                          'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36'
     *                          ]
     * @param int $retry 重试次数
     * @param array $use_mons [0=>false,1=>false] 默认是true
     * @param array $proxys [0=>'http://192.11.222.124:8000', 1=>'http://192.11.222.124:8000']
     * @return array
     */
    public function concurrencyPost($urls, $params = [], $timeouts = [], $content_types = [], $headers = [], $retry = 0, $use_mons = [], $proxys = [])
    {
        try {
            //验参
            if (empty($urls) || !is_array($params)) {
                throw new \Exception('params error');
            }
            //构造client
            $config = [
                'concurrent' => true
            ];
            if (!empty($retry) && is_int($retry)) {
                $config['retry'] = $retry;
            }
            $this->client = (new Base())->initClient($config, $urls, $params);
            //构造配置
            $options = [];
            foreach ($urls as $k => $url) {
                $this->log['url'][$k] = $url;
                $options[$k]['on_stats'] = function (TransferStats $stats) use ($k, $url, $use_mons) {
                    $this->con_result[$k]['http_code'] = $stats->getHandlerStat('http_code');
                    //监控打点：模块名默认为第三方服务url 指标为请求返回的HTTP_code(默认-999)与总响应时间
                    if (!isset($use_mons[$k]) || !empty($use_mons[$k])) {
                        //MonUtil::proxyMon($url, $stats->getHandlerStat('http_code'), round($stats->getHandlerStat('total_time'),4) * 1000);
                    }
                };
            }
            if (!empty($params)) {
                foreach ($params as $k => $v) {
                    $this->log['params'][$k] = $v;
                    if (isset($content_types[$k]) && $content_types[$k] == 'json') {
                        $options[$k]['json'] = $v;
                        if (isset($headers[$k])) {
                            $options[$k]['headers'] = $headers[$k];
                        }
                    } elseif (isset($content_types[$k]) && $content_types[$k] == 'multipart') {
                        $options[$k]['multipart'] = $v;
                    } else {
                        $options[$k]['form_params'] = $v;
                    }
                }
            }
            if (!empty($timeouts)) {
                foreach ($timeouts as $k => $v) {
                    $options[$k]['timeout'] = $v / 1000;
                }
            }
            if (!empty($proxys)) {
                foreach ($proxys as $k => $v) {
                    $options[$k]['proxy'] = $v;
                }
            }
            //异步并发post请求
            $client = $this->client;
            $requests = function ($urls, $options) use ($client) {
                for ($i = 0; $i < count($urls); $i++) {
                    if (!isset($urls[$i]) || !isset($options[$i])) {
                        continue;
                    }
                    $each_url = $urls[$i];
                    $each_options = $options[$i];
                    yield function () use ($client, $each_url, $each_options) {
                        return $client->postAsync($each_url, $each_options);
                    };
                }
            };
            $pool = new Pool($client, $requests($urls, $options), [
                'concurrency' => $this->concurrency_num,
                'fulfilled' => function (Response $response, $index) {
                    $ret = $response->getBody()->getContents();
                    $this->log['response'][$index] = $ret;
                    $ret = empty(json_decode($ret, true)) ? $ret : json_decode($ret, true);
                    $this->con_result[$index]['code'] = $this->success_code;
                    $this->con_result[$index]['response'] = $ret;
                },
                'rejected' => function (TransferException $te, $index) {
                    $this->con_result[$index]['code'] = $this->fail_code;
                    $this->con_result[$index]['msg'] = $te->getMessage();
                    $this->log['response'][$index] = $te->getMessage();
                },
            ]);
            $pool->promise()->wait();
        } catch (\Exception $e) {
            $this->con_result[]['code'] = $this->fail_code;
            $this->con_result[]['msg'] = $e->getMessage();
            $this->log['response'][] = $e->getMessage();
        } finally {
            $this->addLog('concurrencyPost');
            return $this->con_result;
        }
    }

    /**
     * 并发执行post+get混合请求
     * @param array $urls [ 0=>'url1', 1=>'url2']
     * @param array $methods [ 0=>'GET', 1=>'POST']
     * @param array $params [ 0=>[k1=>v1], 1=>[k2=>v2]]
     * @param array $timeouts [ 0=>0.5, 1=>2]
     * @param array $content_types  [ 0=>'json']
     * @param array $headers [
     *                          0 => [
     *                          'Content-type' => 'application/json',
     *                          'User-Agent' => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36'
     *                          ]
     * @param int $retry 重试次数
     * @param array $use_mons [0=>false,1=>false] 默认是true
     * @param array $proxys [0=>'http://192.11.222.124:8000', 1=>'http://192.11.222.124:8000']
     * @return array
     */
    public function concurrencyBlend($urls, $methods, $params = [], $timeouts = [], $content_types = [], $headers = [], $retry = 0, $use_mons = [], $proxys = [])
    {
        try {
            //验参
            if (empty($urls) || empty($methods) || !is_array($params)) {
                throw new \Exception('params error');
            }
            //构造client
            $config = [
                'concurrent' => true
            ];
            if (!empty($retry) && is_int($retry)) {
                $config['retry'] = $retry;
            }
            $this->client = (new Base())->initClient($config, $urls, $params);
            //构造配置
            $options = [];
            foreach ($urls as $k => $url) {
                $this->log['url'][$k] = $url;
                $options[$k]['on_stats'] = function (TransferStats $stats) use ($k, $url, $use_mons) {
                    $this->con_result[$k]['http_code'] = $stats->getHandlerStat('http_code');
                    if (!isset($use_mons[$k]) || !empty($use_mons[$k])) {
                        //MonUtil::proxyMon($url, $stats->getHandlerStat('http_code'), round($stats->getHandlerStat('total_time'),4) * 1000);
                    }
                };
            }
            if (!empty($params)) {
                foreach ($params as $k => $v) {
                    if (!isset($methods[$k])) {
                        continue;
                    }
                    $this->log['params'][$k] = $v;
                    if ($methods[$k] == 'POST') {
                        if (isset($content_types[$k]) && $content_types[$k] == 'json') {
                            $options[$k]['json'] = $v;
                            if (isset($headers[$k])) {
                                $options[$k]['headers'] = $headers[$k];
                            }
                        } elseif (isset($content_types[$k]) && $content_types[$k] == 'multipart') {
                            $options[$k]['multipart'] = $v;
                        } else {
                            $options[$k]['form_params'] = $v;
                        }
                    } else {
                        $options[$k]['query'] = $v;
                    }
                }
            }
            if (!empty($timeouts)) {
                foreach ($timeouts as $k => $v) {
                    $options[$k]['timeout'] = $v / 1000;
                }
            }
            if (!empty($proxys)) {
                foreach ($proxys as $k => $v) {
                    $options[$k]['proxy'] = $v;
                }
            }
            //异步并发请求
            $client = $this->client;
            $requests = function ($urls, $methods, $options) use ($client) {
                for ($i = 0; $i < count($urls); $i++) {
                    if (!isset($urls[$i]) || !isset($methods[$i]) || !isset($options[$i]) || !in_array($methods[$i], ['GET', 'POST'])) {
                        continue;
                    }
                    $each_url = $urls[$i];
                    $each_method = $methods[$i];
                    $each_options = $options[$i];
                    yield function () use ($client, $each_url, $each_method, $each_options) {
                        if ($each_method == 'POST') {
                            return $client->postAsync($each_url, $each_options);
                        }
                        return $client->getAsync($each_url, $each_options);
                    };
                }
            };
            $pool = new Pool($client, $requests($urls, $methods, $options), [
                'concurrency' => $this->concurrency_num,
                'fulfilled' => function (Response $response, $index) {
                    $ret = $response->getBody()->getContents();
                    $this->log['response'][$index] = $ret;
                    $ret = empty(json_decode($ret, true)) ? $ret : json_decode($ret, true);
                    $this->con_result[$index]['code'] = $this->success_code;
                    $this->con_result[$index]['response'] = $ret;
                },
                'rejected' => function (TransferException $te, $index) {
                    $this->con_result[$index]['code'] = $this->fail_code;
                    $this->con_result[$index]['msg'] = $te->getMessage();
                    $this->log['response'][$index] = $te->getMessage();
                },
            ]);
            $pool->promise()->wait();
        } catch (\Exception $e) {
            $this->con_result[]['code'] = $this->fail_code;
            $this->con_result[]['msg'] = $e->getMessage();
            $this->log['response'][] = $e->getMessage();
        } finally {
            $this->addLog('concurrencyBlend');
            return $this->con_result;
        }
    }

    private function addLog($method)
    {
        //过滤无意义的日志
        $log = true;
        $excludes = config('request', 'log.exclude');
        if (!empty($excludes)) {
            foreach ($excludes as $exclude) {
                if (is_array($this->log['url'])) {
                    foreach ($this->log['url'] as $url) {
                        if (strpos($url, $exclude) !== false) {
                            $log = false;
                            break;
                        }
                    }
                } else {
                    if (strpos($this->log['url'], $exclude) !== false) {
                        $log = false;
                        break;
                    }
                }
            }
        }
        if (!empty($log)) {
            $this->log['params'] = $this->change($this->log['params']);
            FileLog::info($method, json_encode($this->log));
        }
    }

    /**
     * 过滤resource类型字段
     * @param array $arr
     * @return array
     */
    private function change($arr)
    {
        foreach ($arr as $k => $v) {
            if (is_array($v)) {
                $arr[$k] = $this->change($v);
            } else {
                if (strpos(gettype($v), 'resource') !== false) {
                    $arr[$k] = gettype($v);
                }
            }
        }
        return $arr;
    }
}
