├── src ├── .gitkeep ├── Exceptions │ ├── Exception.php │ ├── HttpException.php │ ├── ConfigNotFoundException.php │ ├── ConfigValidateException.php │ ├── InvalidArgumentException.php │ └── NoQueryAvailableException.php ├── Config.php ├── Factory.php ├── Channel │ ├── Channel.php │ ├── JiSuChannel.php │ ├── JuHeChannel.php │ ├── KuaiDi100Channel.php │ ├── ShuJuZhiHuiChannel.php │ └── KuaiDiBirdChannel.php ├── Logistics.php ├── SupportLogistics.php └── Traits │ └── HttpRequest.php ├── .editorconfig ├── composer.json └── README.md /src/.gitkeep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 4 6 | end_of_line = lf 7 | charset = utf-8 8 | trim_trailing_whitespace = true 9 | insert_final_newline = false 10 | 11 | [*.{vue,js,scss}] 12 | charset = utf-8 13 | indent_style = space 14 | indent_size = 2 15 | end_of_line = lf 16 | insert_final_newline = true 17 | trim_trailing_whitespace = true 18 | 19 | [*.md] 20 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /src/Exceptions/Exception.php: -------------------------------------------------------------------------------- 1 | 14 | * 15 | * This source file is subject to the MIT license that is bundled 16 | * with this source code in the file LICENSE. 17 | */ 18 | 19 | namespace Wythe\Logistics\Exceptions; 20 | 21 | class Exception extends \Exception 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Exceptions/HttpException.php: -------------------------------------------------------------------------------- 1 | 14 | * 15 | * This source file is subject to the MIT license that is bundled 16 | * with this source code in the file LICENSE. 17 | */ 18 | 19 | namespace Wythe\Logistics\Exceptions; 20 | 21 | class HttpException extends Exception 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "wythe\/logistics", 3 | "description": "查询快递物流信息", 4 | "license": "MIT", 5 | "authors": [ 6 | { 7 | "name": "wythe", 8 | "email": "wythe.huangw@gmail.com" 9 | } 10 | ], 11 | "require": { 12 | "php": ">=7.0" 13 | }, 14 | "require-dev": { 15 | "phpunit/phpunit": "~6", 16 | "mockery/mockery": "^1.2" 17 | }, 18 | "autoload": { 19 | "psr-4": { 20 | "Wythe\\Logistics\\": "src" 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Exceptions/ConfigNotFoundException.php: -------------------------------------------------------------------------------- 1 | 14 | * 15 | * This source file is subject to the MIT license that is bundled 16 | * with this source code in the file LICENSE. 17 | */ 18 | 19 | namespace Wythe\Logistics\Exceptions; 20 | 21 | class ConfigNotFoundException extends Exception 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Exceptions/ConfigValidateException.php: -------------------------------------------------------------------------------- 1 | 14 | * 15 | * This source file is subject to the MIT license that is bundled 16 | * with this source code in the file LICENSE. 17 | */ 18 | 19 | namespace Wythe\Logistics\Exceptions; 20 | 21 | class ConfigValidateException extends Exception 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Exceptions/InvalidArgumentException.php: -------------------------------------------------------------------------------- 1 | 14 | * 15 | * This source file is subject to the MIT license that is bundled 16 | * with this source code in the file LICENSE. 17 | */ 18 | 19 | namespace Wythe\Logistics\Exceptions; 20 | 21 | class InvalidArgumentException extends Exception 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Exceptions/NoQueryAvailableException.php: -------------------------------------------------------------------------------- 1 | 14 | * 15 | * This source file is subject to the MIT license that is bundled 16 | * with this source code in the file LICENSE. 17 | */ 18 | 19 | namespace Wythe\Logistics\Exceptions; 20 | 21 | class NoQueryAvailableException extends Exception 22 | { 23 | } 24 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | 14 | * 15 | * This source file is subject to the MIT license that is bundled 16 | * with this source code in the file LICENSE. 17 | */ 18 | 19 | namespace Wythe\Logistics; 20 | 21 | use Wythe\Logistics\Exceptions\ConfigNotFoundException; 22 | use Wythe\Logistics\Exceptions\ConfigValidateException; 23 | 24 | /** 25 | * 配置类. 26 | */ 27 | class Config 28 | { 29 | /** 30 | * 配置. 31 | * 32 | * @var array 33 | */ 34 | protected static $config; 35 | 36 | /** 37 | * 配置验证规则. 38 | * 39 | * @var array 40 | */ 41 | protected $validateRule = [ 42 | 'juhe' => ['app_key', 'vip'], 43 | 'jisu' => ['app_key', 'vip'], 44 | 'shujuzhihui' => ['app_key', 'vip'], 45 | 'kuaidi100' => ['app_key', 'app_secret'], 46 | 'kuaidibird' => ['app_key', 'app_secret', 'vip'], 47 | ]; 48 | 49 | /** 50 | * 验证规则. 51 | * 52 | * @throws ConfigNotFoundException 53 | * @throws ConfigValidateException 54 | */ 55 | protected function validate(string $channel) 56 | { 57 | if (!in_array($channel, array_keys($this->validateRule))) { 58 | throw new ConfigNotFoundException('没找到相对应配置规则'); 59 | } 60 | $keys = array_keys(static::$config[$channel]); 61 | $intersect = array_intersect($this->validateRule[$channel], $keys); 62 | if (count($intersect) !== count($this->validateRule[$channel])) { 63 | throw new ConfigValidateException('规则验证失败'); 64 | } 65 | } 66 | 67 | /** 68 | * 设置配置. 69 | * 70 | * @throws ConfigNotFoundException 71 | * @throws ConfigValidateException 72 | */ 73 | public function setConfig(array $params) 74 | { 75 | static::$config = $params; 76 | foreach (static::$config as $channel => $param) { 77 | $this->validate($channel); 78 | } 79 | } 80 | 81 | /** 82 | * 获取配置. 83 | */ 84 | public function getConfig(string $key): array 85 | { 86 | return static::$config[$key]; 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/Factory.php: -------------------------------------------------------------------------------- 1 | 14 | * 15 | * This source file is subject to the MIT license that is bundled 16 | * with this source code in the file LICENSE. 17 | */ 18 | 19 | namespace Wythe\Logistics; 20 | 21 | use Wythe\Logistics\Exceptions\InvalidArgumentException; 22 | use Wythe\Logistics\Exceptions\Exception; 23 | use Wythe\Logistics\Channel\Channel; 24 | 25 | class Factory 26 | { 27 | private $defaultChannel = 'kuaiDiBird'; 28 | 29 | protected $channels = []; 30 | 31 | /** 32 | * 获取默认查询类名称. 33 | * 34 | * @throws \Wythe\Logistics\Exceptions\Exception 35 | */ 36 | public function getDefault(): string 37 | { 38 | if (empty($this->defaultChannel)) { 39 | throw new Exception('No default query class name configured.'); 40 | } 41 | 42 | return $this->defaultChannel; 43 | } 44 | 45 | /** 46 | * 设置默认查询类名称. 47 | */ 48 | public function setDefault($name) 49 | { 50 | $this->defaultChannel = $name; 51 | } 52 | 53 | /** 54 | * 数组元素存储查询对象 55 | * 56 | * @return mixed 57 | * 58 | * @throws \Wythe\Logistics\Exceptions\InvalidArgumentException 59 | */ 60 | public function createChannel(string $name = '') 61 | { 62 | $name = $name ?: $this->defaultChannel; 63 | if (!isset($this->channels[$name])) { 64 | $className = $this->formatClassName($name); 65 | if (!class_exists($className)) { 66 | throw new InvalidArgumentException(sprintf('Class "%s" not exists.', $className)); 67 | } 68 | $instance = new $className(); 69 | if (!($instance instanceof Channel)) { 70 | throw new InvalidArgumentException(sprintf('Class "%s" not inherited from %s.', $name, Channel::class)); 71 | } 72 | $this->channels[$name] = $instance; 73 | } 74 | 75 | return $this->channels[$name]; 76 | } 77 | 78 | /** 79 | * 格式化类的名称. 80 | */ 81 | protected function formatClassName(string $name): string 82 | { 83 | if (class_exists($name)) { 84 | return $name; 85 | } 86 | $name = ucfirst(str_replace(['-', '_', ' '], '', $name)); 87 | 88 | return __NAMESPACE__."\\Channel\\{$name}Channel"; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/Channel/Channel.php: -------------------------------------------------------------------------------- 1 | 15 | * 16 | * This source file is subject to the MIT license that is bundled 17 | * with this source code in the file LICENSE. 18 | */ 19 | 20 | namespace Wythe\Logistics\Channel; 21 | 22 | use Wythe\Logistics\Config; 23 | use Wythe\Logistics\Traits\HttpRequest; 24 | 25 | abstract class Channel 26 | { 27 | /* 28 | * HTTP 请求 29 | */ 30 | use HttpRequest; 31 | 32 | /** 33 | * 渠道URL. 34 | * 35 | * @var string 36 | */ 37 | protected $url; 38 | 39 | /** 40 | * 请求资源. 41 | * 42 | * @var array 43 | */ 44 | protected $response; 45 | 46 | /** 47 | * 请求选项. 48 | * 49 | * @var array 50 | */ 51 | protected $option = []; 52 | 53 | /** 54 | * 设置请求选项. 55 | * 56 | * @return \Wythe\Logistics\Channel\Channel 57 | */ 58 | public function setRequestOption(array $option): self 59 | { 60 | if (!empty($this->option)) { 61 | if (isset($option['header']) && isset($this->option['header'])) { 62 | $this->option['header'] = array_merge($this->option['header'], $option['header']); 63 | } 64 | if (isset($option['proxy'])) { 65 | $this->option['proxy'] = $option['proxy']; 66 | } 67 | } else { 68 | $this->option = $option; 69 | } 70 | 71 | return $this; 72 | } 73 | 74 | /** 75 | * 获取实例化的类名称. 76 | */ 77 | protected function getClassName(): string 78 | { 79 | $className = basename(str_replace('\\', '/', get_class($this))); 80 | 81 | return preg_replace('/Channel/', '', $className); 82 | } 83 | 84 | /** 85 | * 获取配置. 86 | */ 87 | protected function getChannelConfig(): array 88 | { 89 | return (new Config())->getConfig(strtolower($this->getClassName())); 90 | } 91 | 92 | /** 93 | * 调用查询接口. 94 | */ 95 | abstract public function request(string $code, string $company = ''): array; 96 | 97 | /** 98 | * 转换为数组. 99 | * 100 | * @param string|array $response 101 | */ 102 | abstract protected function toArray($response); 103 | 104 | /** 105 | * 格式物流信息. 106 | * 107 | * @return mixed 108 | */ 109 | abstract protected function format(); 110 | } 111 | -------------------------------------------------------------------------------- /src/Channel/JiSuChannel.php: -------------------------------------------------------------------------------- 1 | 15 | * 16 | * This source file is subject to the MIT license that is bundled 17 | * with this source code in the file LICENSE. 18 | */ 19 | 20 | namespace Wythe\Logistics\Channel; 21 | 22 | use Wythe\Logistics\Exceptions\HttpException; 23 | 24 | /** 25 | * 极速数据物流查询. 26 | */ 27 | class JiSuChannel extends Channel 28 | { 29 | /** 30 | * JiSuChannel constructor. 31 | */ 32 | public function __construct() 33 | { 34 | $this->url = 'https://api.jisuapi.com/express/query'; 35 | } 36 | 37 | /** 38 | * 请求 39 | * 40 | * @throws \Wythe\Logistics\Exceptions\HttpException 41 | */ 42 | public function request(string $code, string $company = ''): array 43 | { 44 | try { 45 | $config = $this->getChannelConfig(); 46 | $params = ['appkey' => $config['app_key'], 'type' => 'auto', 'number' => $code]; 47 | $response = $this->get($this->url, $params); 48 | $this->toArray($response); 49 | $this->format(); 50 | 51 | return $this->response; 52 | } catch (HttpException $exception) { 53 | throw new HttpException($exception->getMessage()); 54 | } 55 | } 56 | 57 | /** 58 | * 统一物流信息. 59 | */ 60 | protected function format() 61 | { 62 | if (!empty($this->response['data'])) { 63 | $formatData = []; 64 | foreach ($this->response['data'] as $datum) { 65 | $formatData[] = ['time' => $datum['time'], 'description' => $datum['status']]; 66 | } 67 | $this->response['data'] = $formatData; 68 | } 69 | } 70 | 71 | /** 72 | * 转为数组. 73 | * 74 | * @param array|string $response 75 | */ 76 | protected function toArray($response) 77 | { 78 | $jsonToArray = \json_decode($response, true); 79 | if (empty($jsonToArray)) { 80 | $this->response = [ 81 | 'status' => 0, 82 | 'message' => '请求发生不知名错误, 查询不到物流信息', 83 | 'error_code' => 0, 84 | 'data' => [], 85 | 'logistics_company' => '', 86 | ]; 87 | } else { 88 | if (0 === $jsonToArray['status']) { 89 | $this->response = [ 90 | 'status' => 1, 91 | 'message' => 'ok', 92 | 'error_code' => 0, 93 | 'data' => $jsonToArray['result']['list'], 94 | 'logistics_company' => '', 95 | ]; 96 | } else { 97 | $this->response = [ 98 | 'status' => 0, 99 | 'message' => $jsonToArray['msg'], 100 | 'error_code' => $jsonToArray['status'], 101 | 'data' => [], 102 | 'logistics_company' => '', 103 | ]; 104 | } 105 | } 106 | } 107 | } 108 | -------------------------------------------------------------------------------- /src/Channel/JuHeChannel.php: -------------------------------------------------------------------------------- 1 | 15 | * 16 | * This source file is subject to the MIT license that is bundled 17 | * with this source code in the file LICENSE. 18 | */ 19 | 20 | namespace Wythe\Logistics\Channel; 21 | 22 | /** 23 | * 聚合数据 查询物流接口. 24 | */ 25 | class JuHeChannel extends Channel 26 | { 27 | /** 28 | * JuHeChannel constructor. 29 | */ 30 | public function __construct() 31 | { 32 | $this->url = 'http://v.juhe.cn/exp/index'; 33 | } 34 | 35 | /** 36 | * 构造请求参数. 37 | * 38 | * @throws \Wythe\Logistics\Exceptions\HttpException 39 | */ 40 | private function setRequestParam(string $code, string $company): array 41 | { 42 | $config = $this->getChannelConfig(); 43 | $companyCode = (new \Wythe\Logistics\SupportLogistics())->getCode($this->getClassName(), $code, $company); 44 | 45 | return ['key' => $config['app_key'], 'com' => $companyCode]; 46 | } 47 | 48 | /** 49 | * 请求 50 | * 51 | * @throws \Exception 52 | */ 53 | public function request(string $code, string $company = ''): array 54 | { 55 | try { 56 | $params = $this->setRequestParam($code, $company); 57 | $params['no'] = $code; 58 | $response = $this->get($this->url, $params); 59 | $this->toArray($response); 60 | $this->format(); 61 | 62 | return $this->response; 63 | } catch (\Exception $exception) { 64 | throw new \Exception($exception->getMessage()); 65 | } 66 | } 67 | 68 | /** 69 | * 转换为数组. 70 | * 71 | * @param array|string $response 72 | */ 73 | protected function toArray($response) 74 | { 75 | $jsonToArray = \json_decode($response, true); 76 | if (empty($jsonToArray)) { 77 | $this->response = [ 78 | 'status' => 0, 79 | 'message' => '请求发生不知名错误, 查询不到物流信息', 80 | 'error_code' => 0, 81 | 'data' => [], 82 | 'logistics_company' => '', 83 | ]; 84 | } else { 85 | if (0 === $jsonToArray['error_code']) { 86 | $this->response = [ 87 | 'status' => 1, 88 | 'message' => 'ok', 89 | 'error_code' => 0, 90 | 'data' => $jsonToArray['result']['list'], 91 | 'logistics_company' => $jsonToArray['result']['company'], 92 | ]; 93 | } else { 94 | $this->response = [ 95 | 'status' => 0, 96 | 'message' => $jsonToArray['reason'], 97 | 'error_code' => $jsonToArray['error_code'], 98 | 'data' => [], 99 | 'logistics_company' => '', 100 | ]; 101 | } 102 | } 103 | } 104 | 105 | /** 106 | * 格式化数组. 107 | */ 108 | protected function format() 109 | { 110 | if (!empty($this->response['data'])) { 111 | $formatData = []; 112 | foreach ($this->response['data'] as $datum) { 113 | $formatData[] = ['time' => $datum['datetime'], 'description' => $datum['remark']]; 114 | } 115 | $this->response['data'] = $formatData; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/Channel/KuaiDi100Channel.php: -------------------------------------------------------------------------------- 1 | 15 | * 16 | * This source file is subject to the MIT license that is bundled 17 | * with this source code in the file LICENSE. 18 | */ 19 | 20 | namespace Wythe\Logistics\Channel; 21 | 22 | class KuaiDi100Channel extends Channel 23 | { 24 | /** 25 | * 构造函数. 26 | * 27 | * Kuaidi100Channel constructor. 28 | */ 29 | public function __construct() 30 | { 31 | $this->url = 'http://poll.kuaidi100.com/poll/query.do'; 32 | } 33 | 34 | /** 35 | * 调用快递100接口. 36 | * 37 | * @throws \Exception 38 | */ 39 | public function request(string $code, string $company = ''): array 40 | { 41 | try { 42 | $companyCode = (new \Wythe\Logistics\SupportLogistics())->getCode($this->getClassName(), $code, $company); 43 | $postJson = \json_encode([ 44 | 'num' => $code, 45 | 'com' => $companyCode, 46 | ]); 47 | $config = $this->getChannelConfig(); 48 | $params = [ 49 | 'customer' => $config['app_secret'], 50 | 'sign' => \strtoupper(\md5($postJson.$config['app_key'].$config['app_secret'])), 51 | 'param' => $postJson, 52 | ]; 53 | $response = $this->post($this->url, $params); 54 | $this->toArray($response); 55 | $this->format(); 56 | 57 | return $this->response; 58 | } catch (\Exception $exception) { 59 | throw new \Exception($exception->getMessage()); 60 | } 61 | } 62 | 63 | /** 64 | * 转为数组. 65 | * 66 | * @param array|string $response 67 | */ 68 | protected function toArray($response) 69 | { 70 | $jsonToArray = \json_decode($response, true); 71 | if (empty($jsonToArray)) { 72 | $this->response = [ 73 | 'status' => 0, 74 | 'message' => '请求发生不知名错误, 查询不到物流信息', 75 | 'error_code' => 0, 76 | 'data' => [], 77 | 'logistics_company' => '', 78 | ]; 79 | } else { 80 | if (isset($jsonToArray['state'])) { 81 | $this->response = [ 82 | 'status' => 1, 83 | 'message' => 'ok', 84 | 'error_code' => 0, 85 | 'data' => $jsonToArray['data'], 86 | 'logistics_company' => $jsonToArray['com'], 87 | ]; 88 | } else { 89 | $this->response = [ 90 | 'status' => 0, 91 | 'message' => $jsonToArray['message'], 92 | 'error_code' => $jsonToArray['state'], 93 | 'data' => [], 94 | 'logistics_company' => '', 95 | ]; 96 | } 97 | } 98 | } 99 | 100 | /** 101 | * 统一物流信息. 102 | * 103 | * @return mixed|void 104 | */ 105 | protected function format() 106 | { 107 | if (!empty($this->response['data'])) { 108 | $formatData = []; 109 | foreach ($this->response['data'] as $datum) { 110 | $formatData[] = ['time' => $datum['ftime'], 'description' => $datum['context']]; 111 | } 112 | $this->response['data'] = $formatData; 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/Channel/ShuJuZhiHuiChannel.php: -------------------------------------------------------------------------------- 1 | 15 | * 16 | * This source file is subject to the MIT license that is bundled 17 | * with this source code in the file LICENSE. 18 | */ 19 | 20 | namespace Wythe\Logistics\Channel; 21 | 22 | use Wythe\Logistics\Exceptions\HttpException; 23 | 24 | /** 25 | * 数据智汇 查询物流接口. 26 | */ 27 | class ShuJuZhiHuiChannel extends Channel 28 | { 29 | /** 30 | * 错误信息. 31 | * 32 | * @var array 33 | */ 34 | private $errorMessage = [ 35 | 2000 => '系统繁忙', 36 | 1100 => '暂无相关数据', 37 | 1001 => '缺失必要参数expressNo', 38 | 10012 => '请输入appKey!', 39 | 50010 => 'appKey输入错误!', 40 | 50013 => 'API次数已用完,请续费后再调用', 41 | 50110 => '接口维护', 42 | 50111 => '接口停用', 43 | ]; 44 | 45 | /** 46 | * ShuJuZhiHuiChannel constructor. 47 | */ 48 | public function __construct() 49 | { 50 | $this->url = 'http://api.shujuzhihui.cn/api/sjzhApi/searchExpress'; 51 | } 52 | 53 | /** 54 | * 请求 55 | * 56 | * @throws \Wythe\Logistics\Exceptions\HttpException 57 | */ 58 | public function request(string $code, string $company = ''): array 59 | { 60 | try { 61 | $config = $this->getChannelConfig(); 62 | $params = ['appKey' => $config['app_key'], 'expressNo' => $code]; 63 | $response = $this->post($this->url, $params); 64 | $this->toArray($response); 65 | $this->format(); 66 | 67 | return $this->response; 68 | } catch (HttpException $exception) { 69 | throw new HttpException($exception->getMessage()); 70 | } 71 | } 72 | 73 | /** 74 | * 转换为数组. 75 | * 76 | * @param array|string $response 77 | */ 78 | protected function toArray($response) 79 | { 80 | $jsonToArray = \json_decode($response, true); 81 | if (empty($jsonToArray)) { 82 | $this->response = [ 83 | 'status' => 0, 84 | 'message' => '请求发生不知名错误, 查询不到物流信息', 85 | 'error_code' => 0, 86 | 'data' => [], 87 | 'logistics_company' => '', 88 | ]; 89 | } else { 90 | if (0 === $jsonToArray['ERRORCODE']) { 91 | $this->response = [ 92 | 'status' => 1, 93 | 'message' => 'ok', 94 | 'error_code' => 0, 95 | 'data' => $jsonToArray['RESULT']['context'], 96 | 'logistics_company' => $jsonToArray['RESULT']['com'], 97 | ]; 98 | } else { 99 | $this->response = [ 100 | 'status' => 0, 101 | 'message' => $this->errorMessage[$jsonToArray['ERRORCODE']], 102 | 'error_code' => $jsonToArray['ERRORCODE'], 103 | 'data' => [], 104 | 'logistics_company' => '', 105 | ]; 106 | } 107 | } 108 | } 109 | 110 | /** 111 | * 格式化数组. 112 | */ 113 | protected function format() 114 | { 115 | if (!empty($this->response['data'])) { 116 | $formatData = []; 117 | foreach ($this->response['data'] as $datum) { 118 | $formatData[] = ['time' => $datum['time'], 'description' => $datum['desc']]; 119 | } 120 | $this->response['data'] = $formatData; 121 | } 122 | } 123 | } 124 | -------------------------------------------------------------------------------- /src/Channel/KuaiDiBirdChannel.php: -------------------------------------------------------------------------------- 1 | 15 | * 16 | * This source file is subject to the MIT license that is bundled 17 | * with this source code in the file LICENSE. 18 | */ 19 | 20 | namespace Wythe\Logistics\Channel; 21 | 22 | use Wythe\Logistics\Traits\HttpRequest; 23 | 24 | /** 25 | * 快递鸟查询物流接口. 26 | */ 27 | class KuaiDiBirdChannel extends Channel 28 | { 29 | use HttpRequest; 30 | 31 | /** 32 | * 增值请求指令. 33 | * 34 | * @var int 35 | */ 36 | const PAYED = 8001; 37 | 38 | /** 39 | * 免费请求指令. 40 | * 41 | * @var int 42 | */ 43 | const FREE = 1002; 44 | 45 | /** 46 | * KuaiDiBirdChannel constructor. 47 | */ 48 | public function __construct() 49 | { 50 | $this->url = 'http://api.kdniao.com/Ebusiness/EbusinessOrderHandle.aspx'; 51 | } 52 | 53 | /** 54 | * 拼接请求URL链接. 55 | * 56 | * @param string $requestData 请求的数据 57 | */ 58 | public function setRequestParam(string $requestData): array 59 | { 60 | return [ 61 | 'EBusinessID' => $this->getChannelConfig()['app_secret'], 62 | 'DataType' => 2, 63 | 'RequestType' => $this->getChannelConfig()['vip'] ? self::PAYED : self::FREE, 64 | 'RequestData' => \urlencode($requestData), 65 | 'DataSign' => $this->encrypt($requestData, $this->getChannelConfig()['app_key']), 66 | ]; 67 | } 68 | 69 | /** 70 | * 编码 71 | */ 72 | private function encrypt(string $data, string $appKey): string 73 | { 74 | return \urlencode(\base64_encode(\md5($data.$appKey))); 75 | } 76 | 77 | /** 78 | * 请求 79 | * 80 | * @throws \Exception 81 | */ 82 | public function request(string $code, string $company = ''): array 83 | { 84 | try { 85 | $companyCode = (new \Wythe\Logistics\SupportLogistics())->getCode($this->getClassName(), $code, $company); 86 | $requestData = $this->setRequestParam(\json_encode(['OrderCode' => '', 'ShipperCode' => $companyCode, 'LogisticCode' => $code])); 87 | $response = $this->post($this->url, $requestData, ['header' => 'application/x-www-form-urlencoded;charset=utf-8']); 88 | $this->toArray($response); 89 | $this->format(); 90 | 91 | return $this->response; 92 | } catch (\Exception $exception) { 93 | throw new \Exception($exception->getMessage()); 94 | } 95 | } 96 | 97 | /** 98 | * 格式化. 99 | */ 100 | protected function format() 101 | { 102 | if (!empty($this->response['data'])) { 103 | $formatData = []; 104 | foreach ($this->response['data'] as $datum) { 105 | $formatData[] = ['time' => str_replace('/', '-', $datum['AcceptTime']), 'description' => $datum['AcceptStation']]; 106 | } 107 | $this->response['data'] = $formatData; 108 | } 109 | } 110 | 111 | /** 112 | * 转换为数组. 113 | * 114 | * @param array|string $response 115 | */ 116 | protected function toArray($response) 117 | { 118 | $jsonToArray = \json_decode($response, true); 119 | if (empty($jsonToArray)) { 120 | $this->response = [ 121 | 'status' => 0, 122 | 'message' => '请求发生不知名错误, 查询不到物流信息', 123 | 'error_code' => 0, 124 | 'data' => [], 125 | 'logistics_company' => '', 126 | ]; 127 | } else { 128 | if ($jsonToArray['Success']) { 129 | $this->response = [ 130 | 'status' => 1, 131 | 'message' => 'ok', 132 | 'error_code' => 0, 133 | 'data' => $jsonToArray['Traces'], 134 | 'logistics_company' => $jsonToArray['ShipperCode'], 135 | ]; 136 | } else { 137 | $this->response = [ 138 | 'status' => 0, 139 | 'message' => $jsonToArray['Reason'], 140 | 'error_code' => $jsonToArray['State'], 141 | 'data' => [], 142 | 'logistics_company' => '', 143 | ]; 144 | } 145 | } 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /src/Logistics.php: -------------------------------------------------------------------------------- 1 | 9 | * 10 | * This source file is subject to the MIT license that is bundled 11 | * with this source code in the file LICENSE. 12 | */ 13 | 14 | namespace Wythe\Logistics; 15 | 16 | use Wythe\Logistics\Exceptions\InvalidArgumentException; 17 | use Wythe\Logistics\Exceptions\NoQueryAvailableException; 18 | 19 | /** 20 | * 抓取物流信息. 21 | */ 22 | class Logistics 23 | { 24 | /** 25 | * 渠道接口总数. 26 | * 27 | * @var int 28 | */ 29 | const CHANNEL_NUMBER = 7; 30 | 31 | /** 32 | * 成功 33 | * 34 | * @var string 35 | */ 36 | const SUCCESS = 'success'; 37 | 38 | /** 39 | * 失败. 40 | * 41 | * @string 42 | */ 43 | const FAILURE = 'failure'; 44 | 45 | /** 46 | * 快递渠道工厂 47 | * 48 | * @var \Wythe\Logistics\Factory 49 | */ 50 | protected $factory; 51 | 52 | /** 53 | * 构造函数. 54 | */ 55 | public function __construct(array $config) 56 | { 57 | (new Config())->setConfig($config); 58 | $this->factory = new Factory(); 59 | } 60 | 61 | private function channelMap(string $channelName): string 62 | { 63 | $channels = [ 64 | 'juhe' => 'juHe', 65 | 'shujuzhihui' => 'shuJuZhiHui', 66 | 'jisu' => 'jiSu', 67 | 'kuaidibird' => 'kuaiDiBird', 68 | 'kuaidi100' => 'kuaiDi100', 69 | ]; 70 | 71 | return $channels[$channelName]; 72 | } 73 | 74 | /** 75 | * 通过接口获取物流信息. 76 | * 77 | * @param array $channels 78 | * 79 | * @throws \Wythe\Logistics\Exceptions\InvalidArgumentException 80 | * @throws \Wythe\Logistics\Exceptions\NoQueryAvailableException 81 | */ 82 | public function query(string $code, $channels = ['kuaidibird'], string $company = ''): array 83 | { 84 | $results = []; 85 | if (empty($code)) { 86 | throw new InvalidArgumentException('code arguments cannot empty.'); 87 | } 88 | if (!empty($channels) && is_string($channels)) { 89 | $channels = explode(',', $channels); 90 | } 91 | foreach ($channels as $channelName) { 92 | $channel = $this->channelMap($channelName); 93 | try { 94 | $request = $this->factory->createChannel($channel)->request($code, $company); 95 | if (1 === $request['status']) { 96 | $results[$channelName] = [ 97 | 'channel' => $channelName, 98 | 'status' => self::SUCCESS, 99 | 'result' => $request, 100 | ]; 101 | } else { 102 | $results[$channelName] = [ 103 | 'channel' => $channelName, 104 | 'status' => self::FAILURE, 105 | 'exception' => $request['message'], 106 | ]; 107 | } 108 | } catch (\Exception $exception) { 109 | $results[$channelName] = [ 110 | 'channel' => $channelName, 111 | 'status' => self::FAILURE, 112 | 'exception' => $exception->getMessage(), 113 | ]; 114 | } 115 | } 116 | $collectionOfException = array_column($results, 'exception'); 117 | if (self::CHANNEL_NUMBER === count($collectionOfException)) { 118 | throw new NoQueryAvailableException('sorry! no channel class available'); 119 | } 120 | 121 | return $results; 122 | } 123 | 124 | /** 125 | * 通过代理IP获取物流信息. 126 | * 127 | * @param array $channels 128 | * 129 | * @throws \Wythe\Logistics\Exceptions\InvalidArgumentException 130 | * @throws \Wythe\Logistics\Exceptions\NoQueryAvailableException 131 | */ 132 | public function queryByProxy(array $proxy, string $code, $channels = ['kuaidiBird'], string $company = ''): array 133 | { 134 | $results = []; 135 | if (empty($code)) { 136 | throw new InvalidArgumentException('code arguments cannot empty.'); 137 | } 138 | if (!empty($channels) && is_string($channels)) { 139 | $channels = explode(',', $channels); 140 | } 141 | foreach ($channels as $channel) { 142 | try { 143 | $request = $this->factory->createChannel($channel)->request($code, $company); 144 | if (1 === $request['status']) { 145 | $results[$channel] = [ 146 | 'channel' => $channel, 147 | 'status' => self::SUCCESS, 148 | 'result' => $request, 149 | ]; 150 | } else { 151 | $results[$channel] = [ 152 | 'channel' => $channel, 153 | 'status' => self::FAILURE, 154 | 'exception' => $request['message'], 155 | ]; 156 | } 157 | } catch (\Exception $exception) { 158 | $results[$channel] = [ 159 | 'channel' => $channel, 160 | 'status' => self::FAILURE, 161 | 'exception' => $exception->getMessage(), 162 | ]; 163 | } 164 | } 165 | $collectionOfException = array_column($results, 'exception'); 166 | if (self::CHANNEL_NUMBER === count($collectionOfException)) { 167 | throw new NoQueryAvailableException('sorry! no channel class available'); 168 | } 169 | 170 | return $results; 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/SupportLogistics.php: -------------------------------------------------------------------------------- 1 | 14 | * 15 | * This source file is subject to the MIT license that is bundled 16 | * with this source code in the file LICENSE. 17 | */ 18 | 19 | namespace Wythe\Logistics; 20 | 21 | use Wythe\Logistics\Traits\HttpRequest; 22 | 23 | class SupportLogistics 24 | { 25 | use HttpRequest; 26 | 27 | private $companyList = [ 28 | '顺丰' => ['juhe' => 'sf', 'kuaidi100' => 'shunfeng', 'kuaidibird' => 'SF'], 29 | '申通' => ['juhe' => 'sto', 'kuaidi100' => 'shentong', 'kuaidibird' => 'STO'], 30 | '中通' => ['juhe' => 'zto', 'kuaidi100' => 'zhongtong', 'kuaidibird' => 'ZTO'], 31 | '圆通' => ['juhe' => 'yt', 'kuaidi100' => 'yuantong', 'kuaidibird' => 'YTO'], 32 | '韵达' => ['juhe' => 'yd', 'kuaidi100' => 'yunda', 'kuaidibird' => 'YD'], 33 | '天天' => ['juhe' => 'tt', 'kuaidi100' => 'tiantian', 'kuaidibird' => 'HHTT'], 34 | 'ems' => ['juhe' => 'ems', 'kuaidi100' => 'ems', 'kuaidibird' => 'EMS'], 35 | 'ems国际' => ['juhe' => 'emsg', 'kuaidi100' => 'emsguoji', 'kuaidibird' => 'EMSGJ'], 36 | '汇通' => ['juhe' => 'ht', 'kuaidi100' => 'huitongkuaidi', 'kuaidibird' => ''], 37 | '全峰' => ['juhe' => 'qf', 'kuaidi100' => 'quanfengkuaidi', 'kuaidibird' => ''], 38 | '德邦' => ['juhe' => 'db', 'kuaidi100' => 'debangwuliu', 'kuaidibird' => 'DBL'], 39 | '国通' => ['juhe' => 'gt', 'kuaidi100' => 'guotongkuaidi', 'kuaidibird' => ''], 40 | '京东' => ['juhe' => 'jd', 'kuaidi100' => 'jd', 'kuaidibird' => 'JD'], 41 | '宅急送' => ['juhe' => 'zjs', 'kuaidi100' => 'zhaijisong', 'kuaidibird' => 'ZJS'], 42 | 'fedex' => ['juhe' => 'fedex', 'kuaidi100' => 'fedex', 'kuaidibird' => 'FEDEX_GJ'], 43 | 'ups' => ['juhe' => 'ups', 'kuaidi100' => '', 'kuaidibird' => 'UPS'], 44 | '中铁' => ['juhe' => 'ztky', 'kuaidi100' => '', 'kuaidibird' => 'ZHWL'], 45 | '佳吉' => ['juhe' => 'jiaji', 'kuaidi100' => 'jiajiwuliu', 'kuaidibird' => 'CNEX'], 46 | '速尔' => ['juhe' => 'suer', 'kuaidi100' => 'suer', 'kuaidibird' => 'SURE'], 47 | '信丰' => ['juhe' => 'xfwl', 'kuaidi100' => 'xinfengwuliu', 'kuaidibird' => 'XFEX'], 48 | '优速' => ['juhe' => 'yousu', 'kuaidi100' => 'youshuwuliu', 'kuaidibird' => 'UC'], 49 | '中邮' => ['juhe' => 'zhongyou', 'kuaidi100' => 'zhongyouwuliu', 'kuaidibird' => 'ZYKD'], 50 | '天地华宇' => ['juhe' => 'tdhy', 'kuaidi100' => 'tiandihuayu', 'kuaidibird' => 'HOAU'], 51 | '安信达' => ['juhe' => 'axd', 'kuaidi100' => 'anxindakuaixi', 'kuaidibird' => ''], 52 | '快捷' => ['juhe' => 'kuaijie', 'kuaidi100' => 'kuaijiesudi', 'kuaidibird' => 'DJKJWL'], 53 | 'aae' => ['juhe' => 'aae', 'kuaidi100' => 'aae', 'kuaidibird' => 'AAE'], 54 | 'dhl国内件' => ['juhe' => 'dhl', 'kuaidi100' => 'dhl', 'kuaidibird' => 'FEDEX'], 55 | 'dhl国际件' => ['juhe' => 'dhl', 'kuaidi100' => 'dhlen', 'kuaidibird' => 'FEDEX_GJ'], 56 | 'dpex国际' => ['juhe' => 'dpex', 'kuaidi100' => 'dpex', 'kuaidibird' => 'DPEX'], 57 | 'd速' => ['juhe' => 'ds', 'kuaidi100' => 'dsukuaidi', 'kuaidibird' => 'DSWL'], 58 | 'fedex国内' => ['juhe' => 'fedexcn', 'kuaidi100' => 'fedexcn', 'kuaidibird' => 'FEDEX'], 59 | 'fedex国际' => ['juhe' => 'fedexcn', 'kuaidi100' => 'fedex', 'kuaidibird' => 'FEDEX_GJ'], 60 | 'ocs' => ['juhe' => 'ocs', 'kuaidi100' => 'ocs', 'kuaidibird' => ''], 61 | 'tnt' => ['juhe' => 'tnt', 'kuaidi100' => 'tnt', 'kuaidibird' => 'TNT'], 62 | '中国东方' => ['juhe' => 'coe', 'kuaidi100' => 'coe', 'kuaidibird' => ''], 63 | '传喜' => ['juhe' => 'cxwl', 'kuaidi100' => 'chuanxiwuliu', 'kuaidibird' => 'CXHY'], 64 | '城市100' => ['juhe' => 'cs', 'kuaidi100' => 'city100', 'kuaidibird' => 'CITY100'], 65 | '城市之星' => ['juhe' => 'cszx', 'kuaidi100' => '', 'kuaidibird' => ''], 66 | '安捷' => ['juhe' => 'aj', 'kuaidi100' => 'anjie88', 'kuaidibird' => 'AJ'], 67 | '百福东方' => ['juhe' => 'bfdf', 'kuaidi100' => 'baifudongfang', 'kuaidibird' => 'BFDF'], 68 | '程光' => ['juhe' => 'chengguang', 'kuaidi100' => 'chengguangkuaidi', 'kuaidibird' => 'CG'], 69 | '递四方' => ['juhe' => 'dsf', 'kuaidi100' => 'disifang', 'kuaidibird' => 'D4PX'], 70 | '长通' => ['juhe' => 'ctwl', 'kuaidi100' => '', 'kuaidibird' => ''], 71 | '飞豹' => ['juhe' => 'feibao', 'kuaidi100' => 'feibaokuaidi', 'kuaidibird' => ''], 72 | '安能' => ['juhe' => 'ane66', 'kuaidi100' => 'annengwuliu', 'kuaidibird' => 'ANE'], 73 | '远成' => ['juhe' => 'youzheng', 'kuaidi100' => 'youzhengbk', 'kuaidibird' => 'YZPY'], 74 | '百世' => ['juhe' => 'bsky', 'kuaidi100' => 'huitongkuaidi', 'kuaidibird' => 'HTKY'], 75 | '苏宁' => ['juhe' => 'suning', 'kuaidi100' => 'suning', 'kuaidibird' => 'SNWL'], 76 | '九曳' => ['juhe' => 'jiuye', 'kuaidi100' => 'jiuyescm', 'kuaidibird' => 'JIUYE'], 77 | '亚马逊' => ['juhe' => '', 'kuaidi100' => '', 'kuaidibird' => 'AMAZON'], 78 | '环球速运' => ['juhe' => '', 'kuaidi100' => '', 'kuaidibird' => 'HQSY'], 79 | '联昊通' => ['juhe' => '', 'kuaidi100' => 'lianhaowuliu', 'kuaidibird' => 'LHT'], 80 | ]; 81 | 82 | /** 83 | * 获取物流公司编码 84 | * 85 | * @throws \Wythe\Logistics\Exceptions\HttpException 86 | */ 87 | public function getCode(string $channel, string $code, string $companyName = ''): string 88 | { 89 | $url = 'http://m.kuaidi100.com/autonumber/autoComNum'; 90 | $params = ['resultv2' => 1, 'text' => $code]; 91 | $companyCode = ''; 92 | $channelName = strtolower($channel); 93 | $companyCodeInfo = $this->get($url, $params); 94 | $companyCodeArr = \json_decode($companyCodeInfo, true); 95 | if (isset($companyCodeArr['auto'])) { 96 | $kuaidi100CompanyCodeArr = \array_column($companyCodeArr['auto'], 'comCode'); 97 | $kuaidi100CompanyCode = $kuaidi100CompanyCodeArr[0]; 98 | } 99 | foreach ($this->companyList as $name => $item) { 100 | if (isset($kuaidi100CompanyCode) && $item['kuaidi100'] === $kuaidi100CompanyCode) { 101 | return $item[$channelName]; 102 | } else { 103 | if ($companyName) { 104 | if (false !== \mb_stripos($companyName, $name)) { 105 | return $this->companyList[$name][$channelName]; 106 | } 107 | } 108 | } 109 | } 110 | 111 | return $companyCode; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |
简单便捷查询运单快递信息
4 | 5 | 6 | [](https://travis-ci.org/uuk020/logistics) 7 |  8 | [](https://packagist.org/packages/wythe/logistics) 9 | [](https://packagist.org/packages/wythe/logistics) 10 | [](https://packagist.org/packages/wythe/logistics) 11 | [](https://packagist.org/packages/wythe/logistics) 12 | 13 | 14 | ### 支持查询接口平台 15 | 16 | | 平台 | 次数 | 是否需要快递公司编码 | 免费版支持的快递公司 | 17 | | :-----: | :----: | :----: | :----: | 18 | | [快递100](https://www.kuaidi100.com/openapi/applyapi.shtml) | 100次(首次申请) | Y | | 19 | | [快递鸟](http://www.kdniao.com/api-all) | 500单/天(免费) | Y | 支持申通、圆通、百世、天天,其他快递公司可自行尝试,不支持顺丰 | 20 | | [聚合数据](https://www.juhe.cn/docs/api/id/43) | 100次(首次申请) | Y | | 21 | | [极速数据](https://www.jisuapi.com/api/express) | 1000次(免费) | N | | 22 | | [数据智汇](http://www.shujuzhihui.cn/apiDetails?id=1867) | 100次(免费) | N | | 23 | 24 | ### 配置须知 25 | * 只有快递鸟申请后会有两个,一个是用户ID, 填入到app_secret,另外一个则是api_key, 填入app_key, 其他则把申请的key填入到app_key 26 | 27 | ### 环境需求 28 | * PHP >= 7.0 29 | 30 | ### 安装 31 | 32 | ```shell 33 | $ composer require wythe/logistics -vvv 34 | ``` 35 | 36 | ### 使用 37 | ```php 38 | use Wythe\Logistics\Logistics; 39 | $config = [ 40 | 'juhe' => ['app_key' => 'app_key', 'vip' => false], 41 | 'shujuzhihui' => ['app_key' => 'app_key', 'vip' => false], 42 | 'jisu' => ['app_key' => 'app_key', 'vip' => false], 43 | 'kuaidibird' => ['app_key' => 'app_key', 'app_secret' => 'app_secret', 'vip' => 44 | false], 45 | 'kuaidi100' => ['app_key' => ''] 46 | ]; 47 | $logistics = new Logistics($config); 48 | ``` 49 | 50 | ### 参数说明 51 | ``` 52 | array query(string $code, $channels = ['kuaidi100'], string $company = '') 53 | array queryByProxy(array $proxy, string $code, $channels = ['kuaidi100'], string $company = '') 54 | ``` 55 | 56 | * query 与 queryByProxy 返回数组结构是一样, 只是多了一个参数代理IP 57 | * $proxy - 代理地址 结构: ['proxy' => '代理IP:代理端口'] 58 | * $code - 运单号 59 | * $channel - 渠道名称, 可选参数,默认快递鸟. 60 | * $company - 快递公司 具体看 SupportLogistics 文件 61 | 62 | ### 快递 100 接口获取物流信息 所有接口返回格式是统一 63 | ```php 64 | use Wythe\Logistics\Logistics; 65 | $config = [ 66 | 'juhe' => ['app_key' => 'app_key', 'vip' => false], 67 | 'shujuzhihui' => ['app_key' => 'app_key', 'vip' => false], 68 | 'jisu' => ['app_key' => 'app_key', 'vip' => false], 69 | 'kuaidibird' => ['app_key' => 'app_key', 'app_secret' => 'app_secret', 'vip' => 70 | false], 71 | 'kuaidi100' => ['app_key' => ''] 72 | ]; 73 | $logistics = new Logistics($config); 74 | $logistics->query('12313131231', ''); // 第二参数选填,可以为字符串或数组, 默认快递鸟 75 | $logistics->query('12313131231', 'kuaidi100'); 76 | $logistics->query('12313131231', ['kuaidi100']); 77 | ``` 78 | 示例: 79 | 80 | ```php 81 | [ 82 | 'kuaidi100' => [ 83 | 'channel' => 'kuaidi100', 84 | 'status' => 'success', 85 | 'result' => [ 86 | [ 87 | 'status' => 200, 88 | 'message' => 'OK', 89 | 'error_code' => 0, 90 | 'data' => [ 91 | ['time' => '2019-01-09 12:11', 'description' => '仓库-已签收'], 92 | ['time' => '2019-01-07 12:11', 'description' => '广东XX服务点'], 93 | ['time' => '2019-01-06 12:11', 'description' => '广东XX转运中心'] 94 | ], 95 | 'logistics_company' => '申通快递', 96 | 'logistics_bill_no' => '12312211' 97 | ], 98 | [ 99 | 'status' => 201, 100 | 'message' => '快递公司参数异常:单号不存在或者已经过期', 101 | 'error_code' => 0, 102 | 'data' => '', 103 | 'logistics_company' => '', 104 | 'logistics_bill_no' => '' 105 | ] 106 | ] 107 | ] 108 | ] 109 | ``` 110 | 111 | ### 多接口获取物流信息 112 | ```php 113 | use Wythe\Logistics\Logistics; 114 | $config = [ 115 | 'juhe' => ['app_key' => 'app_key', 'vip' => false], 116 | 'shujuzhihui' => ['app_key' => 'app_key', 'vip' => false], 117 | 'jisu' => ['app_key' => 'app_key', 'vip' => false], 118 | 'kuaidibird' => ['app_key' => 'app_key', 'app_secret' => 'app_secret', 'vip' => 119 | false], 120 | 'kuaidi100' => ['app_key' => ''] 121 | ]; 122 | $logistics = new Logistics($config); 123 | $logistics->query('12313131231'); 124 | $logistics->query('12313131231', ['kuaidi100', 'ickd']); 125 | ``` 126 | 示例: 127 | 128 | ```php 129 | [ 130 | 'kuaidi100' => [ 131 | 'channel' => 'kuaidi100', 132 | 'status' => 'success', 133 | 'result' => [ 134 | [ 135 | 'status' => 200, 136 | 'message' => 'OK', 137 | 'error_code' => 0, 138 | 'data' => [ 139 | ['time' => '2019-01-09 12:11', 'description' => '仓库-已签收'], 140 | ['time' => '2019-01-07 12:11', 'description' => '广东XX服务点'], 141 | ['time' => '2019-01-06 12:11', 'description' => '广东XX转运中心'] 142 | ], 143 | 'logistics_company' => '申通快递', 144 | 'logistics_bill_no' => '12312211' 145 | ], 146 | [ 147 | 'status' => 201, 148 | 'message' => '快递公司参数异常:单号不存在或者已经过期', 149 | 'error_code' => 0, 150 | 'data' => '', 151 | 'logistics_company' => '', 152 | 'logistics_bill_no' => '' 153 | ] 154 | ] 155 | ], 156 | 'ickd' => [ 157 | 'channel' => 'ickd', 158 | 'status' => 'success', 159 | 'result' => [ 160 | [ 161 | 'status' => 200, 162 | 'message' => 'OK', 163 | 'error_code' => 0, 164 | 'data' => [ 165 | ['time' => '2019-01-09 12:11', 'description' => '仓库-已签收'], 166 | ['time' => '2019-01-07 12:11', 'description' => '广东XX服务点'], 167 | ['time' => '2019-01-06 12:11', 'description' => '广东XX转运中心'] 168 | ], 169 | 'logistics_company' => '申通快递', 170 | 'logistics_bill_no' => '12312211' 171 | ] 172 | ] 173 | ] 174 | ] 175 | ``` 176 | 177 | ## 参考 178 | * [PHP 扩展包实战教程 - 从入门到发布](https://laravel-china.org/courses/creating-package) 179 | * [高德开放平台接口的 PHP 天气信息组件(weather)](https://github.com/overtrue/weather) 180 | * [满足你的多种发送需求的短信发送组件(easy-sms)](https://github.com/overtrue/easy-sms) 181 | 182 | ## 最后 183 | 欢迎提出 issue 和 pull request 184 | 185 | ## License 186 | MIT 187 | -------------------------------------------------------------------------------- /src/Traits/HttpRequest.php: -------------------------------------------------------------------------------- 1 | 14 | * 15 | * This source file is subject to the MIT license that is bundled 16 | * with this source code in the file LICENSE. 17 | */ 18 | 19 | namespace Wythe\Logistics\Traits; 20 | 21 | use Wythe\Logistics\Exceptions\HttpException; 22 | 23 | trait HttpRequest 24 | { 25 | /** 26 | * 设置cURL参数. 27 | * 28 | * @param resource $handle 29 | */ 30 | private function setCurlCommonOption($handle, array $option = []) 31 | { 32 | \curl_setopt($handle, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1); 33 | \curl_setopt($handle, CURLOPT_USERAGENT, $this->setUseragent()); 34 | \curl_setopt($handle, CURLOPT_CONNECTTIMEOUT, 30); 35 | \curl_setopt($handle, CURLOPT_TIMEOUT, 30); 36 | \curl_setopt($handle, CURLOPT_RETURNTRANSFER, true); 37 | \curl_setopt($handle, CURLOPT_SSL_VERIFYPEER, false); 38 | \curl_setopt($handle, CURLOPT_SSL_VERIFYHOST, false); 39 | if (!empty($option['header']) && is_array($option['header'])) { 40 | \curl_setopt($handle, CURLOPT_HTTPHEADER, $option['header']); 41 | } 42 | if (!empty($option['proxy']) && is_array($option['proxy'])) { 43 | \curl_setopt($handle, CURLOPT_PROXY, $option['proxy']); 44 | } 45 | } 46 | 47 | /** 48 | * 设置请求方式. 49 | */ 50 | private function setCurlUrlMethod($handle, $url, $params) 51 | { 52 | if (!empty($params)) { 53 | if (is_array($params)) { 54 | $params = http_build_query($params); 55 | } 56 | \curl_setopt($handle, CURLOPT_URL, $url.'?'.$params); 57 | } else { 58 | \curl_setopt($handle, CURLOPT_URL, $url); 59 | } 60 | } 61 | 62 | /** 63 | * GET请求 64 | * 65 | * @param string $url 66 | * @param string|array $params 67 | * 68 | * @return string 69 | * 70 | * @throws \Wythe\Logistics\Exceptions\HttpException 71 | */ 72 | protected function get($url, $params, array $option = []) 73 | { 74 | $handle = \curl_init(); 75 | $this->setCurlCommonOption($handle, $option); 76 | $this->setCurlUrlMethod($handle, $url, $params); 77 | $response = \curl_exec($handle); 78 | if (false === $response) { 79 | throw new HttpException('请求接口发生错误'); 80 | } 81 | \curl_close($handle); 82 | 83 | return $response; 84 | } 85 | 86 | /** 87 | * POST请求 88 | * 89 | * @param string $url 90 | * @param string|array $params 91 | * 92 | * @return bool|string 93 | * 94 | * @throws \Wythe\Logistics\Exceptions\HttpException 95 | */ 96 | protected function post($url, $params, array $option = []) 97 | { 98 | $handle = \curl_init(); 99 | $this->setCurlCommonOption($handle, $option); 100 | $this->setCurlUrlMethod($handle, $url, $params, 1); 101 | $response = \curl_exec($handle); 102 | if (false === $response) { 103 | throw new HttpException('请求接口发生错误'); 104 | } 105 | \curl_close($handle); 106 | 107 | return $response; 108 | } 109 | 110 | /** 111 | * 设置useragent. 112 | */ 113 | private function setUseragent(): string 114 | { 115 | $collectionOfUseragent = [ 116 | 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36', 117 | 'Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)', 118 | 'Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)', 119 | 'Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)', 120 | 'Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)', 121 | 'Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)', 122 | 'Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)', 123 | 'Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)', 124 | 'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)', 125 | 'Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6', 126 | 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1', 127 | 'Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0', 128 | 'Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5', 129 | 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6', 130 | 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11', 131 | 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20', 132 | ]; 133 | $index = mt_rand(0, count($collectionOfUseragent) - 1); 134 | 135 | return $collectionOfUseragent[$index]; 136 | } 137 | 138 | /** 139 | * GET 多线程请求 140 | */ 141 | protected function getByQueue(array $urls = [], array $params = [], array $option = []): array 142 | { 143 | $mh = \curl_multi_init(); 144 | $responses = []; 145 | foreach ($urls as $key => $url) { 146 | $handles[$key] = \curl_init(); 147 | if (!empty($option)) { 148 | $this->setCurlCommonOption($handles[$key], $option); 149 | } else { 150 | $this->setCurlCommonOption($handles[$key]); 151 | } 152 | if (!empty($params)) { 153 | $this->setCurlUrlMethod($handles[$key], $url, $params[$key]); 154 | } else { 155 | $this->setCurlUrlMethod($handles[$key], $url, ''); 156 | } 157 | \curl_multi_add_handle($mh, $handles[$key]); 158 | } 159 | $active = null; 160 | do { 161 | while (\CURLM_CALL_MULTI_PERFORM == ($mrc = \curl_multi_exec($mh, $active))) { 162 | } 163 | if (\CURLM_OK != $mrc) { 164 | break; 165 | } 166 | while ($done = \curl_multi_info_read($mh)) { 167 | $error = \curl_error($done['handle']); 168 | $result = \curl_multi_getcontent($done['handle']); 169 | $responses[] = compact('error', 'result'); 170 | \curl_multi_remove_handle($mh, $done['handle']); 171 | \curl_close($done['handle']); 172 | } 173 | if ($active > 0) { 174 | \curl_multi_select($mh); 175 | } 176 | } while ($active); 177 | \curl_multi_close($mh); 178 | 179 | return $responses; 180 | } 181 | 182 | /** 183 | * POST 多线程请求 184 | */ 185 | protected function postByQueue(array $urls = [], array $params = [], array $option = []): array 186 | { 187 | $mh = \curl_multi_init(); 188 | $responses = []; 189 | foreach ($urls as $key => $url) { 190 | $handles[$key] = \curl_init(); 191 | if (!empty($option)) { 192 | $this->setCurlCommonOption($handles[$key], $option); 193 | } else { 194 | $this->setCurlCommonOption($handles[$key]); 195 | } 196 | if (!empty($params)) { 197 | $this->setCurlUrlMethod($handles[$key], $url, $params[$key], 1); 198 | } else { 199 | $this->setCurlUrlMethod($handles[$key], $url, '', 1); 200 | } 201 | \curl_multi_add_handle($mh, $handles[$key]); 202 | } 203 | $active = null; 204 | do { 205 | while (\CURLM_CALL_MULTI_PERFORM == ($mrc = \curl_multi_exec($mh, $active))) { 206 | } 207 | if (\CURLM_OK != $mrc) { 208 | break; 209 | } 210 | while ($done = \curl_multi_info_read($mh)) { 211 | $error = \curl_error($done['handle']); 212 | $result = \curl_multi_getcontent($done['handle']); 213 | $responses[] = compact('error', 'result'); 214 | \curl_multi_remove_handle($mh, $done['handle']); 215 | \curl_close($done['handle']); 216 | } 217 | if ($active > 0) { 218 | \curl_multi_select($mh); 219 | } 220 | } while ($active); 221 | \curl_multi_close($mh); 222 | 223 | return $responses; 224 | } 225 | } 226 | --------------------------------------------------------------------------------