fix:更新已知bug,优化代码

This commit is contained in:
Ying
2022-11-28 19:11:12 +08:00
parent f6aee95cfc
commit 9445b206a2
1378 changed files with 53759 additions and 20789 deletions

View File

@@ -23,7 +23,8 @@
"source": "https://github.com/webman-php/console"
},
"require": {
"symfony/console": ">=5.0"
"symfony/console": ">=5.0",
"doctrine/inflector": "^2.0"
},
"autoload": {
"psr-4": {

View File

@@ -17,10 +17,16 @@ class Command extends Application
$dir_iterator = new \RecursiveDirectoryIterator($path);
$iterator = new \RecursiveIteratorIterator($dir_iterator);
foreach ($iterator as $file) {
if (is_dir($file)) {
/** @var \SplFileInfo $file */
if (strpos($file->getFilename(), '.') === 0) {
continue;
}
$class_name = $namspace.'\\'.basename($file, '.php');
if ($file->getExtension() !== 'php') {
continue;
}
$relativePath = str_replace(str_replace('/', '\\', $path . '\\'), '', str_replace('/', '\\', $file->getRealPath()));
$realNamespace = trim($namspace . '\\' . trim(dirname($relativePath), '.'), '\\');
$class_name = trim($realNamespace . '\\' . $file->getBasename('.php'), '\\');
if (!is_a($class_name, Commands::class, true)) {
continue;
}

View File

@@ -0,0 +1,387 @@
<?php
namespace Webman\Console\Commands;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Input\InputOption;
use Webman\Console\Util;
class AppPluginCreateCommand extends Command
{
protected static $defaultName = 'app-plugin:create';
protected static $defaultDescription = 'App Plugin Create';
/**
* @return void
*/
protected function configure()
{
$this->addArgument('name', InputArgument::REQUIRED, 'App plugin name');
}
/**
* @param InputInterface $input
* @param OutputInterface $output
* @return int
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$name = $input->getArgument('name');
$output->writeln("Create App Plugin $name");
if (strpos($name, '/') !== false) {
$output->writeln('<error>Bad name, name must not contain character \'/\'</error>');
return self::FAILURE;
}
// Create dir config/plugin/$name
if (is_dir($plugin_config_path = base_path()."/plugin/$name")) {
$output->writeln("<error>Dir $plugin_config_path already exists</error>");
return self::FAILURE;
}
$this->createAll($name);
return self::SUCCESS;
}
/**
* @param $name
* @return void
*/
protected function createAll($name)
{
$base_path = base_path();
$this->mkdir("$base_path/plugin/$name/app/controller", 0777, true);
$this->mkdir("$base_path/plugin/$name/app/exception", 0777, true);
$this->mkdir("$base_path/plugin/$name/app/model", 0777, true);
$this->mkdir("$base_path/plugin/$name/app/middleware", 0777, true);
$this->mkdir("$base_path/plugin/$name/app/view/index", 0777, true);
$this->mkdir("$base_path/plugin/$name/config", 0777, true);
$this->mkdir("$base_path/plugin/$name/public", 0777, true);
$this->createFunctionsFile("$base_path/plugin/$name/app/functions.php");
$this->createControllerFile("$base_path/plugin/$name/app/controller/IndexController.php", $name);
$this->createViewFile("$base_path/plugin/$name/app/view/index/index.html");
$this->createExceptionFile("$base_path/plugin/$name/app/exception/Handler.php", $name);
$this->createConfigFiles("$base_path/plugin/$name/config", $name);
}
/**
* @param $path
* @return void
*/
protected function mkdir($path)
{
if (is_dir($path)) {
return;
}
echo "Create $path\r\n";
mkdir($path, 0777, true);
}
/**
* @param $path
* @param $name
* @return void
*/
protected function createControllerFile($path, $name)
{
$content = <<<EOF
<?php
namespace plugin\\$name\\app\\controller;
use support\\Request;
class IndexController
{
public function index()
{
return view('index/index', ['name' => '$name']);
}
}
EOF;
file_put_contents($path, $content);
}
/**
* @param $path
* @return void
*/
protected function createViewFile($path)
{
$content = <<<EOF
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="shortcut icon" href="/favicon.ico"/>
<title>webman app plugin</title>
</head>
<body>
hello <?=htmlspecialchars(\$name)?>
</body>
</html>
EOF;
file_put_contents($path, $content);
}
/**
* @param $path
* @return void
*/
protected function createExceptionFile($path, $name)
{
$content = <<<EOF
<?php
namespace plugin\\$name\\app\\exception;
use Throwable;
use Webman\\Http\\Request;
use Webman\\Http\\Response;
/**
* Class Handler
* @package Support\Exception
*/
class Handler extends \\support\\exception\\Handler
{
public function render(Request \$request, Throwable \$exception): Response
{
\$code = \$exception->getCode();
if (\$request->expectsJson()) {
\$json = ['code' => \$code ? \$code : 500, 'message' => \$this->_debug ? \$exception->getMessage() : 'Server internal error', 'type' => 'failed'];
\$this->_debug && \$json['traces'] = (string)\$exception;
return new Response(200, ['Content-Type' => 'application/json'],
\json_encode(\$json, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}
\$error = \$this->_debug ? \\nl2br((string)\$exception) : 'Server internal error';
return new Response(500, [], \$error);
}
}
EOF;
file_put_contents($path, $content);
}
/**
* @param $file
* @return void
*/
protected function createFunctionsFile($file)
{
$content = <<<EOF
<?php
/**
* Here is your custom functions.
*/
EOF;
file_put_contents($file, $content);
}
/**
* @param $base
* @param $name
* @return void
*/
protected function createConfigFiles($base, $name)
{
// app.php
$content = <<<EOF
<?php
use support\\Request;
return [
'debug' => true,
'controller_suffix' => 'Controller',
'controller_reuse' => true,
];
EOF;
file_put_contents("$base/app.php", $content);
// autoload.php
$content = <<<EOF
<?php
return [
'files' => [
base_path() . '/plugin/$name/app/functions.php',
]
];
EOF;
file_put_contents("$base/autoload.php", $content);
// container.php
$content = <<<EOF
<?php
return new Webman\\Container;
EOF;
file_put_contents("$base/container.php", $content);
// database.php
$content = <<<EOF
<?php
return [];
EOF;
file_put_contents("$base/database.php", $content);
// exception.php
$content = <<<EOF
<?php
return [
'' => \\plugin\\$name\\app\\exception\\Handler::class,
];
EOF;
file_put_contents("$base/exception.php", $content);
// log.php
$content = <<<EOF
<?php
return [
'default' => [
'handlers' => [
[
'class' => Monolog\\Handler\\RotatingFileHandler::class,
'constructor' => [
runtime_path() . '/logs/$name.log',
7,
Monolog\\Logger::DEBUG,
],
'formatter' => [
'class' => Monolog\\Formatter\\LineFormatter::class,
'constructor' => [null, 'Y-m-d H:i:s', true],
],
]
],
],
];
EOF;
file_put_contents("$base/log.php", $content);
// middleware.php
$content = <<<EOF
<?php
return [
'' => [
]
];
EOF;
file_put_contents("$base/middleware.php", $content);
// process.php
$content = <<<EOF
<?php
return [];
EOF;
file_put_contents("$base/process.php", $content);
// redis.php
$content = <<<EOF
<?php
return [
'default' => [
'host' => '127.0.0.1',
'password' => null,
'port' => 6379,
'database' => 0,
],
];
EOF;
file_put_contents("$base/redis.php", $content);
// route.php
$content = <<<EOF
<?php
use Webman\\Route;
EOF;
file_put_contents("$base/route.php", $content);
// static.php
$content = <<<EOF
<?php
return [
'enable' => true,
'middleware' => [], // Static file Middleware
];
EOF;
file_put_contents("$base/static.php", $content);
// translation.php
$content = <<<EOF
<?php
return [
// Default language
'locale' => 'zh_CN',
// Fallback language
'fallback_locale' => ['zh_CN', 'en'],
// Folder where language files are stored
'path' => "$base/resource/translations",
];
EOF;
file_put_contents("$base/translation.php", $content);
// view.php
$content = <<<EOF
<?php
use support\\view\\Raw;
use support\\view\\Twig;
use support\\view\\Blade;
use support\\view\\ThinkPHP;
return [
'handler' => Raw::class
];
EOF;
file_put_contents("$base/view.php", $content);
// thinkorm.php
$content = <<<EOF
<?php
return [];
EOF;
file_put_contents("$base/thinkorm.php", $content);
}
}

View File

@@ -29,7 +29,7 @@ class MakeCommandCommand extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$command = $name = $input->getArgument('name');
$command = $name = trim($input->getArgument('name'));
$output->writeln("Make command $name");
// make:command 不支持子目录
@@ -37,12 +37,14 @@ class MakeCommandCommand extends Command
if (!$command_str = Util::guessPath(app_path(), 'command')) {
$command_str = Util::guessPath(app_path(), 'controller') === 'Controller' ? 'Command' : 'command';
}
$upper = $command_str === 'Command';
$name = ucfirst($name);
$items= explode(':', $name);
$name='';
foreach ($items as $item) {
$name.=ucfirst($item);
}
$file = app_path() . "/$command_str/$name.php";
$upper = $command_str === 'Command';
$namespace = $upper ? 'App\Command' : 'app\command';
$this->createCommand($name, $namespace, $file, $command);
return self::SUCCESS;

View File

@@ -58,7 +58,15 @@ class MakeModelCommand extends Command
$file = app_path() . "/$path/$name.php";
$namespace = str_replace('/', '\\', ($upper ? 'App/' : 'app/') . $path);
}
if (!config('database') && config('thinkorm')) {
$database = config('database');
if (isset($database['default']) && strpos($database['default'], 'plugin.') === 0) {
$database = false;
}
$thinkorm = config('thinkorm');
if (isset($thinkorm['default']) && strpos($thinkorm['default'], 'plugin.') === 0) {
$thinkorm = false;
}
if (!$database && $thinkorm) {
$this->createTpModel($name, $namespace, $file);
} else {
$this->createModel($name, $namespace, $file);

View File

@@ -20,13 +20,13 @@ class RouteListCommand extends Command
*/
protected function execute(InputInterface $input, OutputInterface $output): int
{
$headers = ['uri', 'method', 'callback', 'middleware'];
$headers = ['uri', 'method', 'callback', 'middleware', 'name'];
$rows = [];
foreach (Route::getRoutes() as $route) {
foreach ($route->getMethods() as $method) {
$cb = $route->getCallback();
$cb = $cb instanceof \Closure ? 'Closure' : (is_array($cb) ? json_encode($cb) : var_export($cb, 1));
$rows[] = [$route->getPath(), $method, $cb, json_encode($route->getMiddleware() ?: null)];
$rows[] = [$route->getPath(), $method, $cb, json_encode($route->getMiddleware() ?: null), $route->getName()];
}
}

View File

@@ -1,6 +1,8 @@
<?php
namespace Webman\Console;
use Doctrine\Inflector\InflectorFactory;
class Util
{
public static function nameToNamespace($name)
@@ -14,10 +16,8 @@ class Util
public static function classToName($class)
{
$class = lcfirst($class);
return preg_replace_callback(['/([A-Z])/'], function ($matches) {
return '_' . strtolower($matches[1]);
}, $class);
$inflector = InflectorFactory::create()->build();
return $inflector->pluralize($inflector->tableize($class));
}
public static function nameToClass($class)

View File

@@ -28,24 +28,24 @@ class Event
protected static $logger;
/**
* @param $event_name
* @param $callback
* @param mixed $event_name
* @param callable $listener
* @return int
*/
public static function on($event_name, callable $callback): int
public static function on($event_name, callable $listener): int
{
$is_prefix_name = $event_name[strlen($event_name)-1] === '*';
$is_prefix_name = $event_name[strlen($event_name) - 1] === '*';
if ($is_prefix_name) {
static::$prefixEventMap[substr($event_name, 0, -1)][++static::$id] = $callback;
static::$prefixEventMap[substr($event_name, 0, -1)][++static::$id] = $listener;
} else {
static::$eventMap[$event_name][++static::$id] = $callback;
static::$eventMap[$event_name][++static::$id] = $listener;
}
return static::$id;
}
/**
* @param $event_name
* @param $id
* @param mixed $event_name
* @param integer $id
* @return int
*/
public static function off($event_name, int $id): int
@@ -71,44 +71,45 @@ class Event
foreach ($listeners as $listener) {
try {
$response = $listener($data, $event_name);
} catch (\Throwable $th) {
} catch (\Throwable $e) {
$responses[] = $e;
if (!static::$logger && is_callable('\support\Log::error')) {
static::$logger = Log::channel();
}
if (static::$logger) {
static::$logger->error($th->getMessage());
static::$logger->error($e);
}
continue;
}
$responses[] = $response;
if ($halt && !is_null($response)) {
return $response;
}
if ($response === false) {
break;
}
}
return $halt ? null : $responses;
}
/**
* @return array
*/
public static function list(): array
{
$callbacks = [];
$listeners = [];
foreach (static::$eventMap as $event_name => $callback_items) {
foreach ($callback_items as $id => $callback_item) {
$callbacks[$id] = [$event_name, $callback_item];
$listeners[$id] = [$event_name, $callback_item];
}
}
foreach (static::$prefixEventMap as $event_name => $callback_items) {
foreach ($callback_items as $id => $callback_item) {
$callbacks[$id] = [$event_name.'*', $callback_item];
$listeners[$id] = [$event_name . '*', $callback_item];
}
}
ksort($callbacks);
return $callbacks;
ksort($listeners);
return $listeners;
}
/**

21
vendor/webman/gateway-worker/LICENSE vendored Normal file
View File

@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2022 webman
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.

View File

@@ -0,0 +1,2 @@
# gateway-worker
webman gateway-worker plugin

View File

@@ -0,0 +1,13 @@
{
"name": "webman/gateway-worker",
"type": "library",
"license": "MIT",
"require": {
"workerman/gateway-worker": "^3.0"
},
"autoload": {
"psr-4": {
"Webman\\GatewayWorker\\": "src"
}
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Webman\GatewayWorker;
class BusinessWorker extends \GatewayWorker\BusinessWorker
{
public function __construct($config)
{
foreach ($config as $key => $value)
{
$this->$key = $value;
}
}
public function onWorkerStart()
{
$this->_onWorkerStart = $this->onWorkerStart;
$this->_onWorkerReload = $this->onWorkerReload;
$this->_onWorkerStop = $this->onWorkerStop;
$this->onWorkerStop = array($this, 'onWorkerStop');
$this->onWorkerStart = array($this, 'onWorkerStart');
$this->onWorkerReload = array($this, 'onWorkerReload');
$args = func_get_args();
$this->id = $args[0]->id;
parent::onWorkerStart();
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Webman\GatewayWorker;
class Gateway extends \GatewayWorker\Gateway
{
public function __construct($config)
{
foreach ($config as $key => $value)
{
$this->$key = $value;
}
$this->router = array("\\GatewayWorker\\Gateway", 'routerBind');
}
public function onConnect($connection)
{
parent::onClientConnect($connection);
}
public function onClose($connection)
{
parent::onClientClose($connection);
}
public function onMessage($connection, $data)
{
parent::onClientMessage($connection, $data);
}
public function onWorkerStart()
{
// 保存用户的回调,当对应的事件发生时触发
$this->_onWorkerStart = $this->onWorkerStart;
$this->onWorkerStart = array($this, 'onWorkerStart');
// 保存用户的回调,当对应的事件发生时触发
$this->_onConnect = $this->onConnect;
$this->onConnect = array($this, 'onClientConnect');
// onMessage禁止用户设置回调
$this->onMessage = array($this, 'onClientMessage');
// 保存用户的回调,当对应的事件发生时触发
$this->_onClose = $this->onClose;
$this->onClose = array($this, 'onClientClose');
// 保存用户的回调,当对应的事件发生时触发
$this->_onWorkerStop = $this->onWorkerStop;
$this->onWorkerStop = array($this, 'onWorkerStop');
if (!is_array($this->registerAddress)) {
$this->registerAddress = array($this->registerAddress);
}
// 记录进程启动的时间
$this->_startTime = time();
$args = func_get_args();
$this->id = $args[0]->id;
$worker = func_get_arg(0);
$this->_gatewayPort = substr(strrchr($worker->getSocketName(),':'),1);
parent::onWorkerStart();
}
}

View File

@@ -1,5 +1,5 @@
<?php
namespace Webman\Push;
namespace Webman\GatewayWorker;
class Install
{
@@ -8,9 +8,10 @@ class Install
/**
* @var array
*/
protected static $pathRelation = [
'config/plugin/webman/push' => 'config/plugin/webman/push'
];
protected static $pathRelation = array (
'plugin/webman/gateway' => 'plugin/webman/gateway',
'config/plugin/webman/gateway-worker' => 'config/plugin/webman/gateway-worker',
);
/**
* Install
@@ -18,15 +19,6 @@ class Install
*/
public static function install()
{
$config_app_path = __DIR__ . '/config/plugin/webman/push/app.php';
$config_app_content = file_get_contents($config_app_path);
$app_key = md5(microtime(true).rand(0, 2100000000));
$app_secret = md5($app_key.rand(0, 2100000000));
$config_app_content = str_replace([
'APP_KEY_TO_REPLACE',
'APP_SECRET_TO_REPLACE'
], [$app_key, $app_secret], $config_app_content);
file_put_contents($config_app_path, $config_app_content);
static::installByRelation();
}
@@ -74,4 +66,5 @@ class Install
remove_dir($path);
}
}
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Webman\GatewayWorker;
class Register extends \GatewayWorker\Register
{
public function __construct()
{
}
public function onWorkerstart()
{
// 设置 onMessage 连接回调
$this->onConnect = array($this, 'onConnect');
// 设置 onMessage 回调
$this->onMessage = array($this, 'onMessage');
// 设置 onClose 回调
$this->onClose = array($this, 'onClose');
// 记录进程启动的时间
$this->_startTime = time();
// 强制使用text协议
$this->protocol = '\Workerman\Protocols\Text';
// reusePort
$this->reusePort = false;
}
}

View File

@@ -0,0 +1,4 @@
<?php
return [
'enable' => true,
];

View File

@@ -0,0 +1,37 @@
<?php
use Webman\GatewayWorker\Gateway;
use Webman\GatewayWorker\BusinessWorker;
use Webman\GatewayWorker\Register;
return [
'gateway' => [
'handler' => Gateway::class,
'listen' => 'websocket://0.0.0.0:7272',
'count' => cpu_count(),
'reloadable' => false,
'constructor' => ['config' => [
'lanIp' => '127.0.0.1',
'startPort' => 2300,
'pingInterval' => 25,
'pingData' => '{"type":"ping"}',
'registerAddress' => '127.0.0.1:1236',
'onConnect' => function(){},
]]
],
'worker' => [
'handler' => BusinessWorker::class,
'count' => cpu_count()*2,
'constructor' => ['config' => [
'eventHandler' => plugin\webman\gateway\Events::class,
'name' => 'ChatBusinessWorker',
'registerAddress' => '127.0.0.1:1236',
]]
],
'register' => [
'handler' => Register::class,
'listen' => 'text://0.0.0.0:1236',
'count' => 1, // Must be 1
'constructor' => []
],
];

View File

@@ -0,0 +1,34 @@
<?php
namespace plugin\webman\gateway;
use GatewayWorker\Lib\Gateway;
class Events
{
public static function onWorkerStart($worker)
{
}
public static function onConnect($client_id)
{
}
public static function onWebSocketConnect($client_id, $data)
{
}
public static function onMessage($client_id, $message)
{
Gateway::sendToClient($client_id, "receive message $message");
}
public static function onClose($client_id)
{
}
}

View File

@@ -1,3 +0,0 @@
# push
webman push plugin
https://www.workerman.net/plugin/2

View File

@@ -1,13 +0,0 @@
{
"name": "webman/push",
"type": "library",
"license": "MIT",
"require": {
"php": ">=7.2"
},
"autoload": {
"psr-4": {
"Webman\\Push\\": "src"
}
}
}

View File

@@ -1,240 +0,0 @@
<?php
namespace Webman\Push;
/**
* Modified from https://github.com/pusher/pusher-http-php
*/
class Api
{
/**
* @var array
*/
protected $_settings = [
'timeout' => 2,
];
/**
* @param $api_address
* @param $auth_key
* @param $secret
* @throws PushException
*/
public function __construct($api_address, $auth_key, $secret)
{
$this->checkCompatibility();
$this->_settings['api_address'] = $api_address;
$this->_settings['auth_key'] = $auth_key;
$this->_settings['secret'] = $secret;
$this->_settings['base_path'] = '/apps/1024';
}
/**
* trigger an event by providing event name and payload.
* Optionally provide a socket ID to exclude a client (most likely the sender).
*
* @param array|string $channels An array of channel names to publish the event on.
* @param string $event
* @param mixed $data Event data
* @param string $socket_id [optional]
* @return bool|string
*/
public function trigger($channels, $event, $data, $socket_id = null)
{
if (is_string($channels)) {
$channels = array($channels);
}
$query_params = array();
$s_url = $this->_settings['base_path'] . '/events';
$data_encoded = json_encode($data);
$post_params = array();
$post_params['name'] = $event;
$post_params['data'] = $data_encoded;
$post_params['channels'] = $channels;
if ($socket_id !== null) {
$post_params['socket_id'] = $socket_id;
}
$post_value = json_encode($post_params);
$query_params['body_md5'] = md5($post_value);
$ch = $this->createCurl($this->_settings['api_address'], $s_url, 'POST', $query_params);
curl_setopt($ch, CURLOPT_POSTFIELDS, $post_value);
$response = $this->execCurl($ch);
if ($response['status'] === 200) {
return true;
} else {
return false;
}
}
public function getChannelInfo($channel, $params = array())
{
$this->validateChannel($channel);
$response = $this->get('/channels/' . $channel, $params);
if ($response['status'] === 200) {
$response = json_decode($response['body']);
} else {
$response = false;
}
return $response;
}
public function getChannels($params = array())
{
return $this->get('/channels', $params);
}
private function checkCompatibility()
{
if (!in_array('sha256', hash_algos())) {
throw new PushException('SHA256 appears to be unsupported - make sure you have support for it, or upgrade your version of PHP.');
}
}
private function validateChannel($channel)
{
if (!preg_match('/\A[-a-zA-Z0-9_=@,.;]+\z/', $channel)) {
throw new PushException('Invalid channel name ' . $channel);
}
}
private function validateSocketId($socket_id)
{
if ($socket_id !== null && !preg_match('/\A\d+\.\d+\z/', $socket_id)) {
throw new PushException('Invalid socket ID ' . $socket_id);
}
}
private function createCurl($domain, $s_url, $request_method = 'GET', $query_params = array())
{
static $ch = null;
$signed_query = self::buildAuthQueryString(
$this->_settings['auth_key'],
$this->_settings['secret'],
$request_method,
$s_url,
$query_params);
$full_url = $domain . $s_url . '?' . $signed_query;
if (null === $ch) {
$ch = curl_init();
if ($ch === false) {
throw new PushException('Could not initialise cURL!');
}
}
if (function_exists('curl_reset')) {
curl_reset($ch);
}
curl_setopt($ch, CURLOPT_URL, $full_url);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'Content-Type: application/json',
'Expect:',
));
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_TIMEOUT, $this->_settings['timeout']);
if ($request_method === 'POST') {
curl_setopt($ch, CURLOPT_POST, 1);
} elseif ($request_method === 'GET') {
curl_setopt($ch, CURLOPT_POST, 0);
} // Otherwise let the user configure it
return $ch;
}
private function execCurl($ch)
{
$response = array();
$response['body'] = curl_exec($ch);
$response['status'] = curl_getinfo($ch, CURLINFO_HTTP_CODE);
return $response;
}
public static function buildAuthQueryString($auth_key, $auth_secret, $request_method, $request_path,
$query_params = array())
{
$params = array();
$params['auth_key'] = $auth_key;
$params['auth_timestamp'] = time();
$params = array_merge($params, $query_params);
ksort($params);
$string_to_sign = "$request_method\n" . $request_path . "\n" . self::arrayImplode('=', '&', $params);
$auth_signature = hash_hmac('sha256', $string_to_sign, $auth_secret, false);
$params['auth_signature'] = $auth_signature;
ksort($params);
$auth_query_string = self::arrayImplode('=', '&', $params);
return $auth_query_string;
}
public static function arrayImplode($glue, $separator, $array)
{
if (!is_array($array)) {
return $array;
}
$string = array();
foreach ($array as $key => $val) {
if (is_array($val)) {
$val = implode(',', $val);
}
$string[] = "{$key}{$glue}{$val}";
}
return implode($separator, $string);
}
public function get($path, $params = array())
{
$ch = $this->createCurl($this->_settings['api_address'], $this->_settings['base_path'] . $path, 'GET', $params);
$response = $this->execCurl($ch);
if ($response['status'] === 200) {
$response['result'] = json_decode($response['body'], true);
}
return $response;
}
public function socketAuth($channel, $socket_id, $custom_data = null)
{
$this->validateChannel($channel);
$this->validateSocketId($socket_id);
if ($custom_data) {
$signature = hash_hmac('sha256', $socket_id . ':' . $channel . ':' . $custom_data, $this->_settings['secret'], false);
} else {
$signature = hash_hmac('sha256', $socket_id . ':' . $channel, $this->_settings['secret'], false);
}
$signature = array('auth' => $this->_settings['auth_key'] . ':' . $signature);
if ($custom_data) {
$signature['channel_data'] = $custom_data;
}
return json_encode($signature);
}
public function presenceAuth($channel, $socket_id, $user_id, $user_info = null)
{
$user_data = array('user_id' => $user_id);
if ($user_info) {
$user_data['user_info'] = $user_info;
}
return $this->socketAuth($channel, $socket_id, json_encode($user_data));
}
}
class PushException extends \Exception
{
}

View File

@@ -1,968 +0,0 @@
<?php
/**
* This file is part of webman.
*
* 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 Webman\Push;
use Workerman\Connection\AsyncTcpConnection;
use Workerman\Connection\TcpConnection;
use Workerman\Protocols\Http\Request;
use Workerman\Protocols\Http\Response;
use Workerman\Timer;
use Workerman\Worker;
class Server
{
/**
* 应用信息
*
* @var array
*/
public $appInfo = array();
/**
* 心跳时间
*
* @var int
*/
public $keepAliveTimeout = 60;
/**
* api监听的ip端口
*
* @var string
*/
public $apiListen = 'http://0.0.0.0:1080';
/**
* webhook 延迟设置
*
* @var int
*/
public $webHookDelay = 3;
/**
* @var array
*/
protected $_globalDataSnapshot = [];
/**
* 事件对应的客户端链接
*
* @var array
*/
protected $_eventClients = [];
/**
* 所有的客户端链接
*
* @var array
*/
protected $_allClients = [];
/**
* array(
* 'app_key1' => array(
* 'channel1' => array(
* 'users' => array(
* 'uid1' => array('user_info'=>[], 'ref_count' => x),
* 'uid2' => array('user_info'=>[], 'ref_count' => x),
* ),
* 'type' => 'presence',
* 'subscription_count' => x
* ),
* 'channel2' => array(
* 'users' => array(
* 'uid3' => array('user_info'=>[], 'ref_count' => x)
* ),
* 'type' => 'presence',
* 'subscription_count' => x
* ),
* ),
* 'app_key2' => array(
* 'channel1' => array(
* 'type' => 'private',
* 'subscription_count' => x
* ),
* 'channel2' => array(
* 'type' => 'public',
* 'subscription_count' => x
* ),
* )
* )
* @var array
*/
protected $_globalData = [];
/**
* 当前进程全局唯一订阅id
*
* @var string
*/
protected $_globalID = 1;
/**
* 构造函数
*
* @param string $socket_name
* @param array $context
*/
public function __construct($api_listen, $app_info)
{
$this->apiListen = $api_listen;
$this->appInfo = $app_info;
}
/**
* @param $worker
* @return void
* @throws \Exception
*/
public function onWorkerStart($worker)
{
$api_worker = new Worker($this->apiListen);
$api_worker->onMessage = array($this, 'onApiClientMessage');
$api_worker->listen();
Timer::add($this->keepAliveTimeout/2, array($this, 'checkHeartbeat'));
Timer::add($this->webHookDelay, array($this, 'webHookCheck'));
}
/**
* 客户端连接后
*
* @param $connection
*/
public function onConnect($connection) {
// 客户端有多少次没在规定时间发送心跳
$connection->clientNotSendPingCount = 0;
// 设置websocket握手事件回调
$connection->onWebSocketConnect = array($this, 'onWebSocketConnect');
}
/**
* 当websocket握手时
* @param $connection
* @return mixed
*/
public function onWebSocketConnect(TcpConnection $connection, $header)
{
// /app/1234567890abcdefghig?protocol=7&client=js&version=3.2.4&flash=false
if (!preg_match('/ \/app\/([^\/^\?^ ]+)/', $header, $match)) {
echo "app_key not found\n$header\n";
$connection->pauseRecv();
return;
}
$app_key = $match[1];
if (!isset($this->appInfo[$app_key])) {
echo "Invalid app_key $app_key\n";
$connection->pauseRecv();
return;
}
$socket_id = $this->createsocketID($connection);
$connection->appKey = $app_key;
$connection->socketID = $socket_id;
$connection->channels = array(''=>'');
$connection->channelUidMap = [];
$connection->clientNotSendPingCount = 0;
$this->_eventClients[$app_key][''][$socket_id] = $connection;
$this->_allClients[$socket_id] = $connection;
/*
* 向客户端发送链接成功的消息
* {"event":"pusher:connection_established","data":"{\"socket_id\":\"208836.27464492\",\"activity_timeout\":120}"}
*/
$data = array(
'event' => 'pusher:connection_established',
'data' => json_encode(array(
'socket_id' => $socket_id,
'activity_timeout' => 55
))
);
$connection->send(json_encode($data));
}
/**
* 客户端关闭链接时
*
* @param $connection
*/
public function onClose($connection)
{
if (!isset($connection->socketID)) {
return;
}
$socket_id = $connection->socketID;
$app_key = $connection->appKey;
unset($this->_allClients[$socket_id]);
unset($this->_eventClients[$app_key][''][$socket_id]);
if (isset($connection->channels)) {
$app_key = $connection->appKey;
foreach ($connection->channels as $channel => $uid) {
if ('' === $channel) {
continue;
}
if ($uid === '') {
$this->unsubscribePublicChannel($connection, $channel);
} else {
$this->unsubscribePresenceChannel($connection, $channel, $uid);
}
unset($this->_eventClients[$app_key][$channel][$socket_id]);
}
}
}
/**
* 客户端发来消息时
*
* @param $connection
* @param $data
*
* @return void
*/
public function onMessage($connection, $data)
{
$connection->clientNotSendPingCount = 0;
$data = json_decode($data, true);
if (!$data) {
return;
}
$event = $data['event'];
switch ($event) {
case 'pusher:ping':
$connection->send('{"event":"pusher:pong","data":"{}"}');
return;
// {"event":"pusher:subscribe","data":{"channel":"my-channel"}}
case 'pusher:subscribe':
$channel = $data['data']['channel'];
// private- 和 presence- 开头的channel需要验证
$channel_type = $this->getChannelType($channel);
if ($channel_type === 'presence') {
// {"event":"pusher:subscribe","data":{"auth":"b054014693241bcd9c26:10e3b628cb78e8bc4d1f44d47c9294551b446ae6ec10ef113d3d7e84e99763e6","channel_data":"{\"user_id\":100,\"user_info\":{\"name\":\"123\"}}","channel":"presence-channel"}}
$client_auth = $data['data']['auth'];
if (!isset($data['data']['channel_data'])) {
$connection->send($this->error(null, 'Empty channel_data'));
return;
}
$auth = $connection->appKey.':'.hash_hmac('sha256', $connection->socketID.':'.$channel.':'.$data['data']['channel_data'], $this->appInfo[$connection->appKey]['app_secret'], false);
// {"event":"pusher:error","data":{"code":null,"message":"Received invalid JSON"}}
if ($client_auth !== $auth) {
return $connection->send($this->error(null, 'Received invalid JSON '.$auth));
}
$user_data = json_decode($data['data']['channel_data'], true);
if (!$user_data || !isset($user_data['user_id']) || !isset($user_data['user_info'])) {
$connection->send($this->error(null, 'Bad channel_data'));
return;
}
$this->subscribePresence($connection, $channel, $user_data['user_id'], $user_data['user_info']);
return;
} elseif($channel_type === 'private') {
// {"event":"pusher:subscribe","data":{"auth":"b054014693241bcd9c26:10e3b628cb78e8bc4d1f44d47c9294551b446ae6ec10ef113d3d7e84e99763e6","channel_data":"{\"user_id\":100,\"user_info\":{\"name\":\"123\"}}","channel":"presence-channel"}}
$client_auth = $data['data']['auth'];
$auth = $connection->appKey.':'.hash_hmac('sha256', $connection->socketID.':'.$channel, $this->appInfo[$connection->appKey]['app_secret'], false);
// {"event":"pusher:error","data":{"code":null,"message":"Received invalid JSON"}}
if ($client_auth !== $auth) {
return $connection->send($this->error(null, 'Received invalid JSON '.$auth));
}
$this->subscribePrivateChannel($connection, $channel);
} else {
$this->subscribePublicChannel($connection, $channel);
}
// {"event":"pusher_internal:subscription_succeeded","data":"{}","channel":"my-channel"}
$connection->send(json_encode(
array(
'event' => 'pusher_internal:subscription_succeeded',
'data' => '{}',
'channel' => $channel
), JSON_UNESCAPED_UNICODE
));
return;
// {"event":"pusher:unsubscribe","data":{"channel":"my-channel"}}
case 'pusher:unsubscribe':
$app_key = $connection->appKey;
$channel = $data['data']['channel'];
$channel_type = $this->getChannelType($channel);
switch ($channel_type) {
case 'public':
$this->unsubscribePublicChannel($connection, $channel);
break;
case 'private':
$this->unsubscribePrivateChannel($connection, $channel);
break;
case 'presence':
$uid = $connection->channels[$channel];
$this->unsubscribePresenceChannel($connection, $channel, $uid);
break;
}
return;
// {"event":"client-event","data":{"your":"hi"},"channel":"presence-channel"}
default:
if (strpos($event, 'pusher:') === 0) {
return $connection->send($this->error(null, 'Unknown event'));
}
$channel = $data['channel'];
// 客户端触发事件必须是private 或者 presence的channel
$channel_type = $this->getChannelType($channel);
if ($channel_type !== 'private' && $channel_type !== 'presence') {
// {"event":"pusher:error","data":{"code":null,"message":"Client event rejected - only supported on private and presence channels"}}
return $connection->send($this->error(null, 'Client event rejected - only supported on private and presence channels'));
}
// 当前链接没有订阅这个channel
if (!isset($connection->channels[$channel])) {
return $connection->send($this->error(null, 'Client event rejected - you didn\'t subscribe this channel'));
}
// 事件必须以client-为前缀
if (strpos($event, 'client-') !== 0) {
return $connection->send($this->error(null, 'Client event rejected - client events must be prefixed by \'client-\''));
}
// @todo 检查是否设置了可前端发布事件
// {"event":"pusher:error","data":{"code":null,"message":"To send client events, you must enable this feature in the Settings page of your dashboard."}}
// 全局发布事件
$this->publishToClients($connection->appKey, $channel, $event, json_encode($data['data'], JSON_UNESCAPED_UNICODE), $connection->socketID);
}
}
/**
* 获得channel类型
*
* @param $channel
* @return string
*/
protected function getChannelType($channel) {
if (strpos($channel, 'private-') === 0) {
return 'private';
} elseif (strpos($channel, 'presence-') === 0) {
return 'presence';
}
return 'public';
}
/**
* 组装失败信息
*
* @param $code
* @param $message
* @return string
*/
protected function error($code, $message)
{
return json_encode(array('event'=>'pusher:error', 'data' => array('code' => $code, 'message' => $message)), JSON_UNESCAPED_UNICODE);
}
/**
* 客户端订阅channel
*
* @param $connection
* @param $channel
*
* @return void
*/
public function subscribePublicChannel($connection, $channel) {
$app_key = $connection->appKey;
$connection->channels[$channel] = '';
$this->_eventClients[$app_key][$channel][$connection->socketID] = $connection;
if (!isset($this->_globalData[$app_key][$channel])) {
$this->_globalData[$app_key][$channel] = array(
'type' => 'presence',
'subscription_count' => 0
);
}
$this->_globalData[$app_key][$channel]['subscription_count'] += 1;
}
/**
* 客户端订阅channel
*
* @param $connection
* @param $channel
*
* @return void
*/
public function subscribePrivateChannel($connection, $channel) {
$this->subscribePublicChannel($connection, $channel);
}
/**
* 客户端订阅channel
*
* @param $connection
* @param $channel
*
* @return void
*/
public function subscribePresence($connection, $channel, $uid, $user_info) {
$app_key = $connection->appKey;
$connection->channels[$channel] = $uid;
$this->_eventClients[$app_key][$channel][$connection->socketID] = $connection;
if (!isset($this->_globalData[$app_key][$channel])) {
$this->_globalData[$app_key][$channel] = array(
'type' => 'presence',
'users' => [],
'subscription_count' => 0
);
}
$this->_globalData[$app_key][$channel]['subscription_count'] += 1;
$member_added = false;
if (!isset($this->_globalData[$app_key][$channel]['users'][$uid]['user_info'])) {
$this->_globalData[$app_key][$channel]['users'][$uid] = array('user_info'=>$user_info, 'ref_count' => 0);
$member_added = true;
}
$this->_globalData[$app_key][$channel]['users'][$uid]['ref_count'] += 1;
$presence_data = $this->getPresenceChannelDataForSubscribe($app_key, $channel);
if ($member_added) {
// {"event":"pusher_internal:member_added","data":"{\"user_id\":1488465780,\"user_info\":{\"name\":\"123\",\"sex\":\"1\"}}","channel":"presence-channel"}
$this->publishToClients($app_key, $channel, 'pusher_internal:member_added', json_encode(array(
'user_id' => $uid,
'user_info' => $user_info
), JSON_UNESCAPED_UNICODE), $connection->socketID);
}
// {"event":"pusher_internal:subscription_succeeded","data":"{\"presence\":{\"count\":2,\"ids\":[\"1488465780\",\"14884657802\"],\"hash\":{\"1488465780\":{\"name\":\"123\",\"sex\":\"1\"},\"14884657802\":{\"name\":\"123\",\"sex\":\"1\"}}}}","channel":"presence-channel"}
$connection->send(json_encode(array(
'event' => 'pusher_internal:subscription_succeeded',
'data' => json_encode($presence_data),
'channel' => $channel
),JSON_UNESCAPED_UNICODE
));
}
public function getPresenceChannelDataForSubscribe($app_key, $channel)
{
$hash = [];
$count = 100;
if (isset($this->_globalData[$app_key][$channel])) {
foreach ($this->_globalData[$app_key][$channel]['users'] as $uid => $item) {
$hash[$uid] = $item['user_info'];
if ($count-- <= 0) {
break;
}
}
//$hash = array_slice($this->_globalData[$app_key][$channel]['users'], 0, 100, true);
}
return array(
'presence' => array(
'count' => count($this->_globalData[$app_key][$channel]['users']),
'ids' => array_keys($hash),
'hash' => $hash
)
);
}
/**
* 客户端取消订阅channel
*
* @param $connection
* @param $channel
*
* @return void
*/
public function unsubscribePublicChannel($connection, $channel) {
$app_key = $connection->appKey;
$this->_globalData[$app_key][$channel]['subscription_count']--;
if ($this->_globalData[$app_key][$channel]['subscription_count'] <= 0) {
unset($this->_globalData[$app_key][$channel]);
}
unset($connection->channels[$channel], $this->_eventClients[$connection->appKey][$channel][$connection->socketID]);
}
/**
* 客户端取消订阅channel
*
* @param $connection
* @param $channel
*
* @return void
*/
public function unsubscribePrivateChannel($connection, $channel) {
$this->unsubscribePublicChannel($connection, $channel);
}
/**
* 客户端取消订阅channel
*
* @param $connection
* @param $channel
*
* @return void
*/
public function unsubscribePresenceChannel($connection, $channel, $uid) {
$app_key = $connection->appKey;
$member_removed = false;
$this->_globalData[$app_key][$channel]['subscription_count']--;
if ($this->_globalData[$app_key][$channel]['subscription_count'] <= 0) {
unset($this->_globalData[$app_key][$channel]);
$member_removed = true;
} else {
if (!isset($this->_globalData[$app_key][$channel]['users'][$uid]['ref_count'])) {
error_log("\$this->_globalData[$app_key][$channel]['users'][$uid]['ref_count'] not exist\n");
return;
}
$this->_globalData[$app_key][$channel]['users'][$uid]['ref_count']--;
$ref_count = $this->_globalData[$app_key][$channel]['users'][$uid]['ref_count'];
if ($ref_count <= 0) {
unset($this->_globalData[$app_key][$channel]['users'][$uid]);
$member_removed = true;
}
}
if ($member_removed) {
// {"event":"pusher_internal:member_removed","data":"{\"user_id\":\"14884657801\"}","channel":"presence-channel"}
$this->publishToClients($app_key, $channel, 'pusher_internal:member_removed', json_encode(array('user_id'=>$uid), JSON_UNESCAPED_UNICODE));
}
unset($connection->channels[$channel], $this->_eventClients[$connection->appKey][$channel][$connection->socketID]);
}
/**
* 发布事件
*
* @param $data
*/
public function publishToClients($app_key, $channel, $event, $data, $socket_id = null)
{
if (!isset($this->_eventClients[$app_key][$channel])) {
return;
}
$data = json_encode(array(
'event' => $event,
'data' => $data,
'channel' => $channel
), JSON_UNESCAPED_UNICODE);
foreach ($this->_eventClients[$app_key][$channel] as $connection) {
if ($connection->socketID === $socket_id) {
continue;
}
$connection->clientNotSendPingCount = 0;
// {"event":"my-event","data":"{\"message\":\"hello world\"}","channel":"my-channel"}
$connection->send($data);
}
}
/**
* 检查心跳,将心跳超时的客户端关闭
*
* @return void
*/
public function checkHeartbeat()
{
foreach ($this->_allClients as $connection) {
if ($connection->clientNotSendPingCount > 1) {
$connection->destroy();
}
$connection->clientNotSendPingCount ++;
}
}
/**
* 创建一个全局的客户端id
*
* @param $connection
* @return string
*/
protected function createsocketID($connection)
{
$socket_id = "{$this->_globalID}.{$connection->id}";
return $socket_id;
}
/**
* 创建channel key用于监听分发给该channel的事件
*
* @param $app_key
* @param $channel
* @return string
*/
protected function createChannelKey($app_key, $channel)
{
return "$app_key:$channel";
}
/**
* POST /apps/1024/events?auth_key=b054014693241bcd9c26&auth_signature=ed7f5b604e6bbd21a888a861ed536a430a9d5e4df210937a241a811bd17fcf97&auth_timestamp=1487428415&auth_version=1.0&body_md5=15d251b35306a6da7efa515a0e971f80 HTTP/1.1
* {"name":"my-event","data":"{\"message\":\"hello world\"}","channels":["my-channel"]}
* {"name":"my-event","data":"{\"message\":\"haha\"}","channels":["my-channel"],"socket_id":"123.456"}
*
* GET /apps/1024/channels/my-channel?auth_key=b054014693241bcd9c26&auth_signature=5226650be00a064b417d50d49229e42bbb918e969c42e63aaa63b9d1c6cf9803&auth_timestamp=1489898340&auth_version=1.0
*
* GET /apps/1024/channels/presence-channel?auth_key=b054014693241bcd9c26&auth_signature=d46281bf69ccadfe9da270176c85daa88d4b9da55b1f3c2570d48fa1236f0b2c&auth_timestamp=1489903433&auth_version=1.0&info=subscription_count,user_count
*
* GET /apps/1024/channels/presence-channel/users?auth_key=b054014693241bcd9c26&auth_signature=2eee0ca6292e17b00484bdcb0bba686a47e8a7365a1b190248946182fc926309&auth_timestamp=1489904560&auth_version=1.0
*/
public function onApiClientMessage($connection, Request $request)
{
if (!($app_key = $request->get('auth_key'))) {
return $connection->send(new Response(400, [], 'Bad Request'));
}
if (!isset($this->appInfo[$app_key])) {
return $connection->send(new Response(401, [], 'Invalid app_key'));
}
$path = $request->path();
$explode = explode('/', trim($path, '/'));
if (count($explode) < 3) {
return $connection->send(new Response(400, [], 'Bad Request'));
}
$auth_signature = $request->get('auth_signature');
$params = $request->get();
unset($params['auth_signature']);
ksort($params);
$string_to_sign = $request->method()."\n" . $path . "\n" . self::array_implode('=', '&', $params);
$real_auth_signature = hash_hmac('sha256', $string_to_sign, $this->appInfo[$app_key]['app_secret'], false);
if ($auth_signature !== $real_auth_signature) {
return $connection->send(new Response(401, [], 'Invalid signature'));
}
$type = $explode[2];
switch ($type) {
case 'batch_events':
$packages = json_decode($request->rawBody(), true);
if (!$packages || !isset($packages['batch'])) {
return $connection->send(new Response(400, [], 'Bad request'));
}
$packages = $packages['batch'];
foreach ($packages as $package) {
$channel = $package['channel'];
$event = $package['name'];
$data = $package['data'];
$socket_id = isset($package['socket_id']) ? isset($package['socket_id']) : null;
$this->publishToClients($app_key, $channel, $event, $data, $socket_id);
}
return $connection->send('{}');
break;
case 'events':
$package = json_decode($request->rawBody(), true);
if (!$package) {
return $connection->send(new Response(401, [], 'Invalid signature'));
}
$channels = $package['channels'];
$event = $package['name'];
$data = $package['data'];
foreach ($channels as $channel) {
$socket_id = isset($package['socket_id']) ? isset($package['socket_id']) : null;
$this->publishToClients($app_key, $channel, $event, $data, $socket_id);
}
return $connection->send('{}');
case 'channels':
// info
$request_info = explode(',', $request->get('info', ''));
if (!isset($explode[3])) {
$channels = [];
$prefix = $request->get('filter_by_prefix');
$return_subscription_count = in_array('subscription_count', $request_info);
foreach ($this->_globalData[$app_key] ?? [] as $channel => $item) {
if ($prefix !== null) {
if (strpos($channel, $prefix) !== 0) {
continue;
}
}
$channels[$channel] = [];
if ($return_subscription_count) {
$channels[$channel]['subscription_count'] = $item['subscription_count'];
}
}
return $connection->send(json_encode(['channels' => $channels], JSON_UNESCAPED_UNICODE));
}
$channel = $explode[3];
// users
if (isset($explode[4])) {
if ($explode[4] !== 'users') {
return $connection->send(new Response(400, [], 'Bad Request'));
}
$id_array = isset($this->_globalData[$app_key][$channel]['users']) ?
array_keys($this->_globalData[$app_key][$channel]['users']) : array();
$user_id_array = array();
foreach ($id_array as $id) {
$user_id_array[] = array('id' => $id);
}
$connection->send(json_encode($user_id_array, JSON_UNESCAPED_UNICODE));
}
$occupied = isset($this->_globalData[$app_key][$channel]);
$user_count = isset($this->_globalData[$app_key][$channel]['users']) ? count($this->_globalData[$app_key][$channel]['users']) : 0;
$subscription_count = $occupied ? $this->_globalData[$app_key][$channel]['subscription_count'] : 0;
$channel_info = array(
'occupied' => $occupied
);
foreach ($request_info as $name) {
switch ($name) {
case 'user_count':
$channel_info['user_count'] = $user_count;
break;
case 'subscription_count':
$channel_info['subscription_count'] = $subscription_count;
break;
}
}
$connection->send(json_encode($channel_info, JSON_UNESCAPED_UNICODE));
break;
default:
return $connection->send(new Response(400, [], 'Bad Request'));
}
}
public function webHookCheck()
{
$channel_events = [];
$user_events = [];
$all_app_keys = array_unique(array_merge(array_keys($this->_globalData), array_keys($this->_globalDataSnapshot)));
foreach($all_app_keys as $app_key)
{
if (empty($this->appInfo[$app_key])) {
continue;
}
$snapshot_items = isset($this->_globalDataSnapshot[$app_key]) ? $this->_globalDataSnapshot[$app_key] : [];
$items = isset($this->_globalData[$app_key]) ? $this->_globalData[$app_key] : [];
$channels_added = array_diff_key($items, $snapshot_items);
$channels_removed = array_diff_key($snapshot_items, $items);
if ($channels_added) {
$channel_events[$app_key]['channels_added'] = array_keys($channels_added);
}
if ($channels_removed) {
$channel_events[$app_key]['channels_removed'] = array_keys($channels_removed);
}
$all_channels = [];
foreach ($items as $channel => $foo) {
if ($foo['type'] === 'presence') {
$all_channels[$channel] = $channel;
}
}
foreach ($snapshot_items as $channel => $foo) {
if ($foo['type'] === 'presence' && !isset($all_channels[$channel])) {
$all_channels[$channel] = $channel;
}
}
foreach ($all_channels as $channel) {
$user_array_snapshot = isset($snapshot_items[$channel]['users']) ? $snapshot_items[$channel]['users'] : [];
$user_array = isset($items[$channel]['users']) ? $items[$channel]['users'] : [];
$user_added = array_diff_key($user_array, $user_array_snapshot);
$user_removed = array_diff_key($user_array_snapshot, $user_array);
if ($user_added) {
$user_events[$app_key][$channel]['user_added'] = array_keys($user_added);
}
if ($user_removed) {
$user_events[$app_key][$channel]['user_removed'] = array_keys($user_removed);
}
}
}
$this->_globalDataSnapshot = $this->_globalData;
$this->webHookSend(array('channel_events' => $channel_events, 'user_events' => $user_events));
}
protected function webHookSend($data)
{
$channel_events = $data['channel_events'];
$user_events = $data['user_events'];
$time_ms = microtime(true);
foreach ($user_events as $app_key => $items) {
// 没设置user_event回调则忽略
if (empty($this->appInfo[$app_key]['user_hook'])) {
continue;
}
// {"time_ms":1494300453609,"events":[{"channel":"presence-channel2","user_id":"59094971a","name":"member_added"}]}
$http_events_body = array(
'time_ms' => $time_ms,
'events' => []
);
foreach ($items as $channel => $item) {
if (isset($item['user_added'])) {
foreach ($item['user_added'] as $user_id) {
$http_events_body['events'][] = array(
'channel' => $channel,
'user_id' => $user_id,
'name' => 'user_added'
);
}
}
}
foreach ($items as $channel => $item) {
if (isset($item['user_removed'])) {
foreach ($item['user_removed'] as $user_id) {
$http_events_body['events'][] = array(
'channel' => $channel,
'user_id' => $user_id,
'name' => 'user_removed'
);
}
}
}
if ($http_events_body['events']) {
$this->sendHttpRequest($this->appInfo[$app_key]['user_hook'],
$app_key,
$this->appInfo[$app_key]['app_secret'],
json_encode($http_events_body, JSON_UNESCAPED_UNICODE));
}
}
foreach ($channel_events as $app_key => $item) {
// 没设置channel_event回调则忽略
if (empty($this->appInfo[$app_key]['channel_hook'])) {
continue;
}
// {"time_ms":1494300446592,"events":[{"channel":"presence-channel2","name":"channel_added"}]}
$http_events_body = array(
'time_ms' => $time_ms,
'events' => []
);
if (isset($item['channels_added'])) {
foreach ($item['channels_added'] as $channel) {
$http_events_body['events'][] = array(
'channel' => $channel,
'name' => 'channel_added'
);
}
}
if (isset($item['channels_removed'])) {
foreach ($item['channels_removed'] as $channel) {
$http_events_body['events'][] = array(
'channel' => $channel,
'name' => 'channel_removed'
);
}
}
if ($http_events_body['events']) {
$this->sendHttpRequest($this->appInfo[$app_key]['channel_hook'],
$app_key,
$this->appInfo[$app_key]['app_secret'],
json_encode($http_events_body, JSON_UNESCAPED_UNICODE));
}
}
}
protected function sendHttpRequest($address, $app_key, $secret, $body, $redirect_count = 0)
{
$address_info = parse_url($address);
if (!$address_info) {
echo new \Exception('bad remote_address');
return false;
}
$scheme = isset($address_info['scheme']) && $address_info['scheme'] === 'https' ? 'ssl' : 'tcp';
if (!isset($address_info['port'])) {
$address_info['port'] = $scheme == 'ssl' ? 443 : 80;
}
if (!isset($address_info['path'])) {
$address_info['path'] = '/';
}
if (!isset($address_info['query'])) {
$address_info['query'] = '';
} else {
$address_info['query'] = '?' . $address_info['query'];
}
$remote_address = "{$address_info['host']}:{$address_info['port']}";
$remote_host = $address_info['host'];
$remote_URI = "{$address_info['path']}{$address_info['query']}";
$signature = hash_hmac('sha256', $body, $secret, false);
$base_url = $scheme == 'ssl' ? "https://$remote_address/" : "http://$remote_address/";
$header = "POST $remote_URI HTTP/1.0\r\n";
$header .= "Host: $remote_host\r\n";
$header .= "Connection: close\r\n";
$header .= "X-Pusher-Key: $app_key\r\n";
$header .= "X-Pusher-Signature: $signature\r\n";
$header .= "Content-Type: application/json\r\n";
$header .= "Content-Length: " . strlen($body);
$http_buffer = $header . "\r\n\r\n" . $body;
$client = new AsyncTcpConnection('tcp://' . $remote_address);
if ($scheme == 'ssl') {
$client->transport = 'ssl';
}
$client->onConnect = function ($client) use ($http_buffer) {
$client->send($http_buffer);
};
$client->onMessage = function ($client, $buffer) use ($address, $app_key, $secret, $body, $base_url, $redirect_count) {
$client->close();
if (!preg_match("/HTTP\/1\.\d (\d*?) .*?\r\n/", $buffer, $match)) {
echo "http code not found $buffer\n";
return;
}
$http_code = $match[1];
$base_code = intval($http_code / 100);
if ($base_code == 3 && preg_match("/Location: (.*?)\r\n/", $buffer, $match)) {
if (++$redirect_count > 3) {
$msg = date('Y-m-d H:i:s') . "\nURL:$address\nAPP_KEY:$app_key ERR:too many redirect\n$buffer";
echo $msg;
return;
}
$location = $match[1];
if (strpos($location, 'http://') === 0 || strpos($location, 'https://') === 0) {
$this->sendHttpRequest($location, $app_key, $secret, $body, $redirect_count);
} else {
$this->sendHttpRequest($base_url . $location, $app_key, $secret, $body, $redirect_count);
}
}
if ($base_code !== 2) {
$msg = date('Y-m-d H:i:s') . "\nURL:$address\nAPP_KEY:$app_key\n$buffer";
echo $msg;
}
};
Timer::add(10, array($client, 'close'), null, false);
$client->connect();
}
/**
* array_implode
*
* @param $glue
* @param $separator
* @param $array
* @return string
*/
public static function array_implode($glue, $separator, $array)
{
if (!is_array($array)) {
return $array;
}
$string = array();
foreach ($array as $key => $val) {
if (is_array($val)) {
$val = implode(',', $val);
}
$string[] = "{$key}{$glue}{$val}";
}
return implode($separator, $string);
}
}

View File

@@ -1,10 +0,0 @@
<?php
return [
'enable' => true,
'websocket' => 'websocket://0.0.0.0:3131',
'api' => 'http://0.0.0.0:3232',
'app_key' => '6c07d4502768615a45d8c3193b66eb88',
'app_secret' => 'c395efb291683dc01472deb4e9db4bcd',
'channel_hook' => 'http://127.0.0.1:8787/plugin/webman/push/hook',
'auth' => '/plugin/webman/push/auth'
];

View File

@@ -1,21 +0,0 @@
<?php
use Webman\Push\Server;
return [
'server' => [
'handler' => Server::class,
'listen' => config('plugin.webman.push.app.websocket'),
'count' => 1, // 必须是1
'reloadable' => false, // 执行reload不重启
'constructor' => [
'api_listen' => config('plugin.webman.push.app.api'),
'app_info' => [
config('plugin.webman.push.app.app_key') => [
'channel_hook' => config('plugin.webman.push.app.channel_hook'),
'app_secret' => config('plugin.webman.push.app.app_secret'),
],
]
]
]
];

View File

@@ -1,87 +0,0 @@
<?php
/**
* This file is part of webman.
*
* 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
*/
use support\Request;
use Webman\Route;
use Webman\Push\Api;
/**
* 推送js客户端文件
*/
Route::get('/plugin/webman/push/push.js', function (Request $request) {
return response()->file(base_path().'/vendor/webman/push/src/push.js');
});
/**
* 私有频道鉴权这里应该使用session辨别当前用户身份然后确定该用户是否有权限监听channel_name
*/
Route::post(config('plugin.webman.push.app.auth'), function (Request $request) {
$pusher = new Api(str_replace('0.0.0.0', '127.0.0.1', config('plugin.webman.push.app.api')), config('plugin.webman.push.app.app_key'), config('plugin.webman.push.app.app_secret'));
$channel_name = $request->post('channel_name');
$session = $request->session();
// 这里应该通过session和channel_name判断当前用户是否有权限监听channel_name
$has_authority = true;
if ($has_authority) {
return response($pusher->socketAuth($channel_name, $request->post('socket_id')));
} else {
return response('Forbidden', 403);
}
});
/**
* 当频道上线以及下线时触发的回调
* 频道上线:是指某个频道从没有连接在线到有连接在线的事件
* 频道下线:是指某个频道的所有连接都断开触发的事件
*/
Route::post(parse_url(config('plugin.webman.push.app.channel_hook'), PHP_URL_PATH), function (Request $request) {
// 没有x-pusher-signature头视为伪造请求
if (!$webhook_signature = $request->header('x-pusher-signature')) {
return response('401 Not authenticated', 401);
}
$body = $request->rawBody();
// 计算签名,$app_secret 是双方使用的密钥,是保密的,外部无从得知
$expected_signature = hash_hmac('sha256', $body, config('plugin.webman.push.app.app_secret'), false);
// 安全校验如果签名不一致可能是伪造的请求返回401状态码
if ($webhook_signature !== $expected_signature) {
return response('401 Not authenticated', 401);
}
// 这里存储这上线 下线的channel数据
$payload = json_decode($body, true);
$channels_online = $channels_offline = [];
foreach ($payload['events'] as $event) {
if ($event['name'] === 'channel_added') {
$channels_online[] = $event['channel'];
} else if ($event['name'] === 'channel_removed') {
$channels_offline[] = $event['channel'];
}
}
// 业务根据需要处理上下线的channel例如将在线状态写入数据库通知其它channel等
// 上线的所有channel
echo 'online channels: ' . implode(',', $channels_online) . "\n";
// 下线的所有channel
echo 'offline channels: ' . implode(',', $channels_offline) . "\n";
return 'OK';
});

View File

@@ -1,824 +0,0 @@
function Push(options) {
this.doNotConnect = 0;
options = options || {};
options.heartbeat = options.heartbeat || 25000;
options.pingTimeout = options.pingTimeout || 10000;
this.config = options;
this.uid = 0;
this.channels = {};
this.connection = null;
this.pingTimeoutTimer = 0;
Push.instances.push(this);
this.createConnection();
}
Push.prototype.checkoutPing = function() {
var _this = this;
setTimeout(function () {
if (_this.connection.state === 'connected') {
_this.connection.send('{"event":"pusher:ping","data":{}}');
if (_this.pingTimeoutTimer) {
clearTimeout(_this.pingTimeoutTimer);
_this.pingTimeoutTimer = 0;
}
_this.pingTimeoutTimer = setTimeout(function () {
_this.connection.closeAndClean();
if (!_this.connection.doNotConnect) {
_this.connection.waitReconnect();
}
}, _this.config.pingTimeout);
}
}, this.config.heartbeat);
};
Push.prototype.channel = function (name) {
return this.channels.find(name);
};
Push.prototype.allChannels = function () {
return this.channels.all();
};
Push.prototype.createConnection = function () {
if (this.connection) {
throw Error('Connection already exist');
}
var _this = this;
var url = this.config.url;
function updateSubscribed () {
for (var i in _this.channels) {
_this.channels[i].subscribed = false;
}
}
this.connection = new Connection({
url: url,
app_key: this.config.app_key,
onOpen: function () {
_this.connection.state = 'connecting';
_this.checkoutPing();
},
onMessage: function(params) {
if(_this.pingTimeoutTimer) {
clearTimeout(_this.pingTimeoutTimer);
_this.pingTimeoutTimer = 0;
}
params = JSON.parse(params.data);
var event = params.event;
var channel_name = params.channel;
if (event === 'pusher:pong') {
_this.checkoutPing();
return;
}
if (event === 'pusher:error') {
throw Error(params.data.message);
}
var data = JSON.parse(params.data), channel;
if (event === 'pusher_internal:subscription_succeeded') {
channel = _this.channels[channel_name];
channel.subscribed = true;
channel.processQueue();
channel.emit('pusher:subscription_succeeded');
return;
}
if (event === 'pusher:connection_established') {
_this.connection.socket_id = data.socket_id;
_this.connection.state = 'connected';
_this.subscribeAll();
}
if (event.indexOf('pusher_internal') !== -1) {
console.log("Event '"+event+"' not implement");
return;
}
channel = _this.channels[channel_name];
if (channel) {
channel.emit(event, data);
}
},
onClose: function () {
updateSubscribed();
},
onError: function () {
updateSubscribed();
}
});
};
Push.prototype.disconnect = function () {
this.connection.doNotConnect = 1;
this.connection.close();
};
Push.prototype.subscribeAll = function () {
if (this.connection.state !== 'connected') {
return;
}
for (var channel_name in this.channels) {
//this.connection.send(JSON.stringify({event:"pusher:subscribe", data:{channel:channel_name}}));
this.channels[channel_name].processSubscribe();
}
};
Push.prototype.unsubscribe = function (channel_name) {
if (this.channels[channel_name]) {
delete this.channels[channel_name];
if (this.connection.state === 'connected') {
this.connection.send(JSON.stringify({event:"pusher:unsubscribe", data:{channel:channel_name}}));
}
}
};
Push.prototype.unsubscribeAll = function () {
var channels = Object.keys(this.channels);
if (channels.length) {
if (this.connection.state === 'connected') {
for (var channel_name in this.channels) {
this.unsubscribe(channel_name);
}
}
}
this.channels = {};
};
Push.prototype.subscribe = function (channel_name) {
if (this.channels[channel_name]) {
return this.channels[channel_name];
}
if (channel_name.indexOf('private-') === 0) {
return createPrivateChannel(channel_name, this);
}
if (channel_name.indexOf('presence-') === 0) {
return createPresenceChannel(channel_name, this);
}
return createChannel(channel_name, this);
};
Push.instances = [];
function createChannel(channel_name, push)
{
var channel = new Channel(push.connection, channel_name);
push.channels[channel_name] = channel;
channel.subscribeCb = function () {
push.connection.send(JSON.stringify({event:"pusher:subscribe", data:{channel:channel_name}}));
}
return channel;
}
function createPrivateChannel(channel_name, push)
{
var channel = new Channel(push.connection, channel_name);
push.channels[channel_name] = channel;
channel.subscribeCb = function () {
__ajax({
url: push.config.auth,
type: 'POST',
data: {channel_name: channel_name, socket_id: push.connection.socket_id},
success: function (data) {
data = JSON.parse(data);
data.channel = channel_name;
push.connection.send(JSON.stringify({event:"pusher:subscribe", data:data}));
},
error: function (e) {
throw Error(e);
}
});
};
channel.processSubscribe();
return channel;
}
function createPresenceChannel(channel_name, push)
{
return createPrivateChannel(channel_name, push);
}
/*window.addEventListener('online', function(){
var con;
for (var i in Push.instances) {
con = Push.instances[i].connection;
con.reconnectInterval = 1;
if (con.state === 'connecting') {
con.connect();
}
}
});*/
function Connection(options) {
this.dispatcher = new Dispatcher();
__extends(this, this.dispatcher);
var properies = ['on', 'off', 'emit'];
for (var i in properies) {
this[properies[i]] = this.dispatcher[properies[i]];
}
this.options = options;
this.state = 'initialized'; //initialized connecting connected disconnected
this.doNotConnect = 0;
this.reconnectInterval = 1;
this.connection = null;
this.reconnectTimer = 0;
this.connect();
}
Connection.prototype.updateNetworkState = function(state){
var old_state = this.state;
this.state = state;
if (old_state !== state) {
this.emit('state_change', { previous: old_state, current: state });
}
};
Connection.prototype.connect = function () {
this.doNotConnect = 0;
if (this.networkState == 'connecting' || this.networkState == 'established') {
console.log('networkState is ' + this.networkState + ' and do not need connect');
return;
}
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = 0;
}
this.closeAndClean();
var options = this.options;
var _this = this;
_this.updateNetworkState('connecting');
var cb = function(){
uni.onSocketOpen(function (res) {
_this.reconnectInterval = 1;
if (_this.doNotConnect) {
_this.updateNetworkState('closing');
uni.closeSocket();
return;
}
_this.updateNetworkState('established');
if (options.onOpen) {
options.onOpen(res);
}
});
if (options.onMessage) {
uni.onSocketMessage(options.onMessage);
}
uni.onSocketClose(function (res) {
_this.updateNetworkState('disconnected');
if (!_this.doNotConnect) {
_this.waitReconnect();
}
if (options.onClose) {
options.onClose(res);
}
});
uni.onSocketError(function (res) {
_this.close();
if (!_this.doNotConnect) {
_this.waitReconnect();
}
if (options.onError) {
options.onError(res);
}
});
};
uni.connectSocket({
url: options.url,
fail: function (res) {
console.log('uni.connectSocket fail');
console.log(res);
_this.updateNetworkState('disconnected');
_this.waitReconnect();
},
success: function() {
}
});
cb();
}
Connection.prototype.connect = function () {
this.doNotConnect = 0;
if (this.state === 'connected') {
console.log('networkState is "' + this.state + '" and do not need connect');
return;
}
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = 0;
}
this.closeAndClean();
var options = this.options;
this.updateNetworkState('connecting');
var _this = this;
var cb = function(){
uni.onSocketOpen(function (res) {
_this.reconnectInterval = 1;
if (_this.doNotConnect) {
_this.updateNetworkState('disconnected');
uni.closeSocket();
return;
}
if (options.onOpen) {
options.onOpen(res);
}
});
if (options.onMessage) {
uni.onSocketMessage(options.onMessage);
}
uni.onSocketClose(function (res) {
_this.updateNetworkState('disconnected');
if (!_this.doNotConnect) {
_this.waitReconnect();
}
if (options.onClose) {
options.onClose(res);
}
});
uni.onSocketError(function (res) {
_this.close();
if (!_this.doNotConnect) {
_this.waitReconnect();
}
if (options.onError) {
options.onError(res);
}
});
};
uni.connectSocket({
url: options.url+'/app/'+options.app_key,
fail: function (res) {
console.log('uni.connectSocket fail');
console.log(res);
_this.updateNetworkState('disconnected');
_this.waitReconnect();
},
success: function() {
}
});
cb();
}
Connection.prototype.closeAndClean = function () {
if (this.state === 'connected') {
uni.closeSocket();
}
this.updateNetworkState('disconnected');
};
Connection.prototype.waitReconnect = function () {
if (this.state === 'connected' || this.state === 'connecting') {
return;
}
if (!this.doNotConnect) {
this.updateNetworkState('connecting');
var _this = this;
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
this.reconnectTimer = setTimeout(function(){
_this.connect();
}, this.reconnectInterval);
if (this.reconnectInterval < 1000) {
this.reconnectInterval = 1000;
} else {
// 每次重连间隔增大一倍
this.reconnectInterval = this.reconnectInterval * 2;
}
// 有网络的状态下重连间隔最大2秒
if (this.reconnectInterval > 2000 && navigator.onLine) {
_this.reconnectInterval = 2000;
}
}
}
Connection.prototype.send = function(data) {
if (this.state !== 'connected') {
console.trace('networkState is "' + this.state + '", can not send ' + data);
return;
}
uni.sendSocketMessage({
data: data
});
}
Connection.prototype.close = function(){
this.updateNetworkState('disconnected');
uni.closeSocket();
}
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) {d[p] = b[p];}
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
function Channel(connection, channel_name) {
this.subscribed = false;
this.dispatcher = new Dispatcher();
this.connection = connection;
this.channelName = channel_name;
this.subscribeCb = null;
this.queue = [];
__extends(this, this.dispatcher);
var properies = ['on', 'off', 'emit'];
for (var i in properies) {
this[properies[i]] = this.dispatcher[properies[i]];
}
}
Channel.prototype.processSubscribe = function () {
if (this.connection.state !== 'connected') {
return;
}
this.subscribeCb();
};
Channel.prototype.processQueue = function () {
if (this.connection.state !== 'connected' || !this.subscribed) {
return;
}
for (var i in this.queue) {
this.queue[i]();
}
this.queue = [];
};
Channel.prototype.trigger = function (event, data) {
if (event.indexOf('client-') !== 0) {
throw new Error("Event '" + event + "' should start with 'client-'");
}
var _this = this;
this.queue.push(function () {
_this.connection.send(JSON.stringify({ event: event, data: data, channel: _this.channelName }));
});
this.processQueue();
};
////////////////
var Collections = (function () {
var exports = {};
function extend(target) {
var sources = [];
for (var _i = 1; _i < arguments.length; _i++) {
sources[_i - 1] = arguments[_i];
}
for (var i = 0; i < sources.length; i++) {
var extensions = sources[i];
for (var property in extensions) {
if (extensions[property] && extensions[property].constructor &&
extensions[property].constructor === Object) {
target[property] = extend(target[property] || {}, extensions[property]);
}
else {
target[property] = extensions[property];
}
}
}
return target;
}
exports.extend = extend;
function stringify() {
var m = ["Push"];
for (var i = 0; i < arguments.length; i++) {
if (typeof arguments[i] === "string") {
m.push(arguments[i]);
}
else {
m.push(safeJSONStringify(arguments[i]));
}
}
return m.join(" : ");
}
exports.stringify = stringify;
function arrayIndexOf(array, item) {
var nativeIndexOf = Array.prototype.indexOf;
if (array === null) {
return -1;
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) {
return array.indexOf(item);
}
for (var i = 0, l = array.length; i < l; i++) {
if (array[i] === item) {
return i;
}
}
return -1;
}
exports.arrayIndexOf = arrayIndexOf;
function objectApply(object, f) {
for (var key in object) {
if (Object.prototype.hasOwnProperty.call(object, key)) {
f(object[key], key, object);
}
}
}
exports.objectApply = objectApply;
function keys(object) {
var keys = [];
objectApply(object, function (_, key) {
keys.push(key);
});
return keys;
}
exports.keys = keys;
function values(object) {
var values = [];
objectApply(object, function (value) {
values.push(value);
});
return values;
}
exports.values = values;
function apply(array, f, context) {
for (var i = 0; i < array.length; i++) {
f.call(context || (window), array[i], i, array);
}
}
exports.apply = apply;
function map(array, f) {
var result = [];
for (var i = 0; i < array.length; i++) {
result.push(f(array[i], i, array, result));
}
return result;
}
exports.map = map;
function mapObject(object, f) {
var result = {};
objectApply(object, function (value, key) {
result[key] = f(value);
});
return result;
}
exports.mapObject = mapObject;
function filter(array, test) {
test = test || function (value) {
return !!value;
};
var result = [];
for (var i = 0; i < array.length; i++) {
if (test(array[i], i, array, result)) {
result.push(array[i]);
}
}
return result;
}
exports.filter = filter;
function filterObject(object, test) {
var result = {};
objectApply(object, function (value, key) {
if ((test && test(value, key, object, result)) || Boolean(value)) {
result[key] = value;
}
});
return result;
}
exports.filterObject = filterObject;
function flatten(object) {
var result = [];
objectApply(object, function (value, key) {
result.push([key, value]);
});
return result;
}
exports.flatten = flatten;
function any(array, test) {
for (var i = 0; i < array.length; i++) {
if (test(array[i], i, array)) {
return true;
}
}
return false;
}
exports.any = any;
function all(array, test) {
for (var i = 0; i < array.length; i++) {
if (!test(array[i], i, array)) {
return false;
}
}
return true;
}
exports.all = all;
function encodeParamsObject(data) {
return mapObject(data, function (value) {
if (typeof value === "object") {
value = safeJSONStringify(value);
}
return encodeURIComponent(base64_1["default"](value.toString()));
});
}
exports.encodeParamsObject = encodeParamsObject;
function buildQueryString(data) {
var params = filterObject(data, function (value) {
return value !== undefined;
});
return map(flatten(encodeParamsObject(params)), util_1["default"].method("join", "=")).join("&");
}
exports.buildQueryString = buildQueryString;
function decycleObject(object) {
var objects = [], paths = [];
return (function derez(value, path) {
var i, name, nu;
switch (typeof value) {
case 'object':
if (!value) {
return null;
}
for (i = 0; i < objects.length; i += 1) {
if (objects[i] === value) {
return {$ref: paths[i]};
}
}
objects.push(value);
paths.push(path);
if (Object.prototype.toString.apply(value) === '[object Array]') {
nu = [];
for (i = 0; i < value.length; i += 1) {
nu[i] = derez(value[i], path + '[' + i + ']');
}
}
else {
nu = {};
for (name in value) {
if (Object.prototype.hasOwnProperty.call(value, name)) {
nu[name] = derez(value[name], path + '[' + JSON.stringify(name) + ']');
}
}
}
return nu;
case 'number':
case 'string':
case 'boolean':
return value;
}
}(object, '$'));
}
exports.decycleObject = decycleObject;
function safeJSONStringify(source) {
try {
return JSON.stringify(source);
}
catch (e) {
return JSON.stringify(decycleObject(source));
}
}
exports.safeJSONStringify = safeJSONStringify;
return exports;
})();
var Dispatcher = (function () {
function Dispatcher(failThrough) {
this.callbacks = new CallbackRegistry();
this.global_callbacks = [];
this.failThrough = failThrough;
}
Dispatcher.prototype.on = function (eventName, callback, context) {
this.callbacks.add(eventName, callback, context);
return this;
};
Dispatcher.prototype.on_global = function (callback) {
this.global_callbacks.push(callback);
return this;
};
Dispatcher.prototype.off = function (eventName, callback, context) {
this.callbacks.remove(eventName, callback, context);
return this;
};
Dispatcher.prototype.emit = function (eventName, data) {
var i;
for (i = 0; i < this.global_callbacks.length; i++) {
this.global_callbacks[i](eventName, data);
}
var callbacks = this.callbacks.get(eventName);
if (callbacks && callbacks.length > 0) {
for (i = 0; i < callbacks.length; i++) {
callbacks[i].fn.call(callbacks[i].context || (window), data);
}
}
else if (this.failThrough) {
this.failThrough(eventName, data);
}
return this;
};
return Dispatcher;
}());
var CallbackRegistry = (function () {
function CallbackRegistry() {
this._callbacks = {};
}
CallbackRegistry.prototype.get = function (name) {
return this._callbacks[prefix(name)];
};
CallbackRegistry.prototype.add = function (name, callback, context) {
var prefixedEventName = prefix(name);
this._callbacks[prefixedEventName] = this._callbacks[prefixedEventName] || [];
this._callbacks[prefixedEventName].push({
fn: callback,
context: context
});
};
CallbackRegistry.prototype.remove = function (name, callback, context) {
if (!name && !callback && !context) {
this._callbacks = {};
return;
}
var names = name ? [prefix(name)] : Collections.keys(this._callbacks);
if (callback || context) {
this.removeCallback(names, callback, context);
}
else {
this.removeAllCallbacks(names);
}
};
CallbackRegistry.prototype.removeCallback = function (names, callback, context) {
Collections.apply(names, function (name) {
this._callbacks[name] = Collections.filter(this._callbacks[name] || [], function (oning) {
return (callback && callback !== oning.fn) ||
(context && context !== oning.context);
});
if (this._callbacks[name].length === 0) {
delete this._callbacks[name];
}
}, this);
};
CallbackRegistry.prototype.removeAllCallbacks = function (names) {
Collections.apply(names, function (name) {
delete this._callbacks[name];
}, this);
};
return CallbackRegistry;
}());
function prefix(name) {
return "_" + name;
}
function __ajax(options){
options=options||{};
options.type=(options.type||'GET').toUpperCase();
options.dataType=options.dataType||'json';
params=formatParams(options.data);
var xhr;
if(window.XMLHttpRequest){
xhr=new XMLHttpRequest();
}else{
xhr=ActiveXObject('Microsoft.XMLHTTP');
}
xhr.onreadystatechange=function(){
if(xhr.readyState === 4){
var status=xhr.status;
if(status>=200 && status<300){
options.success&&options.success(xhr.responseText,xhr.responseXML);
}else{
options.error&&options.error(status);
}
}
}
if(options.type==='GET'){
xhr.open('GET',options.url+'?'+params,true);
xhr.send(null);
}else if(options.type==='POST'){
xhr.open('POST',options.url,true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(params);
}
}
function formatParams(data){
var arr=[];
for(var name in data){
arr.push(encodeURIComponent(name)+'='+encodeURIComponent(data[name]));
}
return arr.join('&');
}
export default Push

View File

@@ -1,743 +0,0 @@
function Push(options) {
this.doNotConnect = 0;
options = options || {};
options.heartbeat = options.heartbeat || 25000;
options.pingTimeout = options.pingTimeout || 10000;
this.config = options;
this.uid = 0;
this.channels = {};
this.connection = null;
this.pingTimeoutTimer = 0;
Push.instances.push(this);
this.createConnection();
}
Push.prototype.checkoutPing = function() {
var _this = this;
setTimeout(function () {
if (_this.connection.state === 'connected') {
_this.connection.send('{"event":"pusher:ping","data":{}}');
if (_this.pingTimeoutTimer) {
clearTimeout(_this.pingTimeoutTimer);
_this.pingTimeoutTimer = 0;
}
_this.pingTimeoutTimer = setTimeout(function () {
_this.connection.closeAndClean();
if (!_this.connection.doNotConnect) {
_this.connection.waitReconnect();
}
}, _this.config.pingTimeout);
}
}, this.config.heartbeat);
};
Push.prototype.channel = function (name) {
return this.channels.find(name);
};
Push.prototype.allChannels = function () {
return this.channels.all();
};
Push.prototype.createConnection = function () {
if (this.connection) {
throw Error('Connection already exist');
}
var _this = this;
var url = this.config.url;
function updateSubscribed () {
for (var i in _this.channels) {
_this.channels[i].subscribed = false;
}
}
this.connection = new Connection({
url: url,
app_key: this.config.app_key,
onOpen: function () {
_this.connection.state ='connecting';
_this.checkoutPing();
},
onMessage: function(params) {
if(_this.pingTimeoutTimer) {
clearTimeout(_this.pingTimeoutTimer);
_this.pingTimeoutTimer = 0;
}
params = JSON.parse(params.data);
var event = params.event;
var channel_name = params.channel;
if (event === 'pusher:pong') {
_this.checkoutPing();
return;
}
if (event === 'pusher:error') {
throw Error(params.data.message);
}
var data = JSON.parse(params.data), channel;
if (event === 'pusher_internal:subscription_succeeded') {
channel = _this.channels[channel_name];
channel.subscribed = true;
channel.processQueue();
channel.emit('pusher:subscription_succeeded');
return;
}
if (event === 'pusher:connection_established') {
_this.connection.socket_id = data.socket_id;
_this.connection.state = 'connected';
_this.subscribeAll();
}
if (event.indexOf('pusher_internal') !== -1) {
console.log("Event '"+event+"' not implement");
return;
}
channel = _this.channels[channel_name];
if (channel) {
channel.emit(event, data);
}
},
onClose: function () {
updateSubscribed();
},
onError: function () {
updateSubscribed();
}
});
};
Push.prototype.disconnect = function () {
this.connection.doNotConnect = 1;
this.connection.close();
};
Push.prototype.subscribeAll = function () {
if (this.connection.state !== 'connected') {
return;
}
for (var channel_name in this.channels) {
//this.connection.send(JSON.stringify({event:"pusher:subscribe", data:{channel:channel_name}}));
this.channels[channel_name].processSubscribe();
}
};
Push.prototype.unsubscribe = function (channel_name) {
if (this.channels[channel_name]) {
delete this.channels[channel_name];
if (this.connection.state === 'connected') {
this.connection.send(JSON.stringify({event:"pusher:unsubscribe", data:{channel:channel_name}}));
}
}
};
Push.prototype.unsubscribeAll = function () {
var channels = Object.keys(this.channels);
if (channels.length) {
if (this.connection.state === 'connected') {
for (var channel_name in this.channels) {
this.unsubscribe(channel_name);
}
}
}
this.channels = {};
};
Push.prototype.subscribe = function (channel_name) {
if (this.channels[channel_name]) {
return this.channels[channel_name];
}
if (channel_name.indexOf('private-') === 0) {
return createPrivateChannel(channel_name, this);
}
if (channel_name.indexOf('presence-') === 0) {
return createPresenceChannel(channel_name, this);
}
return createChannel(channel_name, this);
};
Push.instances = [];
function createChannel(channel_name, push)
{
var channel = new Channel(push.connection, channel_name);
push.channels[channel_name] = channel;
channel.subscribeCb = function () {
push.connection.send(JSON.stringify({event:"pusher:subscribe", data:{channel:channel_name}}));
}
return channel;
}
function createPrivateChannel(channel_name, push)
{
var channel = new Channel(push.connection, channel_name);
push.channels[channel_name] = channel;
channel.subscribeCb = function () {
__ajax({
url: push.config.auth,
type: 'POST',
data: {channel_name: channel_name, socket_id: push.connection.socket_id},
success: function (data) {
data = JSON.parse(data);
data.channel = channel_name;
push.connection.send(JSON.stringify({event:"pusher:subscribe", data:data}));
},
error: function (e) {
throw Error(e);
}
});
};
channel.processSubscribe();
return channel;
}
function createPresenceChannel(channel_name, push)
{
return createPrivateChannel(channel_name, push);
}
/*window.addEventListener('online', function(){
var con;
for (var i in Push.instances) {
con = Push.instances[i].connection;
con.reconnectInterval = 1;
if (con.state === 'connecting') {
con.connect();
}
}
});*/
function Connection(options) {
this.dispatcher = new Dispatcher();
__extends(this, this.dispatcher);
var properies = ['on', 'off', 'emit'];
for (var i in properies) {
this[properies[i]] = this.dispatcher[properies[i]];
}
this.options = options;
this.state = 'initialized'; //initialized connecting connected disconnected
this.doNotConnect = 0;
this.reconnectInterval = 1;
this.connection = null;
this.reconnectTimer = 0;
this.connect();
}
Connection.prototype.updateNetworkState = function(state){
var old_state = this.state;
this.state = state;
if (old_state !== state) {
this.emit('state_change', { previous: old_state, current: state });
}
};
Connection.prototype.connect = function () {
this.doNotConnect = 0;
if (this.state === 'connected') {
console.log('networkState is "' + this.state + '" and do not need connect');
return;
}
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
this.reconnectTimer = 0;
}
this.closeAndClean();
var options = this.options;
var websocket = new WebSocket(options.url+'/app/'+options.app_key);
this.updateNetworkState('connecting');
var _this = this;
websocket.onopen = function (res) {
_this.reconnectInterval = 1;
if (_this.doNotConnect) {
_this.updateNetworkState('disconnected');
websocket.close();
return;
}
if (options.onOpen) {
options.onOpen(res);
}
};
if (options.onMessage) {
websocket.onmessage = options.onMessage;
}
websocket.onclose = function (res) {
websocket.onmessage = websocket.onopen = websocket.onclose = websocket.onerror = null;
_this.updateNetworkState('disconnected');
if (!_this.doNotConnect) {
_this.waitReconnect();
}
if (options.onClose) {
options.onClose(res);
}
};
websocket.onerror = function (res) {
_this.close();
if (!_this.doNotConnect) {
_this.waitReconnect();
}
if (options.onError) {
options.onError(res);
}
};
this.connection = websocket;
}
Connection.prototype.closeAndClean = function () {
if(this.connection) {
var websocket = this.connection;
websocket.onmessage = websocket.onopen = websocket.onclose = websocket.onerror = null;
try {
websocket.close();
} catch (e) {}
this.updateNetworkState('disconnected');
}
};
Connection.prototype.waitReconnect = function () {
if (this.state === 'connected' || this.state === 'connecting') {
return;
}
if (!this.doNotConnect) {
this.updateNetworkState('connecting');
var _this = this;
if (this.reconnectTimer) {
clearTimeout(this.reconnectTimer);
}
this.reconnectTimer = setTimeout(function(){
_this.connect();
}, this.reconnectInterval);
if (this.reconnectInterval < 1000) {
this.reconnectInterval = 1000;
} else {
// 每次重连间隔增大一倍
this.reconnectInterval = this.reconnectInterval * 2;
}
// 有网络的状态下重连间隔最大2秒
if (this.reconnectInterval > 2000 && navigator.onLine) {
_this.reconnectInterval = 2000;
}
}
}
Connection.prototype.send = function(data) {
if (this.state !== 'connected') {
console.trace('networkState is "' + this.state + '", can not send ' + data);
return;
}
this.connection.send(data);
}
Connection.prototype.close = function(){
this.updateNetworkState('disconnected');
this.connection.close();
}
var __extends = (this && this.__extends) || function (d, b) {
for (var p in b) if (b.hasOwnProperty(p)) {d[p] = b[p];}
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
function Channel(connection, channel_name) {
this.subscribed = false;
this.dispatcher = new Dispatcher();
this.connection = connection;
this.channelName = channel_name;
this.subscribeCb = null;
this.queue = [];
__extends(this, this.dispatcher);
var properies = ['on', 'off', 'emit'];
for (var i in properies) {
this[properies[i]] = this.dispatcher[properies[i]];
}
}
Channel.prototype.processSubscribe = function () {
if (this.connection.state !== 'connected') {
return;
}
this.subscribeCb();
};
Channel.prototype.processQueue = function () {
if (this.connection.state !== 'connected' || !this.subscribed) {
return;
}
for (var i in this.queue) {
this.queue[i]();
}
this.queue = [];
};
Channel.prototype.trigger = function (event, data) {
if (event.indexOf('client-') !== 0) {
throw new Error("Event '" + event + "' should start with 'client-'");
}
var _this = this;
this.queue.push(function () {
_this.connection.send(JSON.stringify({ event: event, data: data, channel: _this.channelName }));
});
this.processQueue();
};
////////////////
var Collections = (function () {
var exports = {};
function extend(target) {
var sources = [];
for (var _i = 1; _i < arguments.length; _i++) {
sources[_i - 1] = arguments[_i];
}
for (var i = 0; i < sources.length; i++) {
var extensions = sources[i];
for (var property in extensions) {
if (extensions[property] && extensions[property].constructor &&
extensions[property].constructor === Object) {
target[property] = extend(target[property] || {}, extensions[property]);
}
else {
target[property] = extensions[property];
}
}
}
return target;
}
exports.extend = extend;
function stringify() {
var m = ["Push"];
for (var i = 0; i < arguments.length; i++) {
if (typeof arguments[i] === "string") {
m.push(arguments[i]);
}
else {
m.push(safeJSONStringify(arguments[i]));
}
}
return m.join(" : ");
}
exports.stringify = stringify;
function arrayIndexOf(array, item) {
var nativeIndexOf = Array.prototype.indexOf;
if (array === null) {
return -1;
}
if (nativeIndexOf && array.indexOf === nativeIndexOf) {
return array.indexOf(item);
}
for (var i = 0, l = array.length; i < l; i++) {
if (array[i] === item) {
return i;
}
}
return -1;
}
exports.arrayIndexOf = arrayIndexOf;
function objectApply(object, f) {
for (var key in object) {
if (Object.prototype.hasOwnProperty.call(object, key)) {
f(object[key], key, object);
}
}
}
exports.objectApply = objectApply;
function keys(object) {
var keys = [];
objectApply(object, function (_, key) {
keys.push(key);
});
return keys;
}
exports.keys = keys;
function values(object) {
var values = [];
objectApply(object, function (value) {
values.push(value);
});
return values;
}
exports.values = values;
function apply(array, f, context) {
for (var i = 0; i < array.length; i++) {
f.call(context || (window), array[i], i, array);
}
}
exports.apply = apply;
function map(array, f) {
var result = [];
for (var i = 0; i < array.length; i++) {
result.push(f(array[i], i, array, result));
}
return result;
}
exports.map = map;
function mapObject(object, f) {
var result = {};
objectApply(object, function (value, key) {
result[key] = f(value);
});
return result;
}
exports.mapObject = mapObject;
function filter(array, test) {
test = test || function (value) {
return !!value;
};
var result = [];
for (var i = 0; i < array.length; i++) {
if (test(array[i], i, array, result)) {
result.push(array[i]);
}
}
return result;
}
exports.filter = filter;
function filterObject(object, test) {
var result = {};
objectApply(object, function (value, key) {
if ((test && test(value, key, object, result)) || Boolean(value)) {
result[key] = value;
}
});
return result;
}
exports.filterObject = filterObject;
function flatten(object) {
var result = [];
objectApply(object, function (value, key) {
result.push([key, value]);
});
return result;
}
exports.flatten = flatten;
function any(array, test) {
for (var i = 0; i < array.length; i++) {
if (test(array[i], i, array)) {
return true;
}
}
return false;
}
exports.any = any;
function all(array, test) {
for (var i = 0; i < array.length; i++) {
if (!test(array[i], i, array)) {
return false;
}
}
return true;
}
exports.all = all;
function encodeParamsObject(data) {
return mapObject(data, function (value) {
if (typeof value === "object") {
value = safeJSONStringify(value);
}
return encodeURIComponent(base64_1["default"](value.toString()));
});
}
exports.encodeParamsObject = encodeParamsObject;
function buildQueryString(data) {
var params = filterObject(data, function (value) {
return value !== undefined;
});
return map(flatten(encodeParamsObject(params)), util_1["default"].method("join", "=")).join("&");
}
exports.buildQueryString = buildQueryString;
function decycleObject(object) {
var objects = [], paths = [];
return (function derez(value, path) {
var i, name, nu;
switch (typeof value) {
case 'object':
if (!value) {
return null;
}
for (i = 0; i < objects.length; i += 1) {
if (objects[i] === value) {
return {$ref: paths[i]};
}
}
objects.push(value);
paths.push(path);
if (Object.prototype.toString.apply(value) === '[object Array]') {
nu = [];
for (i = 0; i < value.length; i += 1) {
nu[i] = derez(value[i], path + '[' + i + ']');
}
}
else {
nu = {};
for (name in value) {
if (Object.prototype.hasOwnProperty.call(value, name)) {
nu[name] = derez(value[name], path + '[' + JSON.stringify(name) + ']');
}
}
}
return nu;
case 'number':
case 'string':
case 'boolean':
return value;
}
}(object, '$'));
}
exports.decycleObject = decycleObject;
function safeJSONStringify(source) {
try {
return JSON.stringify(source);
}
catch (e) {
return JSON.stringify(decycleObject(source));
}
}
exports.safeJSONStringify = safeJSONStringify;
return exports;
})();
var Dispatcher = (function () {
function Dispatcher(failThrough) {
this.callbacks = new CallbackRegistry();
this.global_callbacks = [];
this.failThrough = failThrough;
}
Dispatcher.prototype.on = function (eventName, callback, context) {
this.callbacks.add(eventName, callback, context);
return this;
};
Dispatcher.prototype.on_global = function (callback) {
this.global_callbacks.push(callback);
return this;
};
Dispatcher.prototype.off = function (eventName, callback, context) {
this.callbacks.remove(eventName, callback, context);
return this;
};
Dispatcher.prototype.emit = function (eventName, data) {
var i;
for (i = 0; i < this.global_callbacks.length; i++) {
this.global_callbacks[i](eventName, data);
}
var callbacks = this.callbacks.get(eventName);
if (callbacks && callbacks.length > 0) {
for (i = 0; i < callbacks.length; i++) {
callbacks[i].fn.call(callbacks[i].context || (window), data);
}
}
else if (this.failThrough) {
this.failThrough(eventName, data);
}
return this;
};
return Dispatcher;
}());
var CallbackRegistry = (function () {
function CallbackRegistry() {
this._callbacks = {};
}
CallbackRegistry.prototype.get = function (name) {
return this._callbacks[prefix(name)];
};
CallbackRegistry.prototype.add = function (name, callback, context) {
var prefixedEventName = prefix(name);
this._callbacks[prefixedEventName] = this._callbacks[prefixedEventName] || [];
this._callbacks[prefixedEventName].push({
fn: callback,
context: context
});
};
CallbackRegistry.prototype.remove = function (name, callback, context) {
if (!name && !callback && !context) {
this._callbacks = {};
return;
}
var names = name ? [prefix(name)] : Collections.keys(this._callbacks);
if (callback || context) {
this.removeCallback(names, callback, context);
}
else {
this.removeAllCallbacks(names);
}
};
CallbackRegistry.prototype.removeCallback = function (names, callback, context) {
Collections.apply(names, function (name) {
this._callbacks[name] = Collections.filter(this._callbacks[name] || [], function (oning) {
return (callback && callback !== oning.fn) ||
(context && context !== oning.context);
});
if (this._callbacks[name].length === 0) {
delete this._callbacks[name];
}
}, this);
};
CallbackRegistry.prototype.removeAllCallbacks = function (names) {
Collections.apply(names, function (name) {
delete this._callbacks[name];
}, this);
};
return CallbackRegistry;
}());
function prefix(name) {
return "_" + name;
}
function __ajax(options){
options=options||{};
options.type=(options.type||'GET').toUpperCase();
options.dataType=options.dataType||'json';
params=formatParams(options.data);
var xhr;
if(window.XMLHttpRequest){
xhr=new XMLHttpRequest();
}else{
xhr=ActiveXObject('Microsoft.XMLHTTP');
}
xhr.onreadystatechange=function(){
if(xhr.readyState === 4){
var status=xhr.status;
if(status>=200 && status<300){
options.success&&options.success(xhr.responseText,xhr.responseXML);
}else{
options.error&&options.error(status);
}
}
}
if(options.type==='GET'){
xhr.open('GET',options.url+'?'+params,true);
xhr.send(null);
}else if(options.type==='POST'){
xhr.open('POST',options.url,true);
xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
xhr.send(params);
}
}
function formatParams(data){
var arr=[];
for(var name in data){
arr.push(encodeURIComponent(name)+'='+encodeURIComponent(data[name]));
}
return arr.join('&');
}

View File

@@ -4,9 +4,11 @@ namespace Webman\ThinkOrm;
use Webman\Bootstrap;
use Workerman\Timer;
use Throwable;
use think\Paginator;
use think\facade\Db;
use think\db\connector\Mysql;
use think\DbManager;
use think\Container;
class ThinkOrm implements Bootstrap
{
@@ -14,36 +16,60 @@ class ThinkOrm implements Bootstrap
public static function start($worker)
{
$config = config('thinkorm');
$default = $config['default'] ?? false;
$connections = $config['connections'] ?? [];
// 配置
Db::setConfig($config);
// 维持mysql心跳
if ($worker) {
Timer::add(55, function () use ($connections, $default) {
if (!class_exists(Mysql::class, false)) {
return;
}
foreach ($connections as $key => $item) {
if ($item['type'] == 'mysql') {
if (class_exists(Container::class, false)) {
$manager_instance = Container::getInstance()->make(DbManager::class);
} else {
$reflect = new \ReflectionClass(Db::class);
$property = $reflect->getProperty('instance');
$property->setAccessible(true);
$manager_instance = $property->getValue();
}
Timer::add(55, function () use ($manager_instance) {
$reflect = new \ReflectionClass($manager_instance);
$property = $reflect->getProperty('instance');
$property->setAccessible(true);
$instances = $property->getValue($manager_instance);
foreach ($instances as $connection) {
/* @var \think\db\connector\Mysql $connection */
if ($connection->getConfig('type') == 'mysql') {
try {
if ($key == $default) {
Db::query('select 1');
} else {
Db::connect($key)->query('select 1');
}
$connection->query('select 1');
} catch (Throwable $e) {}
}
}
Db::getDbLog(true);
});
}
// 自定义分页组件类
$bootstrap = $config['connections'][$config['default']]['bootstrap'] ?? false;
if($bootstrap && class_exists($bootstrap)){
Paginator::maker(function ($items, $listRows, $currentPage, $total, $simple, $options) use ($bootstrap){
return (new \ReflectionClass($bootstrap))->newInstanceArgs(func_get_args());
});
}
Paginator::currentPageResolver(function ($pageName = 'page') {
$page = request()->input($pageName, 1);
$request = request();
if (!$request) {
return 1;
}
$page = $request->input($pageName, 1);
if (filter_var($page, FILTER_VALIDATE_INT) !== false && (int)$page >= 1) {
return (int)$page;
}
return 1;
});
// 设置分页url中域名与参数之间的path字符串
Paginator::currentPathResolver(function (){
$request = request();
return $request ? $request->path() : '/';
});
}
}

View File

@@ -17,7 +17,10 @@ return [
// 数据库连接端口
'hostport' => '3306',
// 数据库连接参数
'params' => [],
'params' => [
// 连接超时3秒
\PDO::ATTR_TIMEOUT => 3,
],
// 数据库编码默认采用utf8
'charset' => 'utf8',
// 数据库表前缀
@@ -26,6 +29,8 @@ return [
'break_reconnect' => true,
// 关闭SQL监听日志
'trigger_sql' => false,
// 自定义分页类
'bootstrap' => ''
],
],
];
];