优化插件

This commit is contained in:
taoser 2023-02-10 14:09:38 +08:00
parent a54e206e00
commit 108083fca2
29 changed files with 2206 additions and 221 deletions

View File

@ -577,6 +577,4 @@ abstract class BaseController
return $content;
}
}

View File

@ -3,53 +3,52 @@ namespace app\admin\controller;
use app\common\controller\AdminController;
use app\common\lib\SqlFile;
use app\common\lib\Zip;
use think\Exception;
use think\facade\View;
use think\facade\Request;
use think\facade\Config;
use app\admin\model\Addons as AddonsModel;
use taoler\com\Files;
use taoler\com\Api;
use app\common\lib\Zip;
use think\response\Json;
use app\admin\model\AuthRule;
use app\admin\model\Addons as AddonsModel;
use think\response\Json;
use Symfony\Component\VarExporter\VarExporter;
use taoler\com\Files;
use app\common\lib\facade\HttpHelper;
class Addons extends AdminController
{
/**
* @return string
*
*/
public function index()
{
if(Request::isAjax()) {
$data = Request::param();
if(!isset($data['type'])) $data['type'] = 'onlineAddons';
if(!isset($data['selector'])) $data['selector'] = 'all';
return $this->getList($data);
}
return View::fetch();
}
/**
* 插件动态列表
* @param $data
* @return Json
*/
public function addonsList()
public function getList($data)
{
$data = Request::param();
$res = [];
//本地插件列表
$addonsList = Files::getDirName('../addons/');
$url = $this->getSystem()['api_url'].'/v1/addons';
$addons = Api::urlGet($url);
$response = HttpHelper::withHost()->get('/v1/addons');
$addons = $response->toJson();
switch($data['type']){
//已安装
case 'installed':
if($addonsList){
$res = ['code'=>0,'msg'=>'','count'=>5];
foreach($addonsList as $v){
$info_file = '../addons/'.$v.'/info.ini';
$info = parse_ini_file($info_file);
$info['show'] = $info['status'] ? '启用' : '禁用';
$info['install'] = $info['status'] ? '是' : '否';
$res['data'][] = $info;
}
$res['col'] = [
['type' => 'numbers'],
['field' => 'name','title'=> '插件', 'width'=> 120],
@ -62,43 +61,44 @@ class Addons extends AdminController
['field' => 'status','title'=> '状态', 'width'=> 95, 'templet' => '#buttonStatus'],
['title' => '操作', 'width'=> 150, 'align'=>'center', 'toolbar'=> '#addons-installed-tool']
];
// $data数据
foreach($addonsList as $v){
$info_file = '../addons/'.$v.'/info.ini';
$info = parse_ini_file($info_file);
$info['show'] = $info['status'] ? '启用' : '禁用';
$info['install'] = $info['status'] ? '是' : '否';
$res['data'][] = $info;
}
} else {
$res = ['code'=>-1,'msg'=>'没有安装任何插件'];
}
$res = ['code'=>-1,'msg'=>'没有安装任何插件'];
}
break;
//在线全部
case 'onlineAddons':
if( $addons->code !== -1){
// 与本地文件对比
foreach($addons->data as $v){
switch ($data['selector']) {
case 'free':
if($v->price == 0) {
if(in_array($v->name,$addonsList)) {
$info = get_addons_info($v->name);
//已安装
$v->isInstall = 1;
//判断是否有新版本
if($v->version > $info['version']) $v->have_newversion = 1;
$v->price = $v->price ? $v->price : '免费';
}
$res['data'][] = $v;
}
break;
case 'pay':
if($v->price > 0) {
if(in_array($v->name,$addonsList)) {
$info = get_addons_info($v->name);
//已安装
$v->isInstall = 1;
//判断是否有新版本
if($v->version > $info['version']) $v->have_newversion = 1;
$v->price = $v->price ? $v->price : '免费';
}
$res['data'][] = $v;
}
break;
case 'all':
if($response->ok()) {
$res['code'] = 0;
$res['msg'] = '';
$res['count'] = count($addons->data);
$res['col'] = [
['type' => 'numbers'],
['field' => 'title','title'=> '插件', 'width'=> 200],
['field' => 'description','title'=> '简介', 'minWidth'=> 200],
['field' => 'author','title'=> '作者', 'width'=> 100],
['field' => 'price','title'=> '价格(元)','width'=> 85],
['field' => 'downloads','title'=> '下载', 'width'=> 70],
['field' => 'version','title'=> '版本', 'templet' => '<div>{{d.version}} {{# if(d.have_newversion == 1){ }}<span class="layui-badge-dot"></span>{{# } }}</div>','width'=> 75],
['field' => 'status','title'=> '在线', 'width'=> 70],
['title' => '操作', 'width'=> 150, 'align'=>'center', 'toolbar'=> '#addons-tool']
];
// $data数据 与本地文件对比
foreach($addons->data as $v){
switch ($data['selector']) {
case 'free':
if($v->price == 0) {
if(in_array($v->name,$addonsList)) {
$info = get_addons_info($v->name);
//已安装
@ -108,29 +108,41 @@ class Addons extends AdminController
$v->price = $v->price ? $v->price : '免费';
}
$res['data'][] = $v;
break;
}
};
}
break;
case 'pay':
if($v->price > 0) {
if(in_array($v->name,$addonsList)) {
$info = get_addons_info($v->name);
//已安装
$v->isInstall = 1;
//判断是否有新版本
if($v->version > $info['version']) $v->have_newversion = 1;
$v->price = $v->price ? $v->price : '免费';
}
$res['data'][] = $v;
}
break;
case 'all':
if(in_array($v->name,$addonsList)) {
$info = get_addons_info($v->name);
//已安装
$v->isInstall = 1;
//判断是否有新版本
if($v->version > $info['version']) $v->have_newversion = 1;
$v->price = $v->price ? $v->price : '免费';
}
$res['data'][] = $v;
break;
}
};
$res['code'] = 0;
$res['msg'] = '';
$res['col'] = [
['type' => 'numbers'],
['field' => 'title','title'=> '插件', 'width'=> 200],
['field' => 'description','title'=> '简介', 'minWidth'=> 200],
['field' => 'author','title'=> '作者', 'width'=> 100],
['field' => 'price','title'=> '价格(元)','width'=> 85],
['field' => 'downloads','title'=> '下载', 'width'=> 70],
['field' => 'version','title'=> '版本', 'templet' => '<div>{{d.version}} {{# if(d.have_newversion == 1){ }}<span class="layui-badge-dot"></span>{{# } }}</div>','width'=> 75],
['field' => 'status','title'=> '在线', 'width'=> 70],
['title' => '操作', 'width'=> 150, 'align'=>'center', 'toolbar'=> '#addons-tool']
];
} else {
$res = ['code'=>-1,'msg'=>'未获取到服务器信息'];
}
} else {
$res = ['code' => -1, 'msg' => '未获取到服务器信息'];
}
break;
}
return json($res);
return json($res);
}
/**
@ -192,20 +204,20 @@ class Addons extends AdminController
}
/**
* 安装插件
* 安装&升级,
* @param array $data
* @param bool $type true执行sql,false升级不执行sql
* @return Json
* @throws \Exception
*/
public function install()
public function install(array $data = [], bool $type = true)
{
$data = Request::param();
$url = $this->getSystem()['api_url'].'/v1/getaddons';
$data = ['name'=>$data['name'], 'version'=>$data['version'], 'uid'=>$data['uid'], 'token'=>$data['token']];
$addons = Api::urlPost($url,$data);
if( $addons->code < 0) {
return json(['code'=>$addons->code,'msg'=>$addons->msg]);
}
//$this->pay($name,$extend);
$data = Request::only(['name','version','uid','token']) ?? $data;
// $data = ['name' => $name, 'version' => $version, 'uid' => $uid, 'token' => $token];
// 接口
$response = HttpHelper::withHost()->post('/v1/getaddons',$data)->toJson();
if($response->code < 0) return json($response);
//版本判断,是否能够安装?
$addInstalledVersion = get_addons_info($data['name']);
if(!empty($addInstalledVersion)){
@ -216,7 +228,7 @@ class Addons extends AdminController
//$tpl_ver_res = version_compare($addInstalledVersion['template_version'], config('taoler.template_version'),'<');
}
$file_url = $addons->addons_src;
$file_url = $response->addons_src;
//判断远程文件是否可用存在
$header = get_headers($file_url, true);
if(!isset($header[0]) && (strpos($header[0], '200') || strpos($header[0], '304'))){
@ -265,24 +277,32 @@ class Addons extends AdminController
if($cpData['code'] == -1) return json(['code'=>-1,'msg'=>$cpData['msg']]);
$class = get_addons_instance($data['name']);
//添加数据库
$sqlInstallFile = root_path().'addons/'.$data['name'].'/install.sql';
if(file_exists($sqlInstallFile)) {
SqlFile::dbExecute($sqlInstallFile);
}
//安装菜单
$menu = get_addons_menu($data['name']);
if(!empty($menu)){
if(isset($menu['is_nav']) && $menu['is_nav']==1){
$pid = 0;
}else{
$pid = AuthRule::where('name','addons')->value('id');
try {
if($type) {
// 执行数据库
$sqlInstallFile = root_path().'addons/'.$data['name'].'/install.sql';
if(file_exists($sqlInstallFile)) {
SqlFile::dbExecute($sqlInstallFile);
}
}
$menu_arr[] = $menu['menu'];
$this->addAddonMenu($menu_arr, (int)$pid,1);
//安装菜单
$menu = get_addons_menu($data['name']);
if(!empty($menu)){
if(isset($menu['is_nav']) && $menu['is_nav'] == 1){
$pid = 0;
}else{
$pid = AuthRule::where('name','addons')->value('id');
}
$menu_arr[] = $menu['menu'];
$this->addAddonMenu($menu_arr, (int)$pid,1);
}
//执行插件安装
$class->install();
set_addons_info($data['name'],['status' => 1,'install' => 1]);
} catch (\Exception $e) {
return json(['code'=>-1,'msg'=> $e->getMessage()]);
}
//执行插件安装
$class->install();
Files::delDirAndFile('../runtime/addons/'.$data['name'] . DS);
@ -295,34 +315,33 @@ class Addons extends AdminController
public function uninstall()
{
$name = input('name');
//执行插件卸载
// 执行插件卸载
$class = get_addons_instance($name);
$class->uninstall();
//卸载菜单
// 卸载菜单
$menu = get_addons_menu($name);
if(!empty($menu)){
$menu_arr[] = $menu['menu'];
$this->delAddonMenu($menu_arr);
}
//卸载插件数据库
$sqlUninstallFile = root_path().'addons/'.$name.'/uninstall.sql';
if(file_exists($sqlUninstallFile)) {
SqlFile::dbExecute($sqlUninstallFile);
}
// 插件addons下目录
$addonsDir = root_path() . 'addons' . DS . $name . DS;
// 插件管理后台目录
$admin_controller = app_path() . 'controller' . DS . $name . DS;
$admin_model = app_path() . 'model' . DS . $name . DS;
$admin_view = app_path() . 'view' . DS . $name . DS;
$admin_validate = app_path() . 'validate' . DS . $name . DS;
// 插件静态资源目录
$addon_public = public_path() . 'addons' . DS . $name . DS;
try {
//卸载插件数据库
$sqlUninstallFile = root_path().'addons/'.$name.'/uninstall.sql';
if(file_exists($sqlUninstallFile)) {
SqlFile::dbExecute($sqlUninstallFile);
}
// 插件addons下目录
$addonsDir = root_path() . 'addons' . DS . $name . DS;
// 插件管理后台目录
$admin_controller = app_path() . 'controller' . DS . $name . DS;
$admin_model = app_path() . 'model' . DS . $name . DS;
$admin_view = app_path() . 'view' . DS . $name . DS;
$admin_validate = app_path() . 'validate' . DS . $name . DS;
// 插件静态资源目录
$addon_public = public_path() . 'addons' . DS . $name . DS;
if(file_exists($addonsDir)) Files::delDir($addonsDir);
if(file_exists($admin_controller)) Files::delDir($admin_controller);
if(file_exists($admin_model)) Files::delDir($admin_model);
@ -337,6 +356,43 @@ class Addons extends AdminController
return json(['code' => 0, 'msg' => '插件卸载成功']);
}
/**
* 升级插件
* @return Json
* @throws \Exception
*/
public function upgrade()
{
$data = Request::only(['name','version','uid','token']);
// 获取配置信息
$config = get_addons_config($data['name']);
// 卸载插件
$class = get_addons_instance($data['name']);
$class->uninstall();
// 卸载菜单
$menu = get_addons_menu($data['name']);
if(!empty($menu)){
$menu_arr[] = $menu['menu'];
$this->delAddonMenu($menu_arr);
}
try {
// 升级安装第二个参数为false
$this->install($data,false);
// 升级sql
$sqlUpdateFile = root_path().'addons/'.$data['name'].'/update.sql';
if(file_exists($sqlUpdateFile)) {
SqlFile::dbExecute($sqlUpdateFile);
}
// 恢复配置
set_addons_config($data['name'],$config);
} catch (\Exception $e) {
return json(['code' => -1, 'msg' => $e->getMessage()]);
}
return json(['code' => 0, 'msg' => '升级成功']);
}
/**
* 启用禁用插件
* @return Json
@ -478,14 +534,8 @@ class Addons extends AdminController
*/
public function userLogin()
{
$data = Request::param();
$url = $this->getSystem()['api_url'].'/v1/user/login';
$user = Api::urlPost($url,$data);
if($user->code == 0) {
return $user;
} else {
return json(['code'=>-1,'msg'=>$user->msg]);
}
$response = HttpHelper::withHost()->post('/v1/user/login', Request::param())->toJson();
return json($response);
}
/**
@ -495,15 +545,17 @@ class Addons extends AdminController
public function pay()
{
$data = Request::only(['id','name','version','uid','price']);
$url = $this->getSystem()['api_url'].'/v1/createOrder';
$order = Api::urlPost($url,$data);
// $url = $this->getSystem()['api_url'].'/v1/createOrder';
// $order = Api::urlPost($url,$data);
if ($order->code == 0) {
$orderData = json_decode(json_encode($order->data),TRUE);
View::assign('orderData',$orderData);
$response = HttpHelper::withHost()->post('/v1/createOrder', $data);
if ($response->ok()) {
// $orderData = json_decode(json_encode($response->toJson()->data),TRUE);
View::assign('orderData',$response->toArray()['data']);
return View::fetch();
} else {
return json(['code'=>-1,'msg'=>$order->msg]);
return json($response->toJson());
}
}
@ -514,18 +566,12 @@ class Addons extends AdminController
public function isPay()
{
$param = Request::only(['name','userinfo']);
//halt($data);
$data = [
'name'=>$param['name'],
'uid'=> $param['userinfo']['uid'],
];
$url = $this->getSystem()['api_url'].'/v1/ispay';
$res = Api::urlPost($url,$data);
if($res->code == 0) {
return json(['code'=>0,'msg'=>'payed']);
} else {
return json(['code'=>-1,'msg'=>'no pay']);
}
$response = HttpHelper::withHost()->post('/v1/ispay', $data)->toJson();
return json($response);
}
}

View File

@ -4,22 +4,24 @@
<div class="layui-fluid">
<div class="layui-card">
<div class="layui-card-body">
<table id="addons-list" lay-filter="addons-list"></table>
<script type="text/html" id="toolbar">
<div class="layui-btn-group" style="padding-bottom: 10px;">
<a href="#denable" type="button" class="layui-btn layui-btn-sm" lay-event="allAddons">全部</a>
<a type="button" class="layui-btn layui-btn-sm" lay-event="freeAddons">免费</a>
<a type="button" class="layui-btn layui-btn-sm" lay-event="payAddons">付费</a>
<a href="#enable" type="button" class="layui-btn layui-btn-normal layui-btn-sm" lay-event="installed">已安装</a>
<a type="button" class="layui-btn layui-btn-sm" lay-event="allAddons" data-url="{:url('addons/index',['type'=>'onlineAddons','selector'=>'all'])}">全部</a>
<a type="button" class="layui-btn layui-btn-sm" lay-event="freeAddons" data-url="{:url('addons/index',['type'=>'onlineAddons','selector'=>'free'])}">免费</a>
<a type="button" class="layui-btn layui-btn-sm" lay-event="payAddons" data-url="{:url('addons/index',['type'=>'onlineAddons','selector'=>'pay'])}">付费</a>
<a type="button" class="layui-btn layui-btn-normal layui-btn-sm" lay-event="installed" data-url="{:url('addons/index',['type'=>'installed','selector'=>''])}">已安装</a>
</div>
<div class="layui-btn-group" style="padding-bottom: 10px;">
<button class="layui-btn layui-btn-danger layui-btn-sm layuiadmin-btn-admin" data-type="add">离线安装</button>
</div>
</script>
<script type="text/html" id="addons-tool">
{{# if(d.have_newversion === 1){ }}
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="install" data-url="{:url('Addons/install')}"><i class="layui-icon layui-icon-edit"></i>升级</a>
<a class="layui-btn layui-btn-danger layui-btn-xs" lay-event="install" data-url="{:url('Addons/upgrade')}" data-userlogin="{:url('Addons/userLogin')}" data-ispay="{:url('Addons/isPay')}"><i class="layui-icon layui-icon-edit"></i>升级</a>
{{# } else { }}
{{# if(d.isInstall ===1) { }}
<a class="layui-btn layui-btn-normal layui-btn-xs" lay-event="config" data-url="{:url('Addons/config')}"><i class="layui-icon layui-icon-set"></i>设置</a>
@ -48,8 +50,7 @@
{block name="js"}
<script src="/static/notify.js"></script>
<script>
var addonsList = "{:url('Addons/addonsList')}";
var addonList = "{:url('Addons/index')}";
layui.config({
base: '/static/admin/' //静态资源所在路径
}).extend({

View File

@ -25,19 +25,17 @@
</div>
</div>
<div class="layui-row">
<div class="layui-col-xs6 layui-col-md6">
<div class="layui-col-md6">
<div class="pay-type">
<div style="padding: 5px;"><a ><img src="/static/res/images/alipay.jpg" style="height:80px;"></a></div>
<div style="padding: 5px; text-align: center;"><a><img src="/static/res/images/alipay.jpg" style="height:80px;"></a></div>
</div>
<div class="soft-info">
<div>不支持退款</div>
<br />
<div>软件协议:本软件为原作者拥有版权权限,购买软件可以商用,禁止第三方出售行为。</div>
</div>
</div>
<div class="layui-col-xs6 layui-col-md6">
<div class="layui-col-md6">
<div class="qrcode" data-text="{$orderData.qr_code_img}">
<img src="{$orderData.qr_code_img}">
</div>

View File

@ -0,0 +1,107 @@
<?php
namespace app\common\lib;
use yzh52521\EasyHttp\Http;
use yzh52521\EasyHttp\Request;
use yzh52521\EasyHttp\Response;
use yzh52521\EasyHttp\RequestException;
class HttpHelper
{
/**
* @var Http;
*/
protected $http;
protected $response;
public function __construct(){
// $this->http = new Http();
if(!$this->http) {
$this->http = new Request();
}
}
/**
* 携带指定接口
* @param string $url
* @return $this
*/
public function withHost(string $url = 'http://api.aieok.com'): HttpHelper
{
$this->http = $this->http->withHost($url);
return $this;
}
/**
* 添加请求头
* @param array $data
* @return $this
*/
public function withHeaders(array $data = []): HttpHelper
{
$this->http = $this->http->withHeaders($data);
return $this;
}
/**
* get请求
* @param string $url
* @param array $data
* @return $this
*/
public function get(string $url, array $data = []): HttpHelper
{
$this->response = $this->http->get($url, $data);
return $this;
}
/**
* POST请求
* @param string $url
* @param array $data
* @return $this
*/
public function post(string $url, array $data = [])
{
$this->response = $this->http->post($url, $data);
return $this;
}
/**
* 返回JSON数据
* @return mixed
*/
public function toJson()
{
if($this->ok()) {
return $this->response->json();
} else {
// return json(['code' => -1, 'msg' => 'server failed']);
return json_decode('{"code": -1, "msg": "server failed"}');
}
}
/**
* 返回ARRAY数据
* @return array
*/
public function toArray()
{
if($this->ok()) {
return $this->response->array();
} else {
return ['code' => -1, 'msg' => 'server failed'];
}
}
/**
* @return bool
*/
public function ok() : bool
{
return $this->response->status() === 200;
}
}

View File

@ -0,0 +1,29 @@
<?php
namespace app\common\lib\facade;
use think\Facade;
use think\response\Json;
/**
* @method static \app\common\lib\HttpHelper withHost(string $url = 'http://api.aieok.com') 携带指定接口
* @method static \app\common\lib\HttpHelper withHeaders(array $data = []) 携带请求头
* @method static \app\common\lib\HttpHelper get(string $url, array $data = []) GET请求
* @method static \app\common\lib\HttpHelper post(string $url, array $data = []) POST请求
* @method static array toArray() 返回ARRAY数据
* @method static bool ok() 服务器200
*/
class HttpHelper extends Facade
{
/**
* 获取当前Facade对应类名或者已经绑定的容器对象标识
* @access protected
* @return string
*/
protected static function getFacadeClass()
{
return 'app\common\lib\HttpHelper';
}
}

View File

@ -12,11 +12,9 @@ declare (strict_types = 1);
namespace app\listener;
use think\facade\Db;
use think\facade\Log;
use app\common\model\User;
use think\facade\Lang;
use taoler\com\Api;
use app\common\lib\facade\HttpHelper;
class UserLogin
{
@ -35,16 +33,17 @@ class UserLogin
if($type == 'log'){
//$name = $user->user['name'];
$ip = request()->ip();
$url = 'http://ip-api.com/json/' . $ip . '?lang=zh-CN&fields=57361';
$ipJson = Api::urlGetRespond($url);
$res = $ipJson->getData();
$data = json_decode($res['data']);
$city ='earth';
if($res['code'] == 0 && !$data->status){
$city = $data->city;
}
$city = 'earth';
try{
$ipInfo = HttpHelper::get($url)->toJson();
if($ipInfo->status == 'success')
{
$city = $ipInfo->city;
}
} catch (\Exception $e) {
// echo $e->getMessage();
}
//国内查询,接口已失效
// $url = 'http://freeapi.ipip.net/' . $ip;
@ -75,7 +74,7 @@ class UserLogin
}
if($type == 'logError'){
$res = $u->allowField(['login_error_num','login_error_time'])->save(['login_error_num'=>$u->login_error_num+1,'login_error_time'=>time()]);
$res = $u->allowField(['login_error_num','login_error_time'])->save(['login_error_num'=>$u->login_error_num + 1,'login_error_time'=>time()]);
}
}

View File

@ -38,7 +38,8 @@
"php-di/php-di": "^6.4",
"workerman/phpsocket.io": "^1.1",
"jaeger/querylist": "^4.2",
"symfony/var-exporter": "^5.4"
"symfony/var-exporter": "^5.4",
"yzh52521/easyhttp": "^1.0"
},
"require-dev": {
"symfony/var-dumper": "^4.2",

54
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "775d0e3963da1506fb878faca1e62b31",
"content-hash": "20e9c0c21d94d1cbf1c29c69868ebe38",
"packages": [
{
"name": "bacon/bacon-qr-code",
@ -3271,6 +3271,58 @@
"source": "https://github.com/yansongda/supports"
},
"time": "2022-03-28T10:25:04+00:00"
},
{
"name": "yzh52521/easyhttp",
"version": "v1.0.3",
"source": {
"type": "git",
"url": "https://github.com/yzh52521/easyhttp.git",
"reference": "aa7f805cfed6ce613f10045aff564b05e62e944c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yzh52521/easyhttp/zipball/aa7f805cfed6ce613f10045aff564b05e62e944c",
"reference": "aa7f805cfed6ce613f10045aff564b05e62e944c",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6.0|^7.0",
"php": "^7.2.5|^8.0",
"psr/log": "^1.0|^2.0|^3.0"
},
"type": "library",
"autoload": {
"psr-4": {
"yzh52521\\EasyHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "yzh52521",
"email": "396751927@qq.com"
}
],
"description": "EasyHttp 是一个轻量级、语义化、对IDE友好的HTTP客户端支持常见的HTTP请求、异步请求和并发请求让你可以快速地使用 HTTP 请求与其他 Web 应用进行通信。",
"homepage": "https://github.com/yzh52521/easyhttp",
"keywords": [
"EasyHttp",
"curl",
"easy-http",
"http",
"php",
"php-http",
"phphttp"
],
"support": {
"issues": "https://github.com/yzh52521/easyhttp/issues",
"source": "https://github.com/yzh52521/easyhttp/tree/v1.0.3"
},
"time": "2022-10-13T06:56:42+00:00"
}
],
"packages-dev": [

View File

@ -16,7 +16,7 @@ return [
// 应用名,此项不可更改
'appname' => 'TaoLer',
// 版本配置
'version' => '2.0.5',
'version' => '2.0.6',
// 加盐
'salt' => 'taoler',
// 数据库备份目录

View File

@ -7,30 +7,70 @@ layui.define(["table", "form", "upload","notify","hxNav"], function (exports) {
upload = layui.upload;
var notify = layui.notify;
function getAddonsList(type,selector ) {
//渲染表格
table.render({
elem: "#addons-list",
toolbar: "#toolbar",
defaultToolbar: [],
url: addonList,
cols: [[
{type: 'checkbox'},
{title: '序号', type: 'numbers'},
{field: 'title', title: '插件', width: 200},
{field: 'description', title: '简介', minWidth: 200},
{field: 'author', title: '作者', width: 100},
{field: 'price', title: '价格(元)', width: 85},
{field: 'downloads', title: '下载', width: 70},
{field: 'version', title: '版本', templet: '<div>{{d.version}} {{# if(d.have_newversion == 1){ }}<span class="layui-badge-dot"></span>{{# } }}</div>', width: 75},
{field: 'status', title: '在线', width: 70},
{title: '操作', width: 165, align: 'center', toolbar: '#addons-tool'}
]],
page: true,
limit: 15,
height: "full-220",
text: "对不起,加载出现异常!",
});
// 重载数据
var addonReload = function (type,selector) {
$.ajax({
type: "post",
url: addonsList,
url: addonList,
data: { type: type, selector: selector },
dataType: "json",
success: function (res) {
//渲染表格
table.render({
elem: "#addons-list",
toolbar: "#toolbar",
defaultToolbar: [],
url: addonsList + "?type=" + type + "&selector=" + selector,
table.reload('addons-list',{
url: addonList,
where: {
type: type, selector: selector
},
cols: [res["col"]],
page: true,
limit: 10,
height: "full-220",
text: "对不起,加载出现异常!",
});
},
});
}
getAddonsList("onlineAddons","all");
//头工具栏事件
table.on("toolbar(addons-list)", function (obj) {
var checkStatus = table.checkStatus(obj.config.id);
var url = $(this).data(url);
switch (obj.event) {
case "installed":
addonReload("installed","all");
break;
case "allAddons":
addonReload("onlineAddons","all");
break;
case "freeAddons":
addonReload("onlineAddons","free");
break;
case "payAddons":
addonReload("onlineAddons","pay");
break;
}
});
var api = {
userinfo: {
@ -48,25 +88,6 @@ layui.define(["table", "form", "upload","notify","hxNav"], function (exports) {
}
}
//头工具栏事件
table.on("toolbar(addons-list)", function (obj) {
var checkStatus = table.checkStatus(obj.config.id);
switch (obj.event) {
case "installed":
getAddonsList("installed",'');
break;
case "allAddons":
getAddonsList("onlineAddons","all");
break;
case "freeAddons":
getAddonsList("onlineAddons","free");
break;
case "payAddons":
getAddonsList("onlineAddons","pay");
break;
}
});
//监听工具条
table.on("tool(addons-list)", function (obj) {
var data = obj.data;
@ -85,12 +106,12 @@ layui.define(["table", "form", "upload","notify","hxNav"], function (exports) {
layer.close(index);
layer.open({
type: 2,
area: ['700px', '650px'],
area: ['80%', '90%'],
fixed: false, //不固定
maxmin: true,
content: 'pay.html'+ "?id=" + data.id+ "&name=" + data.name + "&version=" + data.version + "&uid=" + userinfo.uid + "&price=" + data.price,
success: function (layero, index){
// 订单沦陷
// 订单轮询
var intervalPay = setInterval(function() {
$.post(userIsPayUrl,{name:data.name, userinfo:userinfo},function (res){
if(res.code === 0) {
@ -113,6 +134,8 @@ layui.define(["table", "form", "upload","notify","hxNav"], function (exports) {
layer.close(index);
notify.error(res.msg, "topRight");
}
// 重载
table.reloadData("addons-list",{},'deep');
});
});
} else {
@ -149,7 +172,6 @@ layui.define(["table", "form", "upload","notify","hxNav"], function (exports) {
notify.success("登录成功", function (){
location.reload();
});
} else {
notify.alert(res.msg);
}
@ -163,18 +185,14 @@ layui.define(["table", "form", "upload","notify","hxNav"], function (exports) {
},
success: function (layero, index) {
$(".layui-layer-btn1", layero).prop("href", "https://www.aieok.com/article/reg.html").prop("target", "_blank");
},
end: function () {
$("#login").hide();
},
}
});
});
}
}
//安装插件
if (event === "install") {
if (event === "install" || event === "upgrade") {
var userLoginUrl = $(this).data('userlogin');
var userIsPayUrl = $(this).data('ispay');
install(data,url,userLoginUrl,userIsPayUrl);

View File

@ -8,13 +8,13 @@ $baseDir = dirname($vendorDir);
return array(
'9b552a3cc426e3287cc811caefa3cf53' => $vendorDir . '/topthink/think-helper/src/helper.php',
'35fab96057f1bf5e7aba31a8a6d5fdde' => $vendorDir . '/topthink/think-orm/stubs/load_stubs.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => $vendorDir . '/ralouphie/getallheaders/src/getallheaders.php',
'a4a119a56e50fbb293281d9a48007e0e' => $vendorDir . '/symfony/polyfill-php80/bootstrap.php',
'c964ee0ededf28c96ebd9db5099ef910' => $vendorDir . '/guzzlehttp/promises/src/functions_include.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => $vendorDir . '/guzzlehttp/psr7/src/functions_include.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => $vendorDir . '/symfony/polyfill-mbstring/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => $vendorDir . '/symfony/polyfill-php72/bootstrap.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => $vendorDir . '/guzzlehttp/guzzle/src/functions_include.php',
'667aeda72477189d0494fecd327c3641' => $vendorDir . '/symfony/var-dumper/Resources/functions/dump.php',
'fe62ba7e10580d903cc46d808b5961a4' => $vendorDir . '/tightenco/collect/src/Collect/Support/helpers.php',
'caf31cc6ec7cf2241cb6f12c226c3846' => $vendorDir . '/tightenco/collect/src/Collect/Support/alias.php',

View File

@ -6,6 +6,7 @@ $vendorDir = dirname(__DIR__);
$baseDir = dirname($vendorDir);
return array(
'yzh52521\\EasyHttp\\' => array($vendorDir . '/yzh52521/easyhttp/src'),
'wamkj\\thinkphp\\' => array($vendorDir . '/wamkj/thinkphp6.0-databackup/src'),
'think\\view\\driver\\' => array($vendorDir . '/topthink/think-view/src'),
'think\\trace\\' => array($vendorDir . '/topthink/think-trace/src'),
@ -15,7 +16,7 @@ return array(
'think\\app\\' => array($vendorDir . '/topthink/think-multi-app/src'),
'think\\' => array($vendorDir . '/topthink/framework/src/think', $vendorDir . '/topthink/think-helper/src', $vendorDir . '/topthink/think-orm/src', $vendorDir . '/topthink/think-template/src'),
'taoser\\think\\' => array($vendorDir . '/taoser/think-auth/src'),
'taoser\\' => array($vendorDir . '/taoser/think-setarr/src', $vendorDir . '/taoser/think-addons/src'),
'taoser\\' => array($vendorDir . '/taoser/think-addons/src', $vendorDir . '/taoser/think-setarr/src'),
'phpspirit\\databackup\\' => array($vendorDir . '/lotofbadcode/phpspirit_databackup/src'),
'liliuwei\\social\\' => array($vendorDir . '/liliuwei/thinkphp-social/src'),
'app\\' => array($baseDir . '/app'),

View File

@ -9,13 +9,13 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
public static $files = array (
'9b552a3cc426e3287cc811caefa3cf53' => __DIR__ . '/..' . '/topthink/think-helper/src/helper.php',
'35fab96057f1bf5e7aba31a8a6d5fdde' => __DIR__ . '/..' . '/topthink/think-orm/stubs/load_stubs.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'7b11c4dc42b3b3023073cb14e519683c' => __DIR__ . '/..' . '/ralouphie/getallheaders/src/getallheaders.php',
'a4a119a56e50fbb293281d9a48007e0e' => __DIR__ . '/..' . '/symfony/polyfill-php80/bootstrap.php',
'c964ee0ededf28c96ebd9db5099ef910' => __DIR__ . '/..' . '/guzzlehttp/promises/src/functions_include.php',
'a0edc8309cc5e1d60e3047b5df6b7052' => __DIR__ . '/..' . '/guzzlehttp/psr7/src/functions_include.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
'0e6d7bf4a5811bfa5cf40c5ccd6fae6a' => __DIR__ . '/..' . '/symfony/polyfill-mbstring/bootstrap.php',
'25072dd6e2470089de65ae7bf11d3109' => __DIR__ . '/..' . '/symfony/polyfill-php72/bootstrap.php',
'37a3dc5111fe8f707ab4c132ef1dbc62' => __DIR__ . '/..' . '/guzzlehttp/guzzle/src/functions_include.php',
'667aeda72477189d0494fecd327c3641' => __DIR__ . '/..' . '/symfony/var-dumper/Resources/functions/dump.php',
'fe62ba7e10580d903cc46d808b5961a4' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/helpers.php',
'caf31cc6ec7cf2241cb6f12c226c3846' => __DIR__ . '/..' . '/tightenco/collect/src/Collect/Support/alias.php',
@ -28,6 +28,10 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
);
public static $prefixLengthsPsr4 = array (
'y' =>
array (
'yzh52521\\EasyHttp\\' => 18,
),
'w' =>
array (
'wamkj\\thinkphp\\' => 15,
@ -143,6 +147,10 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
);
public static $prefixDirsPsr4 = array (
'yzh52521\\EasyHttp\\' =>
array (
0 => __DIR__ . '/..' . '/yzh52521/easyhttp/src',
),
'wamkj\\thinkphp\\' =>
array (
0 => __DIR__ . '/..' . '/wamkj/thinkphp6.0-databackup/src',
@ -184,8 +192,8 @@ class ComposerStaticInit1b32198725235c8d6500c87262ef30c2
),
'taoser\\' =>
array (
0 => __DIR__ . '/..' . '/taoser/think-setarr/src',
1 => __DIR__ . '/..' . '/taoser/think-addons/src',
0 => __DIR__ . '/..' . '/taoser/think-addons/src',
1 => __DIR__ . '/..' . '/taoser/think-setarr/src',
),
'phpspirit\\databackup\\' =>
array (

View File

@ -3560,6 +3560,61 @@
"source": "https://github.com/yansongda/supports"
},
"install-path": "../yansongda/supports"
},
{
"name": "yzh52521/easyhttp",
"version": "v1.0.3",
"version_normalized": "1.0.3.0",
"source": {
"type": "git",
"url": "https://github.com/yzh52521/easyhttp.git",
"reference": "aa7f805cfed6ce613f10045aff564b05e62e944c"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/yzh52521/easyhttp/zipball/aa7f805cfed6ce613f10045aff564b05e62e944c",
"reference": "aa7f805cfed6ce613f10045aff564b05e62e944c",
"shasum": ""
},
"require": {
"guzzlehttp/guzzle": "^6.0|^7.0",
"php": "^7.2.5|^8.0",
"psr/log": "^1.0|^2.0|^3.0"
},
"time": "2022-10-13T06:56:42+00:00",
"type": "library",
"installation-source": "dist",
"autoload": {
"psr-4": {
"yzh52521\\EasyHttp\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "yzh52521",
"email": "396751927@qq.com"
}
],
"description": "EasyHttp 是一个轻量级、语义化、对IDE友好的HTTP客户端支持常见的HTTP请求、异步请求和并发请求让你可以快速地使用 HTTP 请求与其他 Web 应用进行通信。",
"homepage": "https://github.com/yzh52521/easyhttp",
"keywords": [
"EasyHttp",
"curl",
"easy-http",
"http",
"php",
"php-http",
"phphttp"
],
"support": {
"issues": "https://github.com/yzh52521/easyhttp/issues",
"source": "https://github.com/yzh52521/easyhttp/tree/v1.0.3"
},
"install-path": "../yzh52521/easyhttp"
}
],
"dev": true,

View File

@ -3,7 +3,7 @@
'name' => 'taoser/taoler',
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'ad4f215470c0da087a55d6ee63199988a4d216bd',
'reference' => 'db01522b1bc5e463fdc10fc88dcc01a3eee4f23a',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -367,7 +367,7 @@
'taoser/taoler' => array(
'pretty_version' => 'dev-master',
'version' => 'dev-master',
'reference' => 'ad4f215470c0da087a55d6ee63199988a4d216bd',
'reference' => 'db01522b1bc5e463fdc10fc88dcc01a3eee4f23a',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@ -553,5 +553,14 @@
'aliases' => array(),
'dev_requirement' => false,
),
'yzh52521/easyhttp' => array(
'pretty_version' => 'v1.0.3',
'version' => '1.0.3.0',
'reference' => 'aa7f805cfed6ce613f10045aff564b05e62e944c',
'type' => 'library',
'install_path' => __DIR__ . '/../yzh52521/easyhttp',
'aliases' => array(),
'dev_requirement' => false,
),
),
);

View File

@ -409,7 +409,7 @@ class Client implements ClientInterface, \Psr\Http\Client\ClientInterface
if (isset($options['query'])) {
$value = $options['query'];
if (\is_array($value)) {
$value = \http_build_query($value, null, '&', \PHP_QUERY_RFC3986);
$value = \http_build_query($value, '', '&', \PHP_QUERY_RFC3986);
}
if (!\is_string($value)) {
throw new InvalidArgumentException('query must be a string or array');

2
vendor/services.php vendored
View File

@ -1,5 +1,5 @@
<?php
// This file is automatically generated at:2022-10-15 14:22:33
// This file is automatically generated at:2022-10-17 08:21:26
declare (strict_types = 1);
return array (
0 => 'taoser\\addons\\Service',

22
vendor/yzh52521/easyhttp/LICENSE vendored Normal file
View File

@ -0,0 +1,22 @@
The MIT License (MIT)
Copyright (c) 2014-present rap2hpoutre
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

341
vendor/yzh52521/easyhttp/README.md vendored Normal file
View File

@ -0,0 +1,341 @@
EasyHttp 是一个轻量级、语义化、对IDE友好的HTTP客户端支持常见的HTTP请求、异步请求和并发请求让你可以快速地使用 HTTP 请求与其他 Web 应用进行通信。
> EasyHttp并不强制依赖于cURL如果没有安装cURLEasyHttp会自动选择使用PHP流处理或者你也可以提供自己的发送HTTP请求的处理方式。
如果您觉得EasyHttp对您有用的话别忘了给点个赞哦^_^
github:[github.com/yzh52521/easyhttp](https://github.com/yzh52521/easyhttp "github.com/yzh52521/easyhttp")
gitee:[gitee.com/yzh52521/easyhttp](https://gitee.com/yzh52521/easyhttp "gitee.com/yzh52521/easyhttp")
本包是基于 [ gouguoyin/easyhttp ](https://gitee.com/gouguoyin/easyhttp "gitee.com/gouguoyin/easyhttp") 进行扩展开发,主要实现了以下扩展:
1. 增加 retry() 重试机制。
2. 增加 debug 日志调试功能。
3. 增加 withHost 指定服务端base_url
# 安装说明
#### 环境依赖
- PHP >= 7.2.5
- 如果使用PHP流处理allow_url_fopen 必须在php.ini中启用。
- 如果使用cURL处理cURL >= 7.19.4并且编译了OpenSSL 与 zlib。
#### 一键安装
composer require yzh52521/easyhttp
## 发起请求
#### 同步请求
###### 常规请求
```php
$response = Http::get('http://httpbin.org/get');
$response = Http::get('http://httpbin.org/get?name=yzh52521');
$response = Http::get('http://httpbin.org/get?name=yzh52521', ['age' => 18]);
$response = Http::post('http://httpbin.org/post');
$response = Http::post('http://httpbin.org/post', ['name' => 'yzh52521']);
$response = Http::patch(...);
$response = Http::put(...);
$response = Http::delete(...);
$response = Http::head(...);
$response = Http::options(...);
```
###### 指定服务端base_url的请求
```php
// 指定服务端base_url地址,最终请求地址为 https://serv.yzh52521.com/login
$response = Http::withHost('https://serv.yzh52521.com')->post('/login');
```
###### 发送 Content-Type 编码请求
```php
// application/x-www-form-urlencoded(默认)
$response = Http::asForm()->post(...);
// application/json
$response = Http::asJson()->post(...);
```
###### 发送 Multipart 表单请求
```php
$response = Http::asMultipart(
'file_input_name', file_get_contents('photo1.jpg'), 'photo2.jpg'
)->post('http://test.com/attachments');
$response = Http::asMultipart(
'file_input_name', fopen('photo1.jpg', 'r'), 'photo2.jpg'
)->post(...);
$response = Http::attach(
'file_input_name', file_get_contents('photo1.jpg'), 'photo2.jpg'
)->post(...);
$response = Http::attach(
'file_input_name', fopen('photo1.jpg', 'r'), 'photo2.jpg'
)->post(...);
```
> 表单enctype属性需要设置成 multipart/form-data
###### 携带请求头的请求
```php
$response = Http::withHeaders([
'x-powered-by' => 'yzh52521'
])->post(...);
```
###### 携带重定向的请求
```php
// 默认
$response = Http::withRedirect(false)->post(...);
$response = Http::withRedirect([
'max' => 5,
'strict' => false,
'referer' => true,
'protocols' => ['http', 'https'],
'track_redirects' => false
])->post(...);
```
###### 携带认证的请求
```php
// Basic认证
$response = Http::withBasicAuth('username', 'password')->post(...);
// Digest认证(需要被HTTP服务器支持)
$response = Http::withDigestAuth('username', 'password')->post(...);
```
###### 携带 User-Agent 的请求
```php
$response = Http::withUA('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/60.0.3100.0 Safari/537.36')->post(...);
```
###### 携带Token令牌的请求
```php
$response = Http::withToken('token')->post(...);
```
###### 携带认证文件的请求
```php
$response = Http::withCert('/path/server.pem', 'password')->post(...);
```
###### 携带SSL证书的请求
```php
// 默认
$response = Http::withVerify(false)->post(...);
$response = Http::withVerify('/path/to/cert.pem')->post(...);
```
###### 携带COOKIE的请求
```php
$response = Http::withCookies(array $cookies, string $domain)->post(...);
```
###### 携带协议版本的请求
```php
$response = Http::withVersion(1.1)->post(...);
```
###### 携带代理的请求
```php
$response = Http::withProxy('tcp://localhost:8125')->post(...);
$response = Http::withProxy([
'http' => 'tcp://localhost:8125', // Use this proxy with "http"
'https' => 'tcp://localhost:9124', // Use this proxy with "https",
'no' => ['.com.cn', 'yzh52521.cn'] // Don't use a proxy with these
])->post(...);
```
###### 设置超时时间(单位秒)
```php
$response = Http::timeout(60)->post(...);
```
###### 设置延迟时间(单位秒)
```php
$response = Http::delay(60)->post(...);
```
###### 设置并发次数
```php
$response = Http::concurrency(10)->promise(...);
```
###### 重发请求设置retry方法。重试次数/两次重试之间的时间间隔(毫秒):
```php
$response = Http::retry(3, 100)->post(...);
```
#### 异步请求
```php
use yzh52521\EasyHttp\Response;
use yzh52521\EasyHttp\RequestException;
Http::getAsync('http://easyhttp.yzh52521.cn/api/sleep3.json', ['token' => TOKEN], function (Response $response) {
echo '异步请求成功,响应内容:' . $response->body() . PHP_EOL;
}, function (RequestException $e) {
echo '异步请求异常,错误码:' . $e->getCode() . ',错误信息:' . $e->getMessage() . PHP_EOL;
});
echo json_encode(['code' => 200, 'msg' => '请求成功'], JSON_UNESCAPED_UNICODE) . PHP_EOL;
//输出
{"code":200,"msg":"请求成功"}
异步请求成功,响应内容:{"code":200,"msg":"success","second":3}
Http::getAsync('http1://easyhttp.yzh52521.cn/api/sleep3.json', function (Response $response) {
echo '异步请求成功,响应内容:' . $response->body() . PHP_EOL;
}, function (RequestException $e) {
echo '异步请求异常,错误信息:' . $e->getMessage() . PHP_EOL;
});
echo json_encode(['code' => 200, 'msg' => '请求成功'], JSON_UNESCAPED_UNICODE) . PHP_EOL;
//输出
{"code":200,"msg":"请求成功"}
异步请求异常错误信息cURL error 1: Protocol "http1" not supported or disabled in libcurl (see https://curl.haxx.se/libcurl/c/libcurl-errors.html)
Http::postAsync(...);
Http::patchAsync(...);
Http::putAsync(...);
Http::deleteAsync(...);
Http::headAsync(...);
Http::optionsAsync(...);
```
#### 异步并发请求
```php
use yzh52521\EasyHttp\Response;
use yzh52521\EasyHttp\RequestException;
$promises = [
Http::getAsync('http://easyhttp.yzh52521.cn/api/sleep3.json'),
Http::getAsync('http1://easyhttp.yzh52521.cn/api/sleep1.json', ['name' => 'yzh52521']),
Http::postAsync('http://easyhttp.yzh52521.cn/api/sleep2.json', ['name' => 'yzh52521']),
];
Http::concurrency(10)->multiAsync($promises, function (Response $response, $index) {
echo "发起第 $index 个异步请求,请求时长:" . $response->json()->second . '秒' . PHP_EOL;
}, function (RequestException $e, $index) {
echo "发起第 $index 个请求失败,失败原因:" . $e->getMessage() . PHP_EOL;
});
//输出
发起第 1 个请求失败失败原因cURL error 1: Protocol "http1" not supported or disabled in libcurl (see https://curl.haxx.se/libcurl/c/libcurl-errors.html)
发起第 2 个异步请求请求时长2 秒
发起第 0 个异步请求请求时长3 秒
```
> 如果未调用concurrency()方法,并发次数默认为$promises的元素个数$promises数组里必须是异步请求
## 使用响应
发起请求后会返回一个 yzh52521\EasyHttp\Response $response的实例该实例提供了以下方法来检查请求的响应
```php
$response->body() : string;
$response->json() : object;
$response->array() : array;
$response->status() : int;
$response->ok() : bool;
$response->successful() : bool;
$response->serverError() : bool;
$response->clientError() : bool;
$response->headers() : array;
$response->header($header) : string;
```
## 异常处理
请求在发生客户端或服务端错误时会抛出 yzh52521\EasyHttp\RequestException $e异常该实例提供了以下方法来返回异常信息
```php
$e->getCode() : int;
$e->getMessage() : string;
$e->getFile() : string;
$e->getLine() : int;
$e->getTrace() : array;
$e->getTraceAsString() : string;
```
## 调试日志
有时候难免要对 Http 的请求和响应包体进行记录以方便查找问题或做什么
```php
//传递一个日志类 thinkphp \think\facade\Log laravel Illuminate\Support\Facades\Log
Http::debug(Log::class)->post(...);
```
## 更新日志
### 2022-05-11
* 新增removeBodyFormat() 用于withOptions 指定body时清除原由的bodyFromat
### 2022-05-10
* 新增发送原生请求的方法client()
* 新增发送原生异步请求的方法clientASync()
### 2021-09-03
* 新增 debug() 调试日志
* 新增 retry() 重试机制
* 修复header重叠的bug
### 2020-03-30
* 修复部分情况下IDE不能智能提示的BUG
* get()、getAsync()方法支持带参数的url
* 新增withUA()方法
* 新增withStream()方法
* 新增asMultipart()方法attach()的别名
* 新增multiAsync()异步并发请求方法
### 2020-03-20
* 新增异步请求getAsync()方法
* 新增异步请求postAsync()方法
* 新增异步请求patchAsync()方法
* 新增异步请求putAsync()方法
* 新增异步请求deleteAsync()方法
* 新增异步请求headAsync()方法
* 新增异步请求optionsAsync()方法
## Todo List
- [x] 异步请求
- [x] 并发请求
- [x] 重试机制
- [ ] 支持http2
- [ ] 支持swoole
# easyhttp

33
vendor/yzh52521/easyhttp/composer.json vendored Normal file
View File

@ -0,0 +1,33 @@
{
"name": "yzh52521/easyhttp",
"description": "EasyHttp 是一个轻量级、语义化、对IDE友好的HTTP客户端支持常见的HTTP请求、异步请求和并发请求让你可以快速地使用 HTTP 请求与其他 Web 应用进行通信。",
"license": "MIT",
"keywords": [
"easyhttp",
"EasyHttp",
"php-http",
"phphttp",
"easy-http",
"php",
"http",
"curl"
],
"homepage": "https://github.com/yzh52521/easyhttp",
"authors": [
{
"name": "yzh52521",
"email": "396751927@qq.com"
}
],
"require": {
"php": "^7.2.5|^8.0",
"guzzlehttp/guzzle": "^6.0|^7.0",
"psr/log":"^1.0|^2.0|^3.0"
},
"autoload": {
"psr-4": {
"yzh52521\\EasyHttp\\": "src/"
}
},
"minimum-stability": "stable"
}

View File

@ -0,0 +1,10 @@
<?php
namespace yzh52521\EasyHttp;
use Exception;
class ConnectionException extends Exception
{
//
}

21
vendor/yzh52521/easyhttp/src/Facade.php vendored Normal file
View File

@ -0,0 +1,21 @@
<?php
namespace yzh52521\EasyHttp;
class Facade
{
protected $facade;
public function __construct()
{
$this->facade = new $this->facade;
}
public function __call($name, $params) {
return call_user_func_array([$this->facade, $name], $params);
}
public static function __callStatic($name, $params) {
return call_user_func_array([new static(), $name], $params);
}
}

55
vendor/yzh52521/easyhttp/src/Http.php vendored Normal file
View File

@ -0,0 +1,55 @@
<?php
namespace yzh52521\EasyHttp;
/**
* @method static \yzh52521\EasyHttp\Request asJson()
* @method static \yzh52521\EasyHttp\Request asForm()
* @method static \yzh52521\EasyHttp\Request asMultipart(string $name, string $contents, string|null $filename = null, array $headers)
* @method static \yzh52521\EasyHttp\Request attach(string $name, string $contents, string|null $filename = null, array $headers)
*
* @method static \yzh52521\EasyHttp\Request withRedirect(bool|array $redirect)
* @method static \yzh52521\EasyHttp\Request withStream(bool $boolean)
* @method static \yzh52521\EasyHttp\Request withVerify(bool|string $verify)
* @method static \yzh52521\EasyHttp\Request withHost(string $host)
* @method static \yzh52521\EasyHttp\Request withHeaders(array $headers)
* @method static \yzh52521\EasyHttp\Request withBasicAuth(string $username, string $password)
* @method static \yzh52521\EasyHttp\Request withDigestAuth(string $username, string $password)
* @method static \yzh52521\EasyHttp\Request withUA(string $ua)
* @method static \yzh52521\EasyHttp\Request withToken(string $token, string $type = 'Bearer')
* @method static \yzh52521\EasyHttp\Request withCookies(array $cookies, string $domain)
* @method static \yzh52521\EasyHttp\Request withProxy(string|array $proxy)
* @method static \yzh52521\EasyHttp\Request withVersion(string $version)
* @method static \yzh52521\EasyHttp\Request withOptions(array $options)
*
* @method static \yzh52521\EasyHttp\Request debug($class)
* @method static \yzh52521\EasyHttp\Request retry(int $retries=1,int $sleep=0)
* @method static \yzh52521\EasyHttp\Request delay(int $seconds)
* @method static \yzh52521\EasyHttp\Request timeout(int $seconds)
* @method static \yzh52521\EasyHttp\Request concurrency(int $times)
* @method static \yzh52521\EasyHttp\Request client(string $method, string $url, array $options = [])
* @method static \yzh52521\EasyHttp\Request clientAsync(string $method, string $url, array $options = [])
* @method static \yzh52521\EasyHttp\Request removeBodyFormat()
*
* @method static \yzh52521\EasyHttp\Request get(string $url, array $query = [])
* @method static \yzh52521\EasyHttp\Request post(string $url, array $data = [])
* @method static \yzh52521\EasyHttp\Request patch(string $url, array $data = [])
* @method static \yzh52521\EasyHttp\Request put(string $url, array $data = [])
* @method static \yzh52521\EasyHttp\Request delete(string $url, array $data = [])
* @method static \yzh52521\EasyHttp\Request head(string $url, array $data = [])
* @method static \yzh52521\EasyHttp\Request options(string $url, array $data = [])
*
* @method static \yzh52521\EasyHttp\Request getAsync(string $url, array|null $query = null, callable $success = null, callable $fail = null)
* @method static \yzh52521\EasyHttp\Request postAsync(string $url, array|null $data = null, callable $success = null, callable $fail = null)
* @method static \yzh52521\EasyHttp\Request patchAsync(string $url, array|null $data = null, callable $success = null, callable $fail = null)
* @method static \yzh52521\EasyHttp\Request putAsync(string $url, array|null $data = null, callable $success = null, callable $fail = null)
* @method static \yzh52521\EasyHttp\Request deleteAsync(string $url, array|null $data = null, callable $success = null, callable $fail = null)
* @method static \yzh52521\EasyHttp\Request headAsync(string $url, array|null $data = null, callable $success = null, callable $fail = null)
* @method static \yzh52521\EasyHttp\Request optionsAsync(string $url, array|null $data = null, callable $success = null, callable $fail = null)
* @method static \yzh52521\EasyHttp\Request multiAsync(array $promises, callable $success = null, callable $fail = null)
*/
class Http extends Facade
{
protected $facade = Request::class;
}

273
vendor/yzh52521/easyhttp/src/Logger.php vendored Normal file
View File

@ -0,0 +1,273 @@
<?php
namespace yzh52521\EasyHttp;
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\MessageFormatter;
use GuzzleHttp\Promise;
use Psr\Http\Message\RequestInterface;
use Psr\Http\Message\ResponseInterface;
use Psr\Log\LogLevel;
use Psr\Log\LoggerInterface;
use InvalidArgumentException;
/**
* Guzzle middleware which logs a request and its response.
*/
class Logger
{
/**
* @var \Psr\Log\LoggerInterface|callable
*/
protected $logger;
/**
* @var \GuzzleHttp\MessageFormatter|callable
*/
protected $formatter;
/**
* @var string|callable Constant or callable that accepts a Response.
*/
protected $logLevel;
/**
* @var boolean Whether or not to log requests as they are made.
*/
protected $logRequests;
/**
* Creates a callable middleware for logging requests and responses.
*
* @param LoggerInterface|callable $logger
* @param string|callable Constant or callable that accepts a Response.
*/
public function __construct($logger, $formatter = null)
{
// Use the setters to take care of type validation
$this->setLogger($logger);
$this->setFormatter($formatter ?: $this->getDefaultFormatter());
}
/**
* Returns the default formatter;
*
* @return MessageFormatter
*/
protected function getDefaultFormatter()
{
return new MessageFormatter();
}
/**
* Sets whether requests should be logged before the response is received.
*
* @param boolean $logRequests
*/
public function setRequestLoggingEnabled($logRequests = true)
{
$this->logRequests = (bool) $logRequests;
}
/**
* Sets the logger, which can be a PSR-3 logger or a callable that accepts
* a log level, message, and array context.
*
* @param LoggerInterface|callable $logger
*
* @throws InvalidArgumentException
*/
public function setLogger($logger)
{
if ($logger instanceof LoggerInterface || is_callable($logger)) {
$this->logger = $logger;
} else {
throw new InvalidArgumentException(
"Logger has to be a Psr\Log\LoggerInterface or callable"
);
}
}
/**
* Sets the formatter, which can be a MessageFormatter or callable that
* accepts a request, response, and a reason if an error has occurred.
*
* @param MessageFormatter|callable $formatter
*
* @throws InvalidArgumentException
*/
public function setFormatter($formatter)
{
if ($formatter instanceof MessageFormatter || is_callable($formatter)) {
$this->formatter = $formatter;
} else {
throw new InvalidArgumentException(
"Formatter has to be a \GuzzleHttp\MessageFormatter or callable"
);
}
}
/**
* Sets the log level to use, which can be either a string or a callable
* that accepts a response (which could be null). A log level could also
* be null, which indicates that the default log level should be used.
*
* @param string|callable|null
*/
public function setLogLevel($logLevel)
{
$this->logLevel = $logLevel;
}
/**
* Logs a request and/or a response.
*
* @param RequestInterface $request
* @param ResponseInterface|null $response
* @param $reason
* @return mixed
*/
protected function log(
RequestInterface $request,
ResponseInterface $response = null,
$reason = null
) {
if ($reason instanceof RequestException) {
$response = $reason->getResponse();
}
$level = $this->getLogLevel($response);
$message = $this->getLogMessage($request, $response, $reason);
$context = compact('request', 'response', 'reason');
// Make sure that the content of the body is available again.
if ($response) {
$response->getBody()->seek(0);;
}
if (is_callable($this->logger)) {
return call_user_func($this->logger, $level, $message, $context);
}
$this->logger->log($level, $message, $context);
}
/**
* Formats a request and response as a log message.
*
* @param RequestInterface $request
* @param ResponseInterface|null $response
* @param mixed $reason
*
* @return string The formatted message.
*/
protected function getLogMessage(
RequestInterface $request,
ResponseInterface $response = null,
$reason = null
) {
if ($this->formatter instanceof MessageFormatter) {
return $this->formatter->format(
$request,
$response,
$reason
);
}
return call_user_func($this->formatter, $request, $response, $reason);
}
/**
* Returns a log level for a given response.
*
* @param ResponseInterface $response The response being logged.
*
* @return string LogLevel
*/
protected function getLogLevel(ResponseInterface $response = null)
{
if ( ! $this->logLevel) {
return $this->getDefaultLogLevel($response);
}
if (is_callable($this->logLevel)) {
return call_user_func($this->logLevel, $response);
}
return (string) $this->logLevel;
}
/**
* Returns the default log level for a response.
*
* @param ResponseInterface $response
*
* @return string LogLevel
*/
protected function getDefaultLogLevel(ResponseInterface $response = null) {
if ($response && $response->getStatusCode() >= 300) {
return LogLevel::NOTICE;
}
return LogLevel::INFO;
}
/**
* Returns a function which is handled when a request was successful.
*
* @param RequestInterface $request
*
* @return \Closure
*/
protected function onSuccess(RequestInterface $request)
{
return function ($response) use ($request) {
$this->log($request, $response);
return $response;
};
}
/**
* Returns a function which is handled when a request was rejected.
*
* @param RequestInterface $request
*
* @return \Closure
*/
protected function onFailure(RequestInterface $request)
{
return function ($reason) use ($request) {
// Only log a rejected request if it hasn't already been logged.
if ( ! $this->logRequests) {
$this->log($request, null, $reason);
}
return Promise\rejection_for($reason);
};
}
/**
* Called when the middleware is handled by the client.
*
* @param callable $handler
*
* @return \Closure
*/
public function __invoke(callable $handler)
{
return function ($request, array $options) use ($handler) {
// Only log requests if explicitly set to do so
if ($this->logRequests) {
$this->log($request);
}
return $handler($request, $options)->then(
$this->onSuccess($request),
$this->onFailure($request)
);
};
}
}

607
vendor/yzh52521/easyhttp/src/Request.php vendored Normal file
View File

@ -0,0 +1,607 @@
<?php
namespace yzh52521\EasyHttp;
use GuzzleHttp\Handler\CurlHandler;
use GuzzleHttp\HandlerStack;
use GuzzleHttp\Pool;
use GuzzleHttp\Client;
use GuzzleHttp\Promise;
use GuzzleHttp\Cookie\CookieJar;
use GuzzleHttp\Exception\ConnectException;
/**
* @method \yzh52521\EasyHttp\Response body()
* @method \yzh52521\EasyHttp\Response array()
* @method \yzh52521\EasyHttp\Response json()
* @method \yzh52521\EasyHttp\Response headers()
* @method \yzh52521\EasyHttp\Response header( string $header )
* @method \yzh52521\EasyHttp\Response status()
* @method \yzh52521\EasyHttp\Response successful()
* @method \yzh52521\EasyHttp\Response ok()
* @method \yzh52521\EasyHttp\Response redirect()
* @method \yzh52521\EasyHttp\Response clientError()
* @method \yzh52521\EasyHttp\Response serverError()
*/
class Request
{
/**
* \GuzzleHttp\Client单例
* @var array
*/
private static $instances = [];
/**
* \GuzzleHttp\Client;
* @var Client
*/
protected $client;
/**
* Body格式
* @var string
*/
protected $bodyFormat;
protected $isRemoveBodyFormat = false;
/**
* @var array
*/
protected $options = [];
/**
* @var array
*/
protected $promises = [];
/**
* 并发次数
* @var
*/
protected $concurrency;
/**
*
* @var HandlerStack
*/
protected $handlerStack;
/**
* Request constructor.
*/
public function __construct()
{
$this->client = $this->getInstance();
$this->bodyFormat = 'form_params';
$this->options = [
'http_errors' => false,
];
if (!$this->handlerStack instanceof HandlerStack) {
$this->handlerStack = HandlerStack::create( new CurlHandler() );
}
$this->options['handler'] = $this->handlerStack;
}
/**
* Request destructor.
*/
public function __destruct()
{
if (!empty( $this->promises )) {
Promise\settle( $this->promises )->wait();
}
}
/**
* 获取单例
* @return mixed
*/
public function getInstance()
{
$name = get_called_class();
if (!isset( self::$instances[$name] )) {
self::$instances[$name] = new Client();
}
return self::$instances[$name];
}
public function asForm()
{
$this->bodyFormat = 'form_params';
$this->withHeaders( ['Content-Type' => 'application/x-www-form-urlencoded'] );
return $this;
}
public function asJson()
{
$this->bodyFormat = 'json';
$this->withHeaders( ['Content-Type' => 'application/json'] );
return $this;
}
public function asMultipart(string $name,string $contents,string $filename = null,array $headers = [])
{
$this->bodyFormat = 'multipart';
$this->options = array_filter( [
'name' => $name,
'contents' => $contents,
'headers' => $headers,
'filename' => $filename,
] );
return $this;
}
public function withHost(string $host)
{
$this->options['base_uri'] = $host;
return $this;
}
public function withOptions(array $options)
{
unset( $this->options[$this->bodyFormat],$this->options['body'] );
$this->options = array_merge_recursive( $this->options,$options );
return $this;
}
public function withCert(string $path,string $password)
{
$this->options['cert'] = [$path,$password];
return $this;
}
public function withHeaders(array $headers)
{
$this->options = array_merge_recursive( $this->options,[
'headers' => $headers,
] );
return $this;
}
public function withBasicAuth(string $username,string $password)
{
$this->options['auth'] = [$username,$password];
return $this;
}
public function withDigestAuth(string $username,string $password)
{
$this->options['auth'] = [$username,$password,'digest'];
return $this;
}
public function withUA(string $ua)
{
$this->options['headers']['User-Agent'] = trim( $ua );
return $this;
}
public function withToken(string $token,string $type = 'Bearer')
{
$this->options['headers']['Authorization'] = trim( $type.' '.$token );
return $this;
}
public function withCookies(array $cookies,string $domain)
{
$this->options = array_merge_recursive( $this->options,[
'cookies' => CookieJar::fromArray( $cookies,$domain ),
] );
return $this;
}
public function withProxy($proxy)
{
$this->options['proxy'] = $proxy;
return $this;
}
public function withVersion($version)
{
$this->options['version'] = $version;
return $this;
}
public function withRedirect($redirect = false)
{
$this->options['allow_redirects'] = $redirect;
return $this;
}
public function withVerify($verify = false)
{
$this->options['verify'] = $verify;
return $this;
}
public function withStream($boolean = false)
{
$this->options['stream'] = $boolean;
return $this;
}
public function concurrency(int $times)
{
$this->concurrency = $times;
return $this;
}
public function retry(int $retries = 1,int $sleep = 0)
{
$this->handlerStack->push( ( new Retry() )->handle( $retries,$sleep ) );
$this->options['handler'] = $this->handlerStack;
return $this;
}
public function delay(int $seconds)
{
$this->options['delay'] = $seconds * 1000;
return $this;
}
public function timeout(int $seconds)
{
$this->options['timeout'] = $seconds * 1000;
return $this;
}
public function removeBodyFormat()
{
$this->isRemoveBodyFormat = true;
return $this;
}
public function debug($class)
{
$logger = new Logger( function ($level,$message,array $context) use ($class) {
$class::log( $level,$message );
},function ($request,$response,$reason) {
$requestBody = $request->getBody();
$requestBody->rewind();
//请求头
$requestHeaders = [];
foreach ( $request->getHeaders() as $k => $vs ) {
foreach ( $vs as $v ) {
$requestHeaders[] = "$k: $v";
}
}
//响应头
$responseHeaders = [];
foreach ( $response->getHeaders() as $k => $vs ) {
foreach ( $vs as $v ) {
$responseHeaders[] = "$k: $v";
}
}
$uri = $request->getUri();
$path = $uri->getPath();
if ($query = $uri->getQuery()) {
$path .= '?'.$query;
}
return sprintf(
"Request %s\n%s %s HTTP/%s\r\n%s\r\n\r\n%s\r\n--------------------\r\nHTTP/%s %s %s\r\n%s\r\n\r\n%s",
$uri,
$request->getMethod(),
$path,
$request->getProtocolVersion(),
join( "\r\n",$requestHeaders ),
$requestBody->getContents(),
$response->getProtocolVersion(),
$response->getStatusCode(),
$response->getReasonPhrase(),
join( "\r\n",$responseHeaders ),
$response->getBody()->getContents()
);
} );
$this->handlerStack->push( $logger );
$this->options['handler'] = $this->handlerStack;
return $this;
}
public function attach(string $name,string $contents,string $filename = null,array $headers = [])
{
$this->options['multipart'] = array_filter( [
'name' => $name,
'contents' => $contents,
'headers' => $headers,
'filename' => $filename,
] );
return $this;
}
public function get(string $url,array $query = [])
{
$params= parse_url( $url,PHP_URL_QUERY );
parse_str( $params?:'',$result );
$this->options['query'] = array_merge( $result,$query );
return $this->request( 'GET',$url,$query );
}
public function post(string $url,array $data = [])
{
$this->options[$this->bodyFormat] = $data;
return $this->request( 'POST',$url,$data );
}
public function patch(string $url,array $data = [])
{
$this->options[$this->bodyFormat] = $data;
return $this->request( 'PATCH',$url,$data );
}
public function put(string $url,array $data = [])
{
$this->options[$this->bodyFormat] = $data;
return $this->request( 'PUT',$url,$data );
}
public function delete(string $url,array $data = [])
{
$this->options[$this->bodyFormat] = $data;
return $this->request( 'DELETE',$url,$data );
}
public function head(string $url,array $data = [])
{
$this->options[$this->bodyFormat] = $data;
return $this->request( 'HEAD',$url,$data );
}
public function options(string $url,array $data = [])
{
$this->options[$this->bodyFormat] = $data;
return $this->request( 'OPTIONS',$url,$data );
}
public function getAsync(string $url,$query = null,callable $success = null,callable $fail = null)
{
is_callable( $query ) || $this->options['query'] = $query;
return $this->requestAsync( 'GET',$url,$query,$success,$fail );
}
public function postAsync(string $url,$data = null,callable $success = null,callable $fail = null)
{
is_callable( $data ) || $this->options[$this->bodyFormat] = $data;
return $this->requestAsync( 'POST',$url,$data,$success,$fail );
}
public function patchAsync(string $url,$data = null,callable $success = null,callable $fail = null)
{
is_callable( $data ) || $this->options[$this->bodyFormat] = $data;
return $this->requestAsync( 'PATCH',$url,$data,$success,$fail );
}
public function putAsync(string $url,$data = null,callable $success = null,callable $fail = null)
{
is_callable( $data ) || $this->options[$this->bodyFormat] = $data;
return $this->requestAsync( 'PUT',$url,$data,$success,$fail );
}
public function deleteAsync(string $url,$data = null,callable $success = null,callable $fail = null)
{
is_callable( $data ) || $this->options[$this->bodyFormat] = $data;
return $this->requestAsync( 'DELETE',$url,$data,$success,$fail );
}
public function headAsync(string $url,$data = null,callable $success = null,callable $fail = null)
{
is_callable( $data ) || $this->options[$this->bodyFormat] = $data;
return $this->requestAsync( 'HEAD',$url,$data,$success,$fail );
}
public function optionsAsync(string $url,$data = null,callable $success = null,callable $fail = null)
{
is_callable( $data ) || $this->options[$this->bodyFormat] = $data;
return $this->requestAsync( 'OPTIONS',$url,$data,$success,$fail );
}
public function multiAsync(array $promises,callable $success = null,callable $fail = null)
{
$count = count( $promises );
$this->concurrency = $this->concurrency ?: $count;
$requests = function () use ($promises) {
foreach ( $promises as $promise ) {
yield function () use ($promise) {
return $promise;
};
}
};
$fulfilled = function ($response,$index) use ($success) {
if (!is_null( $success )) {
$response = $this->response( $response );
call_user_func_array( $success,[$response,$index] );
}
};
$rejected = function ($exception,$index) use ($fail) {
if (!is_null( $fail )) {
$exception = $this->exception( $exception );
call_user_func_array( $fail,[$exception,$index] );
}
};
$pool = new Pool( $this->client,$requests(),[
'concurrency' => $this->concurrency,
'fulfilled' => $fulfilled,
'rejected' => $rejected,
] );
$pool->promise();
return $pool;
}
protected function request(string $method,string $url,array $options = [])
{
isset( $this->options[$this->bodyFormat] ) && $this->options[$this->bodyFormat] = $options;
if ($this->isRemoveBodyFormat) {
unset( $this->options[$this->bodyFormat] );
}
try {
$response = $this->client->request( $method,$url,$this->options );
return $this->response( $response );
} catch ( ConnectException $e ) {
throw new ConnectionException( $e->getMessage(),0,$e );
}
}
/**
* 原生请求
* @param string $method
* @param string $url
* @param array $options
* @return Response
* @throws ConnectionException
* @throws \GuzzleHttp\Exception\GuzzleException
*/
public function client(string $method,string $url,array $options = [])
{
try {
if (empty( $options )) {
$options = $this->options;
}
$response = $this->client->request( $method,$url,$options );
return $this->response( $response );
} catch ( ConnectException $e ) {
throw new ConnectionException( $e->getMessage(),0,$e );
}
}
/**
* 原生异常请求
* @param string $method
* @param string $url
* @param array $options
* @return Response
* @throws ConnectionException
*/
public function clientAsync(string $method,string $url,array $options = [])
{
try {
if (empty( $options )) {
$options = $this->options;
}
$response = $this->client->requestAsync( $method,$url,$options );
return $this->response( $response );
} catch ( ConnectException $e ) {
throw new ConnectionException( $e->getMessage(),0,$e );
}
}
protected function requestAsync(
string $method,
string $url,
$options = null,
callable $success = null,
callable $fail = null
)
{
if (is_callable( $options )) {
$successCallback = $options;
$failCallback = $success;
} else {
$successCallback = $success;
$failCallback = $fail;
}
isset( $this->options[$this->bodyFormat] ) && $this->options[$this->bodyFormat] = $options;
if ($this->isRemoveBodyFormat) {
unset( $this->options[$this->bodyFormat] );
}
try {
$promise = $this->client->requestAsync( $method,$url,$this->options );
$fulfilled = function ($response) use ($successCallback) {
if (!is_null( $successCallback )) {
$response = $this->response( $response );
call_user_func_array( $successCallback,[$response] );
}
};
$rejected = function ($exception) use ($failCallback) {
if (!is_null( $failCallback )) {
$exception = $this->exception( $exception );
call_user_func_array( $failCallback,[$exception] );
}
};
$promise->then( $fulfilled,$rejected );
$this->promises[] = $promise;
return $promise;
} catch ( ConnectException $e ) {
throw new ConnectionException( $e->getMessage(),0,$e );
}
}
protected function response($response)
{
return new Response( $response );
}
protected function exception($exception)
{
return new RequestException( $exception );
}
}

View File

@ -0,0 +1,43 @@
<?php
namespace yzh52521\EasyHttp;
class RequestException
{
public $exception;
public function __construct($exception)
{
$this->exception = $exception;
}
public function getCode()
{
return $this->exception->getCode();
}
public function getMessage()
{
return $this->exception->getMessage();
}
public function getFile()
{
return $this->exception->getFile();
}
public function getLine()
{
return $this->exception->getLine();
}
public function getTrace()
{
return $this->exception->getTrace();
}
public function getTraceAsString()
{
return $this->exception->getTraceAsString();
}
}

View File

@ -0,0 +1,212 @@
<?php
namespace yzh52521\EasyHttp;
use ArrayAccess;
use LogicException;
class Response implements ArrayAccess
{
protected $response;
/**
* The decoded JSON response.
*
* @var array
*/
protected $decoded;
public function __construct($response)
{
$this->response = $response;
}
/**
* Get the body of the response.
* @return string
*/
public function body()
{
return (string)$this->response->getBody();
}
/**
* Get the Array decoded body of the response.
* @return array|mixed
*/
public function array()
{
if (!$this->decoded) {
$this->decoded = json_decode( (string)$this->response->getBody(),true );
}
return $this->decoded;
}
/**
* Get the JSON decoded body of the response.
* @return object|mixed
*/
public function json()
{
if (!$this->decoded) {
$this->decoded = json_decode( (string)$this->response->getBody() );
}
return $this->decoded;
}
/**
* Get a header from the response.
* @param string $header
* @return mixed
*/
public function header(string $header)
{
return $this->response->getHeaderLine( $header );
}
/**
* Get the headers from the response.
* @return mixed
*/
public function headers()
{
return $this->mapWithKeys( $this->response->getHeaders(),function ($v,$k) {
return [$k => $v];
} )->response;
}
/**
* Get the status code of the response.
* @return int
*/
public function status()
{
return (int)$this->response->getStatusCode();
}
/**
* Determine if the request was successful.
* @return bool
*/
public function successful()
{
return $this->status() >= 200 && $this->status() < 300;
}
/**
* Determine if the response code was "OK".
* @return bool
*/
public function ok()
{
return $this->status() === 200;
}
/**
* Determine if the response was a redirect.
* @return bool
*/
public function redirect()
{
return $this->status() >= 300 && $this->status() < 400;
}
/**
* Determine if the response indicates a client error occurred.
* @return bool
*/
public function clientError()
{
return $this->status() >= 400 && $this->status() < 500;
}
/**
* Determine if the response indicates a server error occurred.
* @return bool
*/
public function serverError()
{
return $this->status() >= 500;
}
/**
* Determine if the given offset exists.
*
* @param string $offset
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetExists($offset)
{
return array_key_exists( $offset,$this->json() );
}
/**
* Get the value for a given offset.
*
* @param string $offset
* @return mixed
*/
#[\ReturnTypeWillChange]
public function offsetGet($offset)
{
return $this->json()[$offset];
}
/**
* Set the value at the given offset.
*
* @param string $offset
* @param mixed $value
* @return void
*
* @throws \LogicException
*/
#[\ReturnTypeWillChange]
public function offsetSet($offset,$value)
{
throw new LogicException( 'Response data may not be mutated using array access.' );
}
/**
* Unset the value at the given offset.
*
* @param string $offset
* @return void
*
* @throws \LogicException
*/
#[\ReturnTypeWillChange]
public function offsetUnset($offset)
{
throw new LogicException( 'Response data may not be mutated using array access.' );
}
/**
* Get the body of the response.
*
* @return string
*/
public function __toString()
{
return $this->body();
}
protected function mapWithKeys($items,callable $callback)
{
$result = [];
foreach ( $items as $key => $value ) {
$assoc = $callback( $value,$key );
foreach ( $assoc as $mapKey => $mapValue ) {
$result[$mapKey] = $mapValue;
}
}
return new static( $result );
}
}

46
vendor/yzh52521/easyhttp/src/Retry.php vendored Normal file
View File

@ -0,0 +1,46 @@
<?php
namespace yzh52521\EasyHttp;
use GuzzleHttp\Middleware;
use GuzzleHttp\Psr7\Request;
use GuzzleHttp\Psr7\Response;
use GuzzleHttp\Exception\ServerException;
use GuzzleHttp\Exception\ConnectException;
class Retry
{
public function handle($retries,$sleep)
{
return Middleware::retry($this->decider($retries), $this->delay($sleep));
}
protected function decider(int $times)
{
return function (
$retries,
Request $request,
Response $response = null,
RequestException $exception = null
) use ($times) {
// 超过最大重试次数,不再重试
if ($retries >= $times) {
return false;
}
return $exception instanceof ConnectException || $exception instanceof ServerException || ($response && $response->getStatusCode() >= 500);
};
}
/**
* 返回一个匿名函数,该匿名函数返回下次重试的时间(毫秒)
* @param int $retry_delay
* @return \Closure
*/
protected function delay(int $retry_delay)
{
return function ($retries) use ($retry_delay) {
return $retry_delay * $retries;
};
}
}