jsonrpc
This commit is contained in:
parent
0dcefe05f8
commit
c931365f65
@ -7,6 +7,11 @@ SERVER_LISTEN = http://0.0.0.0:7878
|
|||||||
SERVER_COUNT = cpu_count()
|
SERVER_COUNT = cpu_count()
|
||||||
MAX_PACKAGE_SIZE = 1024*1024*5
|
MAX_PACKAGE_SIZE = 1024*1024*5
|
||||||
|
|
||||||
|
# jsonrcp服务配置
|
||||||
|
JSONRPC_SERVER_NAME = jsonrpc
|
||||||
|
JSONRPC_SERVER_PORT = 8022
|
||||||
|
JSONRPC_SERVER_COUNT = cpu_count()
|
||||||
|
|
||||||
# REDIS配置
|
# REDIS配置
|
||||||
REDIS_HOST = 127.0.0.1
|
REDIS_HOST = 127.0.0.1
|
||||||
REDIS_PORT = 6379
|
REDIS_PORT = 6379
|
||||||
|
@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
namespace app\controller;
|
namespace app\controller;
|
||||||
|
|
||||||
use ErrorException;
|
|
||||||
use Exception;
|
|
||||||
use support\Log;
|
use support\Log;
|
||||||
use support\Request;
|
use support\Request;
|
||||||
use Wandoubaba\Res;
|
use Wandoubaba\Res;
|
||||||
@ -13,7 +11,14 @@ class IndexController
|
|||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
$res = new Res();
|
$res = new Res();
|
||||||
$res->setData([env('LOG_MAX_FILES_DEBUG') ?: env('LOG_MAX_FIlES') ?: 7]);
|
$res1 = \jsonrpc\Client::service('default', 'Demo')->hello('Json');
|
||||||
|
$res2 = \jsonrpc\Client::service('user', 'User')->login('admin', '123456');
|
||||||
|
$res3 = \jsonrpc\Client::service('default', 'Demo')->text();
|
||||||
|
$res->setData([
|
||||||
|
'res1' => $res1,
|
||||||
|
'res2' => $res2,
|
||||||
|
'res3' => $res3,
|
||||||
|
]);
|
||||||
return json($res);
|
return json($res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -26,5 +31,4 @@ class IndexController
|
|||||||
{
|
{
|
||||||
return json(['code' => 0, 'msg' => 'ok']);
|
return json(['code' => 0, 'msg' => 'ok']);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
30
app/jsonrpc/Demo.php
Normal file
30
app/jsonrpc/Demo.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\jsonrpc;
|
||||||
|
|
||||||
|
use Wandoubaba\Res;
|
||||||
|
|
||||||
|
class Demo
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 提供rpc调用的服务方法示例,方法属性是public static
|
||||||
|
*
|
||||||
|
* @author Aaron <chenqiang@h024.cn>
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
*
|
||||||
|
* @return array Res转换的数组
|
||||||
|
*/
|
||||||
|
public static function hello(string $name)
|
||||||
|
{
|
||||||
|
$res = new Res();
|
||||||
|
|
||||||
|
$res->success()->setMsg("Hello, {$name}");
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function text()
|
||||||
|
{
|
||||||
|
return '直接返回字符串';
|
||||||
|
}
|
||||||
|
}
|
30
app/jsonrpc/User.php
Normal file
30
app/jsonrpc/User.php
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\jsonrpc;
|
||||||
|
|
||||||
|
use Wandoubaba\Res;
|
||||||
|
|
||||||
|
class User
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 提供rpc调用的服务方法示例,方法属性是public static
|
||||||
|
*
|
||||||
|
* @author Aaron <chenqiang@h024.cn>
|
||||||
|
*
|
||||||
|
* @param string $name
|
||||||
|
* @param string $password
|
||||||
|
*
|
||||||
|
* @return array Res转换的数组
|
||||||
|
*/
|
||||||
|
public static function login(string $name, string $password)
|
||||||
|
{
|
||||||
|
$res = new Res();
|
||||||
|
$correct = password_hash('123456', PASSWORD_DEFAULT);
|
||||||
|
if (password_verify($password, $correct)) {
|
||||||
|
$res->success()->setMsg("user {$name} login success.");
|
||||||
|
} else {
|
||||||
|
$res->failed()->setMsg("user {$name} login failed.");
|
||||||
|
}
|
||||||
|
return $res;
|
||||||
|
}
|
||||||
|
}
|
@ -47,7 +47,8 @@
|
|||||||
"app\\View\\Components\\": "./app/view/components"
|
"app\\View\\Components\\": "./app/view/components"
|
||||||
},
|
},
|
||||||
"files": [
|
"files": [
|
||||||
"./support/helpers.php"
|
"./support/helpers.php",
|
||||||
|
"./jsonrpc/Protocol.php"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
17
config/jsonrpc.php
Normal file
17
config/jsonrpc.php
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
<?php
|
||||||
|
/**
|
||||||
|
* 这是配合jsonrpc/Client.php使用的配置文件
|
||||||
|
* 如果项目中只提供服务端而不提供客户端,则不需要做这个配置
|
||||||
|
*/
|
||||||
|
|
||||||
|
return [
|
||||||
|
'default' => [
|
||||||
|
'tcp://127.0.0.1:' . env('JSONRPC_SERVER_PORT', 8021), // rpc服务端的地址和端口
|
||||||
|
],
|
||||||
|
'user' => [
|
||||||
|
'tcp://127.0.0.1:8022',
|
||||||
|
],
|
||||||
|
// 'server2' => [
|
||||||
|
// /// ...
|
||||||
|
// ]
|
||||||
|
];
|
@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This file is part of webman.
|
* This file is part of webman.
|
||||||
*
|
*
|
||||||
@ -38,5 +39,10 @@ return [
|
|||||||
'enable_memory_monitor' => DIRECTORY_SEPARATOR === '/',
|
'enable_memory_monitor' => DIRECTORY_SEPARATOR === '/',
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
]
|
],
|
||||||
|
env('JSONRPC_SERVER_NAME', 'jsonrpc') => [
|
||||||
|
'handler' => jsonrpc\Server::class,
|
||||||
|
'listen' => 'JsonNL://0.0.0.0:' . env('JSONRPC_SERVER_PORT', '8021'),
|
||||||
|
'count' => eval("return " . env('JSONRPC_SERVER_COUNT', cpu_count() * 4) . ";"),
|
||||||
|
],
|
||||||
];
|
];
|
||||||
|
270
jsonrpc/Client.php
Normal file
270
jsonrpc/Client.php
Normal file
@ -0,0 +1,270 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is part of workerman.
|
||||||
|
*
|
||||||
|
* Licensed under The MIT License
|
||||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||||
|
* Redistributions of files must retain the above copyright notice.
|
||||||
|
*
|
||||||
|
* @author walkor<walkor@workerman.net>
|
||||||
|
* @copyright walkor<walkor@workerman.net>
|
||||||
|
* @link http://www.workerman.net/
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||||
|
*/
|
||||||
|
|
||||||
|
namespace jsonrpc;
|
||||||
|
|
||||||
|
use Protocols\JsonNL;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Aaron修改注释
|
||||||
|
* 需要在config目录下创建jsonrpc.php文件,内容如下:
|
||||||
|
*
|
||||||
|
return [
|
||||||
|
'default' => [
|
||||||
|
'tcp://127.0.0.1:11002', // rpc服务端的地址和端口
|
||||||
|
],
|
||||||
|
'server1' => [
|
||||||
|
/// ...
|
||||||
|
],
|
||||||
|
'server2' => [
|
||||||
|
/// ...
|
||||||
|
]
|
||||||
|
];
|
||||||
|
*
|
||||||
|
* 客户端的调用方法
|
||||||
|
// 调用default服务组中Demo类中的hello方法并传入参数$name
|
||||||
|
$res = RpcClient::service('default', 'Demo')->hello($name);
|
||||||
|
// 调用server1服务组中User类中的login方法并传入参数$name, $password,
|
||||||
|
$res = RpcClient::service('server1', 'User')->login($userName, $userPassword);
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* RpcClient Rpc客户端
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* 示例
|
||||||
|
* // 服务端列表
|
||||||
|
$address_array = array(
|
||||||
|
'tcp://127.0.0.1:2015',
|
||||||
|
'tcp://127.0.0.1:2015'
|
||||||
|
);
|
||||||
|
// 配置服务端列表
|
||||||
|
RpcClient::config($address_array);
|
||||||
|
|
||||||
|
$uid = 567;
|
||||||
|
$user_client = RpcClient::instance('User');
|
||||||
|
// ==同步调用==
|
||||||
|
$ret_sync = $user_client->getInfoByUid($uid);
|
||||||
|
|
||||||
|
// ==异步调用==
|
||||||
|
// 异步发送数据
|
||||||
|
$user_client->asend_getInfoByUid($uid);
|
||||||
|
$user_client->asend_getEmail($uid);
|
||||||
|
|
||||||
|
这里是其它的业务代码
|
||||||
|
..............................................
|
||||||
|
|
||||||
|
// 异步接收数据
|
||||||
|
$ret_async1 = $user_client->arecv_getEmail($uid);
|
||||||
|
$ret_async2 = $user_client->arecv_getInfoByUid($uid);
|
||||||
|
*
|
||||||
|
* @author walkor <worker-man@qq.com>
|
||||||
|
*/
|
||||||
|
class Client
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 发送数据和接收数据的超时时间 单位S
|
||||||
|
* @var integer
|
||||||
|
*/
|
||||||
|
const TIME_OUT = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步调用发送数据前缀
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const ASYNC_SEND_PREFIX = 'asend_';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步调用接收数据
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
const ASYNC_RECV_PREFIX = 'arecv_';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 服务端地址
|
||||||
|
* @var array
|
||||||
|
*/
|
||||||
|
protected static $addressArray = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 异步调用实例
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected static $asyncInstances = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步调用实例
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected static $instances = array();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 到服务端的socket连接
|
||||||
|
* @var resource
|
||||||
|
*/
|
||||||
|
protected $connection = null;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 实例的服务名
|
||||||
|
* @var string
|
||||||
|
*/
|
||||||
|
protected $serviceName = '';
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Client的唯一公开方法,调用方式:
|
||||||
|
*
|
||||||
|
* $res = Client::service('default', 'Demo')->hello($name);
|
||||||
|
*
|
||||||
|
* @author Aaron <chenqiang@h024.cn>
|
||||||
|
*
|
||||||
|
* @param [type] $config 在config/rpc.php文件中定义的服务器组名称
|
||||||
|
* @param [type] $service_name rpc服务器中的对象名称
|
||||||
|
*/
|
||||||
|
public static function service(string $config, string $service_name)
|
||||||
|
{
|
||||||
|
$address_array = config('jsonrpc.' . $config);
|
||||||
|
self::config($address_array);
|
||||||
|
return self::instance($service_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 设置/获取服务端地址
|
||||||
|
* @param array $address_array
|
||||||
|
*/
|
||||||
|
protected static function config($address_array = array())
|
||||||
|
{
|
||||||
|
if (!empty($address_array)) {
|
||||||
|
self::$addressArray = $address_array;
|
||||||
|
}
|
||||||
|
return self::$addressArray;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取一个实例
|
||||||
|
* @param string $service_name
|
||||||
|
* @return instance of RpcClient
|
||||||
|
*/
|
||||||
|
protected static function instance($service_name)
|
||||||
|
{
|
||||||
|
if (!isset(self::$instances[$service_name])) {
|
||||||
|
self::$instances[$service_name] = new self($service_name);
|
||||||
|
}
|
||||||
|
return self::$instances[$service_name];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 构造函数
|
||||||
|
* @param string $service_name
|
||||||
|
*/
|
||||||
|
protected function __construct($service_name)
|
||||||
|
{
|
||||||
|
$this->serviceName = $service_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 调用
|
||||||
|
* @param string $method
|
||||||
|
* @param array $arguments
|
||||||
|
* @throws Exception
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public function __call($method, $arguments)
|
||||||
|
{
|
||||||
|
// 判断是否是异步发送
|
||||||
|
if (0 === strpos($method, self::ASYNC_SEND_PREFIX)) {
|
||||||
|
$real_method = substr($method, strlen(self::ASYNC_SEND_PREFIX));
|
||||||
|
$instance_key = $real_method . serialize($arguments);
|
||||||
|
if (isset(self::$asyncInstances[$instance_key])) {
|
||||||
|
throw new \Exception($this->serviceName . "->$method(" . implode(',', $arguments) . ") have already been called");
|
||||||
|
}
|
||||||
|
self::$asyncInstances[$instance_key] = new self($this->serviceName);
|
||||||
|
return self::$asyncInstances[$instance_key]->sendData($real_method, $arguments);
|
||||||
|
}
|
||||||
|
// 如果是异步接受数据
|
||||||
|
if (0 === strpos($method, self::ASYNC_RECV_PREFIX)) {
|
||||||
|
$real_method = substr($method, strlen(self::ASYNC_RECV_PREFIX));
|
||||||
|
$instance_key = $real_method . serialize($arguments);
|
||||||
|
if (!isset(self::$asyncInstances[$instance_key])) {
|
||||||
|
throw new \Exception($this->serviceName . "->asend_$real_method(" . implode(',', $arguments) . ") have not been called");
|
||||||
|
}
|
||||||
|
$tmp = (object)self::$asyncInstances[$instance_key];
|
||||||
|
unset(self::$asyncInstances[$instance_key]);
|
||||||
|
return $tmp->recvData();
|
||||||
|
}
|
||||||
|
// 同步发送接收
|
||||||
|
$this->sendData($method, $arguments);
|
||||||
|
return $this->recvData();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 发送数据给服务端
|
||||||
|
* @param string $method
|
||||||
|
* @param array $arguments
|
||||||
|
*/
|
||||||
|
protected function sendData($method, $arguments)
|
||||||
|
{
|
||||||
|
$this->openConnection();
|
||||||
|
$bin_data = JsonNL::encode(array(
|
||||||
|
'class' => $this->serviceName,
|
||||||
|
'method' => $method,
|
||||||
|
'param_array' => $arguments,
|
||||||
|
));
|
||||||
|
if (fwrite($this->connection, $bin_data) !== strlen($bin_data)) {
|
||||||
|
throw new \Exception('Can not send data');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 从服务端接收数据
|
||||||
|
* @throws Exception
|
||||||
|
*/
|
||||||
|
protected function recvData()
|
||||||
|
{
|
||||||
|
$ret = fgets($this->connection);
|
||||||
|
$this->closeConnection();
|
||||||
|
if (!$ret) {
|
||||||
|
throw new \Exception("recvData empty");
|
||||||
|
}
|
||||||
|
return JsonNL::decode($ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打开到服务端的连接
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function openConnection()
|
||||||
|
{
|
||||||
|
$address = self::$addressArray[array_rand(self::$addressArray)];
|
||||||
|
$this->connection = stream_socket_client($address, $err_no, $err_msg);
|
||||||
|
if (!$this->connection) {
|
||||||
|
throw new \Exception("can not connect to $address , $err_no:$err_msg");
|
||||||
|
}
|
||||||
|
stream_set_blocking($this->connection, true);
|
||||||
|
stream_set_timeout($this->connection, self::TIME_OUT);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关闭到服务端的连接
|
||||||
|
* @return void
|
||||||
|
*/
|
||||||
|
protected function closeConnection()
|
||||||
|
{
|
||||||
|
fclose($this->connection);
|
||||||
|
$this->connection = null;
|
||||||
|
}
|
||||||
|
}
|
66
jsonrpc/Protocol.php
Normal file
66
jsonrpc/Protocol.php
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This file is part of workerman.
|
||||||
|
*
|
||||||
|
* Licensed under The MIT License
|
||||||
|
* For full copyright and license information, please see the MIT-LICENSE.txt
|
||||||
|
* Redistributions of files must retain the above copyright notice.
|
||||||
|
*
|
||||||
|
* @author walkor<walkor@workerman.net>
|
||||||
|
* @copyright walkor<walkor@workerman.net>
|
||||||
|
* @link http://www.workerman.net/
|
||||||
|
* @license http://www.opensource.org/licenses/mit-license.php MIT License
|
||||||
|
*/
|
||||||
|
|
||||||
|
// 这个文件需要单独在compose.json中的autoload->files中引用
|
||||||
|
// "./jsonnl/Protocol.php"
|
||||||
|
namespace Protocols;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* RPC 协议解析 相关
|
||||||
|
* 协议格式为 [json字符串\n]
|
||||||
|
* @author walkor <worker-man@qq.com>
|
||||||
|
* */
|
||||||
|
class JsonNL
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 检查包的完整性
|
||||||
|
* 如果能够得到包长,则返回包的在buffer中的长度,否则返回0继续等待数据
|
||||||
|
* @param string $buffer
|
||||||
|
*/
|
||||||
|
public static function input($buffer)
|
||||||
|
{
|
||||||
|
// 获得换行字符"\n"位置
|
||||||
|
$pos = strpos($buffer, "\n");
|
||||||
|
// 没有换行符,无法得知包长,返回0继续等待数据
|
||||||
|
if ($pos === false) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
// 有换行符,返回当前包长(包含换行符)
|
||||||
|
return $pos + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 打包,当向客户端发送数据的时候会自动调用
|
||||||
|
* @param string $buffer
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function encode($buffer)
|
||||||
|
{
|
||||||
|
// json序列化,并加上换行符作为请求结束的标记
|
||||||
|
return json_encode($buffer) . "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解包,当接收到的数据字节数等于input返回的值(大于0的值)自动调用
|
||||||
|
* 并传递给onMessage回调函数的$data参数
|
||||||
|
* @param string $buffer
|
||||||
|
* @return string
|
||||||
|
*/
|
||||||
|
public static function decode($buffer)
|
||||||
|
{
|
||||||
|
// 去掉换行,还原成数组
|
||||||
|
return json_decode(trim($buffer), true);
|
||||||
|
}
|
||||||
|
}
|
55
jsonrpc/Server.php
Normal file
55
jsonrpc/Server.php
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace jsonrpc;
|
||||||
|
|
||||||
|
use Workerman\Connection\TcpConnection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 在config/process中把这个类注册为服务
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @author Aaron Chen <qiang.c@wukezhenzhu.com>
|
||||||
|
*/
|
||||||
|
class Server
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 在项目的app目录下创建jsonrpc目录,它下面的类的静态方法可以被jsonrpc客户端调用
|
||||||
|
*/
|
||||||
|
static $service_space = "app\\jsonrpc";
|
||||||
|
|
||||||
|
public function onMessage(TcpConnection $connection, $data)
|
||||||
|
{
|
||||||
|
// 判断数据是否正确
|
||||||
|
if (empty($data['class']) || empty($data['method']) || !isset($data['param_array'])) {
|
||||||
|
// 发送数据给客户端,请求包错误
|
||||||
|
return $connection->send(array('code' => 400, 'msg' => 'bad request'));
|
||||||
|
}
|
||||||
|
// 获得要调用的类、方法、及参数
|
||||||
|
$class = self::$service_space . "\\{$data['class']}";
|
||||||
|
$method = $data['method'];
|
||||||
|
$param_array = $data['param_array'];
|
||||||
|
|
||||||
|
// 判断类对应文件是否载入
|
||||||
|
if (!class_exists($class)) {
|
||||||
|
if (!class_exists($class) || !method_exists($class, $method)) {
|
||||||
|
$code = 404;
|
||||||
|
$msg = "class $class or method $method not found";
|
||||||
|
// 发送数据给客户端 类不存在
|
||||||
|
return $connection->send(array('code' => $code, 'msg' => $msg, 'data' => null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 调用类的方法
|
||||||
|
try {
|
||||||
|
$ret = call_user_func_array(array($class, $method), $param_array);
|
||||||
|
// 发送数据给客户端,调用成功,data下标对应的元素即为调用结果
|
||||||
|
return $connection->send($ret);
|
||||||
|
}
|
||||||
|
// 有异常
|
||||||
|
catch (\Exception $e) {
|
||||||
|
// 发送数据给客户端,发生异常,调用失败
|
||||||
|
$code = $e->getCode() ? $e->getCode() : 500;
|
||||||
|
return $connection->send(array('code' => $code, 'msg' => $e->getMessage(), 'data' => $e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user