Yohobuy.php 11.2 KB
<?php

/**
 * 有货相关接口类
 * 
 * @name Yohobuy
 * @package library/Api
 * @copyright yoho.inc
 * @version 1.0 (2015-9-30 16:42:51)
 * @author fei.hong <fei.hong@yoho.cn>
 */

namespace Api;

use Plugin\Cache;

class Yohobuy
{

//    /* 正式环境 */
//    const API_URL = 'http://api2.open.yohobuy.com/';
//    const SERVICE_URL = 'http://service.api.yohobuy.com/';
//    const YOHOBUY_URL = 'http://www.yohobuy.com/';

    /* 测试环境 */
    const API_URL = 'http://test2.open.yohobuy.com/';
    const SERVICE_URL = 'http://test.service.api.yohobuy.com/';
    const YOHOBUY_URL = 'http://www.yohobuy.com/';

    /**
     * 私钥列表
     * 
     * @var array 
     */
    private static $privateKeyList = array(
        'android' => 'fd4ad5fcfa0de589ef238c0e7331b585',
        'iphone' => 'a85bb0674e08986c6b115d5e3a4884fa',
        'ipad' => 'ad9fcda2e679cf9229e37feae2cdcf80',
    );

    /**
     * 取得当前的客户端类型
     */
    public static function clientType()
    {
        // 苹果设备
        if (strstr($_SERVER['HTTP_USER_AGENT'], 'iPhone')) {
            return 'iphone';
        }
        // 苹果IPAD
        elseif (strstr($_SERVER['HTTP_USER_AGENT'], 'iPad')) {
            return 'ipad';
        }
        // 其它
        else {
            return 'android';
        }
    }

    /**
     * 取得公共的参数
     * 
     * @return array
     */
    public static function param()
    {
        $clientType = self::clientType();
        $param = array(
            'app_version' => '3.6',
            'client_type' => $clientType,
            'os_version' => 'yohobuy:h5',
            'private_key' => self::$privateKeyList[$clientType],
            'screen_size' => '720x1280',
            'v' => '6',
        );
        return $param;
    }

    /**
     * 构建URL
     * 
     * @param string $url
     * @param array $data
     * @return string
     */
    public static function httpBuildQuery($url, $data)
    {
        // 销毁私钥参数
        if (isset($data['private_key'])) {
            unset($data['private_key']);
        }
        if (strstr($url, '?') !== false) {
            $url .= '&' . http_build_query($data, null, '&');
        } else {
            $url .= '?' . http_build_query($data, null, '&');
        }
        return $url;
    }

    /**
     * get方式调用接口
     * 
     * @param string $url 接口URL
     * @param array $data 参数列表
     * @parma mixed $cache 控制是否启用接口数据的缓存(时间单位为秒). 如3600表示缓存1小时, false表示不缓存
     * @param bool $returnJson 控制是否返回json格式数据
     * @param int $timeout 超时时间
     * @return mixed
     */
    public static function get($url, $data = array(), $cache = false, $returnJson = false, $timeout = 5)
    {
        // 销毁私钥参数
        if (isset($data['private_key'])) {
            unset($data['private_key']);
        }
        if (!empty($data)) {
            $url = self::httpBuildQuery($url, $data);
        }

        /* 开启缓存的情况 */
        if ($cache) {
            // 先尝试获取一级缓存(master), 有数据则直接返回.
            $result = Cache::get($url, 'master');
            if (!empty($result)) {
                return $result;
            }
        }

        $ch = curl_init($url);
        curl_setopt($ch, CURLOPT_HEADER, 0);
        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        $result = curl_exec($ch);
        if (!$returnJson && !empty($result)) {
            $result = json_decode($result, true);
        }
        curl_close($ch);
        $data = array();

        /* 开启缓存的情况 */
        if ($cache) {
            // 接口调用异常时, 不害怕,从我们的二级缓存(slave)里再取数据.
            if (empty($result)) {
                $result = Cache::get($url, 'slave');
            }
            // 接口调用成功时,这里会设置一级(master)和二级(slave)的缓存数据.
            else {
                Cache::set($url, $result, $cache);
            }
        }

        return $result;
    }

    /**
     * post提交数据
     * 
     * @param string $url 接口URL
     * @param array $data 参数列表
     * @param bool $returnJson 控制是否返回json格式数据
     * @param int $timeout 超时时间
     * @param array $header
     * @param array $cookie
     * @return mixed
     */
    public static function post($url, $data = array(), $returnJson = false, $timeout = 5, $header = array(), $cookie = array())
    {
        $ch = curl_init($url);

        curl_setopt($ch, CURLOPT_TIMEOUT, $timeout);
        if (!empty($header)) {
            curl_setopt($ch, CURLOPT_HTTPHEADER, $header);
        } else {
            curl_setopt($ch, CURLOPT_HEADER, 0);
        }

        if (!empty($cookie)) {
            $cookie_str = array();
            foreach ($cookie as $key => $val) {
                $cookie_str[] = urlencode($key) . '=' . urlencode($val);
            }
            curl_setopt($ch, CURLOPT_COOKIE, implode(';', $cookie_str));
        }

        curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($ch, CURLOPT_POST, true);
        // 销毁私钥参数
        if (isset($data['private_key'])) {
            unset($data['private_key']);
        }
        if (!empty($data)) {
            curl_setopt($ch, CURLOPT_POSTFIELDS, $data);
        }
        $result = curl_exec($ch);
        if (!$returnJson && !empty($result)) {
            $result = json_decode($result, true);
        }
        curl_close($ch);
        $data = array();

        return $result;
    }

    /**
     * 批量调用接口
     * 
     * @param array $urlList 接口列表
     * @param array $options CURL设置项
     * @parma mixed $cache 控制是否启用接口数据的缓存(时间单位为秒). 如3600表示缓存1小时, false表示不缓存
     * @param int $timeout 超时时间,单位是秒
     * @return array
     */
    public static function getMulti($urlList = array(), $options = array(), $cache = false, $timeout = 3)
    {
        /* 开启缓存的情况 */
        if ($cache) {
            $key = md5(implode(',', array_values($urlList)));
            // 先尝试获取一级缓存(master), 有数据则直接返回.
            $result = Cache::get($key, 'master');
            if (!empty($result)) {
                return $result;
            }
        }

        $result = array();
        $response = array();
        $running = 0;
        $data = '';
        $error = '';
        $defaultOptions = array(
            CURLOPT_HEADER => 0,
            CURLOPT_RETURNTRANSFER => 1,
            CURLOPT_CONNECTTIMEOUT => $timeout,
            CURLOPT_TIMEOUT => $timeout,
            CURLOPT_NOSIGNAL => 1, //忽略所有的curl传递给php的信号,减少并发crash
        );
        $mh = curl_multi_init();
        $ch = array();

        // 应用CURL配置
        if (empty($options)) {
            $options = $defaultOptions;
        } else {
            $options = array_merge($defaultOptions, $options);
        }

        // 添加子链接句柄
        foreach ($urlList as $name => $api) {
            $ch[$name] = curl_init($api);
            curl_setopt_array($ch[$name], $options);
            curl_multi_add_handle($mh, $ch[$name]);
            $result[$name] = array();
        }

        // 调用API接口
        do {
            do {
                $status = curl_multi_exec($mh, $running);
            } while ($status == CURLM_CALL_MULTI_PERFORM);

            if ($status != CURLM_OK) {
                break;
            }

            if ($running > 0) {
                curl_multi_select($mh, 0.5);
            }
        } while ($running);

        // 获取API接口响应的结果
        foreach ($urlList as $name => $api) {
            $error = curl_error($ch[$name]);
            if ($error != '') {
                continue;
            }

            $data = curl_multi_getcontent($ch[$name]);
            if (!$data) {
                continue;
            }

            $response = json_decode($data, true);
            if (empty($response['data'])) {
                continue;
            }
            $result[$name] = $response['data'];

            curl_multi_remove_handle($mh, $ch[$name]);
            curl_close($ch[$name]);
        }
        curl_multi_close($mh);

        /* 开启缓存的情况 */
        if ($cache) {
            // 接口调用异常时, 不害怕,从我们的二级缓存(slave)里再取数据.
            if (empty($result)) {
                $result = Cache::get($key, 'slave');
            }
            // 接口调用成功时,这里会设置一级(master)和二级(slave)的缓存数据.
            else {
                Cache::set($key, $result, $cache);
            }
        }

        return $result;
    }

    /**
     * rpc调用远程服务(YAR)
     * 
     * @see http://php.net/manual/zh/yar-client.setopt.php
     * @param string $uri
     * @param string $method
     * @param array $parameters
     * @param mixed $cache 控制是否启用接口数据的缓存(时间单位为秒). 如3600表示缓存1小时, false表示不缓存
     * @param int $timeout
     * @return array
     */
    public static function yarClient($uri, $method, $parameters = array(), $cache = false, $timeout = 3000)
    {
        /* 开启缓存的情况 */
        if ($cache) {
            $key = self::httpBuildQuery($uri . $method, $parameters);
            // 先尝试获取一级缓存(master), 有数据则直接返回.
            $result = Cache::get($key, 'master');
            if (!empty($result)) {
                return $result;
            }
        }

        $client = new \Yar_Client($uri);
        $client->SetOpt(YAR_OPT_PACKAGER, 'php');
        $client->SetOpt(YAR_OPT_TIMEOUT, $timeout);
        $client->SetOpt(YAR_OPT_CONNECT_TIMEOUT, $timeout);

        try {
            $result = call_user_func_array(array($client, $method), $parameters);
        } catch (\Exception $e) {
            $result = array();
        }

        /* 开启缓存的情况 */
        if ($cache) {
            // 接口调用异常时, 不害怕,从我们的二级缓存(slave)里再取数据.
            if (empty($result)) {
                $result = Cache::get($key, 'slave');
            }
            // 接口调用成功时,这里会设置一级(master)和二级(slave)的缓存数据.
            else {
                Cache::set($key, $result, $cache);
            }
        }

        return $result;
    }

    /**
     * 并行(异步)调用远程服务
     * 
     * @see http://php.net/manual/zh/class.yar-concurrent-client.php
     * @param string $uri
     * @param string $method
     * @param array $parameter
     * @param callable $callback
     * @param int $timeout
     * @return void
     */
    public static function yarConcurrentCall($uri, $method, $parameters, $callback, $timeout = 3000)
    {
        \Yar_Concurrent_Client::call($uri, $method, $parameters, $callback, null, array(
            YAR_OPT_PACKAGER => 'php',
            YAR_OPT_TIMEOUT => $timeout,
            YAR_OPT_CONNECT_TIMEOUT => $timeout
        ));
    }

    public static function yarConcurrentLoop($callback = null)
    {
        \Yar_Concurrent_Client::loop($callback);
    }

}