mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-02 22:35:38 +08:00
Compare commits
35 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c635891e0b | ||
|
|
7b7a2d7010 | ||
|
|
23b1f797ad | ||
|
|
a72e0f705c | ||
|
|
504934f057 | ||
|
|
e976a96d0b | ||
|
|
dc7af105f4 | ||
|
|
fd96543268 | ||
|
|
cebc6fb310 | ||
|
|
c654cf40d5 | ||
|
|
68cc536af3 | ||
|
|
42412fee98 | ||
|
|
500ec4707e | ||
|
|
b09857e3b8 | ||
|
|
7d79126c8f | ||
|
|
a542558503 | ||
|
|
6697591a22 | ||
|
|
aad28f1ec4 | ||
|
|
f1949b1bd0 | ||
|
|
3b8aac5d8f | ||
|
|
5fd45c2542 | ||
|
|
af89c1b1f6 | ||
|
|
3287b96f30 | ||
|
|
00a8683658 | ||
|
|
c0ea068d04 | ||
|
|
9ba58ff90f | ||
|
|
1a1cf0ad30 | ||
|
|
1de93b9dc1 | ||
|
|
99e44eea3d | ||
|
|
e67958a8d1 | ||
|
|
23b3dc34e2 | ||
|
|
775672d515 | ||
|
|
40e17fab62 | ||
|
|
59fde3d075 | ||
|
|
a8183757be |
@@ -2,7 +2,7 @@
|
||||
|
||||
[]()
|
||||
[](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE)
|
||||
[]()
|
||||
[](https://packagist.org/packages/zhamao/framework)
|
||||
|
||||
[](https://github.com/zhamao-robot/zhamao-framework/search?q=stupid)
|
||||
[](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO)
|
||||
@@ -19,8 +19,9 @@ zhamao-framework 是一个基于 酷Q 的 PHP Swoole 的机器人框架,它会
|
||||
除了起到解析消息的作用,炸毛框架 还提供了完整的 WebSocket + HTTP 服务器,你还能用此框架构建出高性能的 API 接口服务器。
|
||||
|
||||
## 开始
|
||||
1. 你可以使用项目的 `Use this template` 功能将框架克隆到你的公开或私有仓库进行开发
|
||||
2. 你也可以直接到 **Release** 中下载最新的 phar 包,放入文件夹后快速启动框架
|
||||
先安装环境,环境安装见下方文档。
|
||||
1. `composer create-project zhamao/framework-starter` 从模板新建基础文档结构进行使用
|
||||
2. 你也可以直接到 **Release** 中下载最新的 phar 包,放入文件夹后 `php server.phar` 快速启动框架
|
||||
3. 还可以使用 Dockerfile 构建 Docker 容器
|
||||
|
||||
## 文档
|
||||
@@ -83,6 +84,6 @@ Pages托管:[https://framework.zhamao.xin/](https://framework.zhamao.xin/)
|
||||
|
||||
欢迎随时在 HTTP-API 插件群里提问,当然更好的话可以加作者 QQ(627577391)或提交 Issue 进行疑难解答。
|
||||
|
||||
本项目在更行内容时,请及时关注 GitHub 动态,更新前请将自己的模块代码做好备份。
|
||||
本项目在更新内容时,请及时关注 GitHub 动态,更新前请将自己的模块代码做好备份。
|
||||
|
||||
项目框架采用 Apache-2.0 协议开源,在分发或重写修改等操作时需遵守协议。项目模块部分(`Module` 文件夹) 在非借鉴框架内代码时可不遵守 Apache-2.0 协议进行分发和修改(声明版权)。
|
||||
|
||||
File diff suppressed because one or more lines are too long
92
bin/start
92
bin/start
@@ -10,6 +10,14 @@ require __DIR__ . '/../src/Scheduler/Scheduler.php';
|
||||
Swoole\Coroutine::set([
|
||||
'max_coroutine' => 30000,
|
||||
]);
|
||||
global $vendor_mode;
|
||||
$vendor_mode = false;
|
||||
if (mb_strpos(__DIR__, getcwd()) !== false && substr(str_replace(getcwd(), "", __DIR__), 0, 8) == "/vendor/") {
|
||||
define("LOAD_MODE", 1); //composer项目模式
|
||||
define("LOAD_MODE_COMPOSER_PATH", getcwd());
|
||||
} else {
|
||||
define("LOAD_MODE", 0); //正常模式
|
||||
}
|
||||
|
||||
date_default_timezone_set("Asia/Shanghai");
|
||||
|
||||
@@ -24,13 +32,86 @@ switch ($argv[1] ?? '') {
|
||||
}
|
||||
});
|
||||
break;
|
||||
case 'phar-build':
|
||||
array_shift($argv);
|
||||
require_once 'phar-build';
|
||||
break;
|
||||
case 'systemd':
|
||||
array_shift($argv);
|
||||
require_once 'systemd';
|
||||
break;
|
||||
case 'init':
|
||||
array_shift($argv);
|
||||
if (LOAD_MODE != 1) {
|
||||
echo "initialization must be started with composer-project mode!\n";
|
||||
exit(1);
|
||||
}
|
||||
$cwd = LOAD_MODE_COMPOSER_PATH;
|
||||
echo "Copying default module file ...";
|
||||
@mkdir($cwd . "/config");
|
||||
@mkdir($cwd . "/src");
|
||||
@mkdir($cwd . "/src/Custom");
|
||||
@mkdir($cwd . "/src/Module");
|
||||
@mkdir($cwd . "/src/Module/Example");
|
||||
@mkdir($cwd . "/src/Module/Middleware");
|
||||
$ls = [
|
||||
"/config/global.php",
|
||||
"/.gitignore",
|
||||
"/config/file_header.json",
|
||||
"/config/motd.txt",
|
||||
"/src/Module/Example/Hello.php",
|
||||
"/src/Module/Middleware/TimerMiddleware.php",
|
||||
"/src/Custom/global_function.php"
|
||||
];
|
||||
foreach($ls as $v) {
|
||||
if(!file_exists($cwd.$v)) {
|
||||
echo "Copying ".$v.PHP_EOL;
|
||||
copy($cwd."/vendor/zhamao/framework".$v, $cwd.$v);
|
||||
}
|
||||
}
|
||||
$autoload = [
|
||||
"psr-4" => [
|
||||
"Module\\" => "src/Module",
|
||||
"Custom\\" => "src/Custom"
|
||||
],
|
||||
"files" => [
|
||||
"src/Custom/global_function.php"
|
||||
]
|
||||
];
|
||||
$scripts = [
|
||||
"server" => "vendor/bin/start server",
|
||||
"server:log-debug" => "vendor/bin/start server --log-debug",
|
||||
"server:log-verbose" => "vendor/bin/start server --log-verbose",
|
||||
"server:log-info" => "vendor/bin/start server --log-info",
|
||||
"server:log-warning" => "vendor/bin/start server --log-warning",
|
||||
"server:debug-mode" => "vendor/bin/start server --debug-mode",
|
||||
"systemd" => "vendor/bin/start systemd"
|
||||
];
|
||||
echo PHP_EOL;
|
||||
if (file_exists($cwd . "/composer.json")) {
|
||||
echo "Updating composer.json ...";
|
||||
$composer = json_decode(file_get_contents($cwd . "/composer.json"), true);
|
||||
if (!isset($composer["autoload"])) {
|
||||
$composer["autoload"] = $autoload;
|
||||
}
|
||||
if (!isset($composer["scripts"])) {
|
||||
$composer["scripts"] = $scripts;
|
||||
}
|
||||
file_put_contents($cwd . "/composer.json", json_encode($composer, 64 | 128 | 256));
|
||||
echo PHP_EOL;
|
||||
} else {
|
||||
echo("Error occurred. Please check your updates.\n");
|
||||
exit(1);
|
||||
}
|
||||
echo "success!\n";
|
||||
break;
|
||||
case '':
|
||||
case 'framework':
|
||||
case 'server':
|
||||
if(!is_dir(__DIR__.'/../vendor/')){
|
||||
if (!is_dir(__DIR__ . '/../vendor/') && LOAD_MODE == 0) {
|
||||
echo "Warning: you have not update composer!\n";
|
||||
exec("composer update", $out, $var);
|
||||
if($var != 0) {
|
||||
if ($var != 0) {
|
||||
echo "You need to run \"composer update\" at root of zhamao-framework!\n";
|
||||
die;
|
||||
}
|
||||
@@ -39,10 +120,13 @@ switch ($argv[1] ?? '') {
|
||||
break;
|
||||
case '--help':
|
||||
case '-h':
|
||||
echo "\nUsage: ".$argv[0]." [OPTION]\n";
|
||||
echo "\nUsage: " . $argv[0] . " [OPTION]\n";
|
||||
echo "\nzhamao-framework start script, provides several startup arguments.";
|
||||
echo "\n\n -h, --help\t\tShow this help menu";
|
||||
echo "\n framework, server\tstart main framework, this is default option\n\n";
|
||||
echo "\n framework, server\tstart main framework, this is default option";
|
||||
echo "\n phar-build\t\tbuild a new phar archive";
|
||||
echo "\n init\t\t\tinitialize framework structure in this directory";
|
||||
echo "\n systemd\t\tgenerate a new systemd \".service\" file to use\n\n";
|
||||
break;
|
||||
default:
|
||||
echo "Unknown option \"{$argv[1]}\"!\n\"--help\" for more information\n";
|
||||
|
||||
11
bin/systemd
11
bin/systemd
@@ -2,7 +2,8 @@
|
||||
<?php /** @since 1.2 */
|
||||
switch ($argv[1] ?? '') {
|
||||
case '--generate':
|
||||
generate();
|
||||
case '':
|
||||
generate($argv);
|
||||
break;
|
||||
case '--help':
|
||||
case '-h':
|
||||
@@ -14,13 +15,17 @@ switch ($argv[1] ?? '') {
|
||||
break;
|
||||
}
|
||||
|
||||
function generate() {
|
||||
function generate($argv) {
|
||||
$s = "[Unit]\nDescription=zhamao-framework Daemon\nAfter=rc-local.service\n\n[Service]\nType=simple";
|
||||
$s .= "\nUser=" . exec("whoami");
|
||||
$s .= "\nGroup=" . exec("groups | awk '{print $1}'");
|
||||
$s .= "\nWorkingDirectory=" . getcwd();
|
||||
$s .= "\nExecStart=" . getcwd() . "/bin/start server --disable-console-input";
|
||||
if ($argv[0] == "systemd" && !file_exists(getcwd() . '/systemd'))
|
||||
$s .= "\nExecStart=" . getcwd() . "/vendor/bin/start server --disable-console-input";
|
||||
else
|
||||
$s .= "\nExecStart=" . getcwd() . "/bin/start server --disable-console-input";
|
||||
$s .= "\nRestart=always\n\n[Install]\nWantedBy=multi-user.target\n";
|
||||
@mkdir(getcwd() . "/resources/");
|
||||
file_put_contents(getcwd() . "/resources/zhamao.service", $s);
|
||||
echo "File successfully generated. Path: " . getcwd() . "/resources/zhamao.service\n";
|
||||
}
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
composer update
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"name": "zhamao/framework",
|
||||
"description": "high-performance intelligent assistant",
|
||||
"description": "High performance QQ robot and web server development framework",
|
||||
"minimum-stability": "stable",
|
||||
"license": "Apache-2.0",
|
||||
"version": "1.4.0",
|
||||
"version": "1.6",
|
||||
"authors": [
|
||||
{
|
||||
"name": "whale",
|
||||
@@ -14,22 +14,28 @@
|
||||
"email": "hugo_swift@yahoo.com"
|
||||
}
|
||||
],
|
||||
"prefer-stable": true,
|
||||
"bin": [
|
||||
"bin/start"
|
||||
],
|
||||
"require": {
|
||||
"php": ">=7.2",
|
||||
"swoole/ide-helper": "@dev",
|
||||
"ext-mbstring": "*",
|
||||
"swlib/saber": "^1.0",
|
||||
"doctrine/annotations": "<1.10.2",
|
||||
"doctrine/annotations": "~1.10",
|
||||
"ext-json": "*",
|
||||
"ext-posix": "*",
|
||||
"ext-ctype": "*",
|
||||
"ext-pdo": "*",
|
||||
"psy/psysh": "@stable"
|
||||
},
|
||||
"repositories": {
|
||||
"packagist": {
|
||||
"type": "composer",
|
||||
"url": "https://mirrors.aliyun.com/composer/"
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
"Custom\\": "src/Custom",
|
||||
"Framework\\": "src/Framework",
|
||||
"ZM\\": "src/ZM",
|
||||
"Module\\": "src/Module"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -14,7 +14,7 @@ $config['http_reverse_link'] = "http://127.0.0.1:".$config['port'];
|
||||
$config['debug_mode'] = false;
|
||||
|
||||
/** 存放框架内文件数据的目录 */
|
||||
$config['zm_data'] = WORKING_DIR.'/zm_data/';
|
||||
$config['zm_data'] = realpath(__DIR__ . "/../").'/zm_data/';
|
||||
|
||||
/** 存放各个模块配置文件的目录 */
|
||||
$config['config_dir'] = $config['zm_data'].'config/';
|
||||
@@ -27,7 +27,8 @@ $config['swoole'] = [
|
||||
'log_file' => $config['crash_dir'].'swoole_error.log',
|
||||
'worker_num' => 1,
|
||||
'dispatch_mode' => 2,
|
||||
'task_worker_num' => 0
|
||||
//'task_worker_num' => 1,
|
||||
//'task_enable_coroutine' => true
|
||||
];
|
||||
|
||||
/** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */
|
||||
@@ -38,7 +39,13 @@ $config['sql_config'] = [
|
||||
'sql_database' => 'db_name',
|
||||
'sql_password' => '',
|
||||
'sql_enable_cache' => true,
|
||||
'sql_reset_cache' => '0300'
|
||||
'sql_reset_cache' => '0300',
|
||||
'sql_options' => [
|
||||
PDO::ATTR_STRINGIFY_FETCHES => false,
|
||||
PDO::ATTR_EMULATE_PREPARES => false
|
||||
],
|
||||
'sql_no_exception' => false,
|
||||
'sql_default_fetch_mode' => PDO::FETCH_BOTH // added in 1.5.6
|
||||
];
|
||||
|
||||
/** CQHTTP连接约定的token */
|
||||
@@ -73,10 +80,15 @@ $config['context_class'] = \ZM\Context\Context::class;
|
||||
/** 静态文件访问 */
|
||||
$config['static_file_server'] = [
|
||||
'status' => false,
|
||||
'document_root' => WORKING_DIR . '/resources/html',
|
||||
'document_root' => realpath(__DIR__ . "/../") . '/resources/html',
|
||||
'document_index' => [
|
||||
'index.html'
|
||||
]
|
||||
];
|
||||
|
||||
/** 注册 Swoole Server 事件注解的类列表 */
|
||||
$config['server_event_handler_class'] = [
|
||||
\Framework\ServerEventHandler::class, //默认不可删除,否则会不能使用框架
|
||||
];
|
||||
|
||||
return $config;
|
||||
|
||||
@@ -30,6 +30,8 @@ Swoole\Coroutine::set([
|
||||
date_default_timezone_set("Asia/Shanghai");
|
||||
|
||||
define('WORKING_DIR', __DIR__);
|
||||
define('FRAMEWORK_DIR', __DIR__);
|
||||
define('LOAD_MODE', 2);
|
||||
|
||||
$s = new FrameworkLoader($argv);
|
||||
|
||||
|
||||
@@ -1,7 +1,3 @@
|
||||
<?php
|
||||
|
||||
//这里写你的全局函数
|
||||
|
||||
function phptest(){
|
||||
echo "Nothing.\n";
|
||||
}
|
||||
@@ -184,7 +184,8 @@ class Console
|
||||
$vss->callback = function(?WSConnection $conn) use ($terminal_id){
|
||||
$req = ctx()->getRequest();
|
||||
if($conn->getType() != "terminal") return false;
|
||||
if(($req->header["x-terminal-id"] ?? "") != $terminal_id || ($req->header["x-pid"] ?? "") != posix_getpid()) {
|
||||
Console::debug("Terminal fd: ".$conn->fd);
|
||||
if(($req->header["x-terminal-id"] ?? "") != $terminal_id) {
|
||||
$conn->close();
|
||||
return false;
|
||||
}
|
||||
@@ -195,6 +196,7 @@ class Console
|
||||
$vss2->type = "message";
|
||||
$vss2->rule = "connectType:terminal";
|
||||
$vss2->callback = function(?WSConnection $conn){
|
||||
if ($conn === null) return false;
|
||||
if($conn->getType() != "terminal") return false;
|
||||
$cmd = ctx()->getFrame()->data;
|
||||
self::executeCommand($cmd);
|
||||
|
||||
@@ -15,12 +15,10 @@ class DataProvider
|
||||
}
|
||||
|
||||
public static function getWorkingDir() {
|
||||
global $is_phar;
|
||||
if ($is_phar === true) {
|
||||
return realpath('.');
|
||||
} else {
|
||||
return WORKING_DIR;
|
||||
}
|
||||
if(LOAD_MODE == 0) return WORKING_DIR;
|
||||
elseif (LOAD_MODE == 1) return LOAD_MODE_COMPOSER_PATH;
|
||||
elseif (LOAD_MODE == 2) return realpath('.');
|
||||
return null;
|
||||
}
|
||||
|
||||
public static function getDataConfig() {
|
||||
|
||||
@@ -3,15 +3,13 @@
|
||||
|
||||
namespace Framework;
|
||||
|
||||
use Co;
|
||||
use Doctrine\Common\Annotations\AnnotationException;
|
||||
use Swoole\Http\Request;
|
||||
use Doctrine\Common\Annotations\AnnotationReader;
|
||||
use ReflectionClass;
|
||||
use ReflectionMethod;
|
||||
use Swoole\Runtime;
|
||||
use Swoole\WebSocket\Frame;
|
||||
use ZM\Event\EventHandler;
|
||||
use ZM\Annotation\Swoole\OnEvent;
|
||||
use Exception;
|
||||
use Swoole\WebSocket\Server;
|
||||
use ZM\Http\Response;
|
||||
|
||||
/**
|
||||
* Class FrameworkLoader
|
||||
@@ -37,17 +35,22 @@ class FrameworkLoader
|
||||
private $server;
|
||||
|
||||
public function __construct($args = []) {
|
||||
if (self::$instance !== null) die("Cannot run two FrameworkLoader in one process!");
|
||||
self::$instance = $this;
|
||||
|
||||
|
||||
$this->requireGlobalFunctions();
|
||||
if (!isPharMode()) {
|
||||
define('WORKING_DIR', getcwd());
|
||||
} else {
|
||||
echo "Phar mode: " . WORKING_DIR . PHP_EOL;
|
||||
if (LOAD_MODE == 0) define("WORKING_DIR", getcwd());
|
||||
elseif (LOAD_MODE == 1) define("WORKING_DIR", realpath(__DIR__ . "/../../"));
|
||||
elseif (LOAD_MODE == 2) echo "Phar mode: " . WORKING_DIR . PHP_EOL;
|
||||
//$this->registerAutoloader('classLoader');
|
||||
require_once "DataProvider.php";
|
||||
if (file_exists(DataProvider::getWorkingDir() . "/vendor/autoload.php")) {
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
require_once DataProvider::getWorkingDir() . "/vendor/autoload.php";
|
||||
}
|
||||
$this->registerAutoloader('classLoader');
|
||||
if (LOAD_MODE == 2) {
|
||||
require_once FRAMEWORK_DIR . "/vendor/autoload.php";
|
||||
spl_autoload_register('phar_classloader');
|
||||
}
|
||||
|
||||
|
||||
self::$settings = new GlobalConfig();
|
||||
if (self::$settings->get("debug_mode") === true) {
|
||||
$args[] = "--debug-mode";
|
||||
@@ -66,7 +69,6 @@ class FrameworkLoader
|
||||
$this->selfCheck();
|
||||
try {
|
||||
$this->server = new Server(self::$settings->get("host"), self::$settings->get("port"));
|
||||
if (in_array("--remote-shell", $args)) RemoteShell::listen($this->server, "127.0.0.1");
|
||||
$settings = self::$settings->get("swoole");
|
||||
if (in_array("--daemon", $args)) {
|
||||
$settings["daemonize"] = 1;
|
||||
@@ -74,25 +76,35 @@ class FrameworkLoader
|
||||
self::$argv[] = "--disable-console-input";
|
||||
}
|
||||
$this->server->set($settings);
|
||||
$this->server->on("WorkerStart", [$this, "onWorkerStart"]);
|
||||
$this->server->on("message", function ($server, Frame $frame) {
|
||||
Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd);
|
||||
EventHandler::callSwooleEvent("message", $server, $frame);
|
||||
});
|
||||
$this->server->on("request", function ($request, $response) {
|
||||
$response = new Response($response);
|
||||
Console::debug("Receiving Http request event, cid=" . Co::getCid());
|
||||
EventHandler::callSwooleEvent("request", $request, $response);
|
||||
});
|
||||
$this->server->on("open", function ($server, Request $request) {
|
||||
Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd);
|
||||
EventHandler::callSwooleEvent("open", $server, $request);
|
||||
});
|
||||
$this->server->on("close", function ($server, $fd) {
|
||||
Console::debug("Calling Swoole \"close\" event from fd=" . $fd);
|
||||
EventHandler::callSwooleEvent("close", $server, $fd);
|
||||
});
|
||||
$all_event_class = self::$settings->get("server_event_handler_class");
|
||||
$event_list = [];
|
||||
foreach ($all_event_class as $v) {
|
||||
$reader = new AnnotationReader();
|
||||
$reflection_class = new ReflectionClass($v);
|
||||
$methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC);
|
||||
foreach ($methods as $vs) {
|
||||
$method_annotations = $reader->getMethodAnnotations($vs);
|
||||
if ($method_annotations != []) {
|
||||
$annotation = $method_annotations[0];
|
||||
if ($annotation instanceof OnEvent) {
|
||||
$annotation->class = $v;
|
||||
$annotation->method = $vs->getName();
|
||||
$event_list[strtolower($annotation->event)] = $annotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach ($event_list as $k => $v) {
|
||||
$this->server->on($k, function (...$param) use ($v) {
|
||||
$c = $v->class;
|
||||
//echo $c.PHP_EOL;
|
||||
$c = new $c();
|
||||
call_user_func_array([$c, $v->method], $param);
|
||||
});
|
||||
}
|
||||
|
||||
ZMBuf::initAtomic();
|
||||
if (in_array("--remote-shell", $args)) RemoteShell::listen($this->server, "127.0.0.1");
|
||||
if (in_array("--log-error", $args)) ZMBuf::$atomics["info_level"]->set(0);
|
||||
if (in_array("--log-warning", $args)) ZMBuf::$atomics["info_level"]->set(1);
|
||||
if (in_array("--log-info", $args)) ZMBuf::$atomics["info_level"]->set(2);
|
||||
@@ -102,8 +114,8 @@ class FrameworkLoader
|
||||
"host: " . self::$settings->get("host") .
|
||||
", port: " . self::$settings->get("port") .
|
||||
", log_level: " . ZMBuf::$atomics["info_level"]->get() .
|
||||
", version: " . json_decode(file_get_contents(WORKING_DIR . "/composer.json"), true)["version"] .
|
||||
"\nworking_dir: " . (isPharMode() ? realpath('.') : WORKING_DIR)
|
||||
", version: " . ZM_VERSION .
|
||||
"\nworking_dir: " . DataProvider::getWorkingDir()
|
||||
);
|
||||
global $motd;
|
||||
if (!file_exists(DataProvider::getWorkingDir() . "/config/motd.txt")) {
|
||||
@@ -113,6 +125,15 @@ class FrameworkLoader
|
||||
}
|
||||
if (in_array("--debug-mode", self::$argv))
|
||||
Console::warning("You are in debug mode, do not use in production!");
|
||||
register_shutdown_function(function() {
|
||||
$error = error_get_last();
|
||||
if(isset($error["type"]) && $error["type"] == 1) {
|
||||
if(mb_strpos($error["message"], "require") !== false && mb_strpos($error["message"], "callback") !== false) {
|
||||
echo "\e[38;5;203mYou may need to update your \"global.php\" config!\n";
|
||||
echo "Please see: https://github.com/zhamao-robot/zhamao-framework/issues/15\e[m\n";
|
||||
}
|
||||
}
|
||||
});
|
||||
$this->server->start();
|
||||
} catch (Exception $e) {
|
||||
Console::error("Framework初始化出现错误,请检查!");
|
||||
@@ -125,13 +146,10 @@ class FrameworkLoader
|
||||
require_once __DIR__ . '/global_functions.php';
|
||||
}
|
||||
|
||||
private function registerAutoloader(string $string) {
|
||||
if (!spl_autoload_register($string)) die("Failed to register autoloader named \"$string\" !");
|
||||
}
|
||||
|
||||
private function defineProperties() {
|
||||
define("ZM_START_TIME", microtime(true));
|
||||
define("ZM_DATA", self::$settings->get("zm_data"));
|
||||
define("ZM_VERSION", json_decode(file_get_contents(__DIR__ . "/../../composer.json"), true)["version"] ?? "unknown");
|
||||
define("CONFIG_DIR", self::$settings->get("config_dir"));
|
||||
define("CRASH_DIR", self::$settings->get("crash_dir"));
|
||||
@mkdir(ZM_DATA);
|
||||
@@ -142,6 +160,8 @@ class FrameworkLoader
|
||||
define("ZM_MATCH_NUMBER", 2);
|
||||
define("ZM_MATCH_SECOND", 3);
|
||||
define("ZM_BREAKPOINT", 'if(in_array("--debug-mode", \Framework\FrameworkLoader::$argv)) extract(\Psy\debug(get_defined_vars(), isset($this) ? $this : @get_called_class()));');
|
||||
define("BP", ZM_BREAKPOINT);
|
||||
define("ZM_DEFAULT_FETCH_MODE", self::$settings->get("sql_config")["sql_default_fetch_mode"] ?? 4);
|
||||
}
|
||||
|
||||
private function selfCheck() {
|
||||
@@ -158,16 +178,6 @@ class FrameworkLoader
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param \Swoole\Server $server
|
||||
* @param $worker_id
|
||||
* @throws AnnotationException
|
||||
*/
|
||||
public function onWorkerStart(\Swoole\Server $server, $worker_id) {
|
||||
self::$instance = $this;
|
||||
self::$run_time = microtime(true);
|
||||
EventHandler::callSwooleEvent("WorkerStart", $server, $worker_id);
|
||||
}
|
||||
}
|
||||
|
||||
global $motd;
|
||||
|
||||
84
src/Framework/ServerEventHandler.php
Normal file
84
src/Framework/ServerEventHandler.php
Normal file
@@ -0,0 +1,84 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Framework;
|
||||
|
||||
|
||||
use Co;
|
||||
use Doctrine\Common\Annotations\AnnotationException;
|
||||
use Swoole\Http\Request;
|
||||
use Swoole\Server;
|
||||
use Swoole\WebSocket\Frame;
|
||||
use ZM\Annotation\AnnotationParser;
|
||||
use ZM\Annotation\Swoole\OnEvent;
|
||||
use ZM\Connection\ConnectionManager;
|
||||
use ZM\Event\EventHandler;
|
||||
use ZM\Http\Response;
|
||||
|
||||
class ServerEventHandler
|
||||
{
|
||||
/**
|
||||
* @OnEvent("WorkerStart")
|
||||
* @param Server $server
|
||||
* @param $worker_id
|
||||
* @throws AnnotationException
|
||||
* @throws \ReflectionException
|
||||
*/
|
||||
public function onWorkerStart(Server $server, $worker_id) {
|
||||
if ($server->taskworker === false) {
|
||||
FrameworkLoader::$run_time = microtime(true);
|
||||
EventHandler::callSwooleEvent("WorkerStart", $server, $worker_id);
|
||||
} else {
|
||||
ob_start();
|
||||
AnnotationParser::registerMods();
|
||||
//加载Custom目录下的自定义的内部类
|
||||
ConnectionManager::registerCustomClass();
|
||||
ob_get_clean();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @OnEvent("message")
|
||||
* @param $server
|
||||
* @param Frame $frame
|
||||
* @throws AnnotationException
|
||||
*/
|
||||
public function onMessage($server, Frame $frame) {
|
||||
Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd);
|
||||
EventHandler::callSwooleEvent("message", $server, $frame);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OnEvent("request")
|
||||
* @param $request
|
||||
* @param $response
|
||||
* @throws AnnotationException
|
||||
*/
|
||||
public function onRequest($request, $response) {
|
||||
$response = new Response($response);
|
||||
Console::debug("Receiving Http request event, cid=" . Co::getCid());
|
||||
EventHandler::callSwooleEvent("request", $request, $response);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OnEvent("open")
|
||||
* @param $server
|
||||
* @param Request $request
|
||||
* @throws AnnotationException
|
||||
*/
|
||||
public function onOpen($server, Request $request) {
|
||||
Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd);
|
||||
EventHandler::callSwooleEvent("open", $server, $request);
|
||||
}
|
||||
|
||||
/**
|
||||
* @OnEvent("close")
|
||||
* @param $server
|
||||
* @param $fd
|
||||
* @throws AnnotationException
|
||||
*/
|
||||
public function onClose($server, $fd) {
|
||||
Console::debug("Calling Swoole \"close\" event from fd=" . $fd);
|
||||
EventHandler::callSwooleEvent("close", $server, $fd);
|
||||
}
|
||||
}
|
||||
@@ -52,6 +52,7 @@ class ZMBuf
|
||||
public static $context = [];
|
||||
public static $instance = [];
|
||||
public static $context_class = [];
|
||||
public static $server_events = [];
|
||||
|
||||
static function get($name, $default = null) {
|
||||
return self::$cache[$name] ?? $default;
|
||||
|
||||
@@ -7,15 +7,13 @@ use Swoole\Coroutine\System;
|
||||
use ZM\Context\ContextInterface;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
function isPharMode() {
|
||||
return substr(__DIR__, 0, 7) == 'phar://';
|
||||
}
|
||||
|
||||
function classLoader($p) {
|
||||
function phar_classloader($p){
|
||||
$filepath = getClassPath($p);
|
||||
if ($filepath === null)
|
||||
echo "F:Warning: get class path wrongs.$p\n";
|
||||
//else echo "F:DBG: Found " . $p . "\n";
|
||||
if($filepath === null) {
|
||||
Console::debug("F:Warning: get class path wrongs.$p");
|
||||
return;
|
||||
}
|
||||
try {
|
||||
require_once $filepath;
|
||||
} catch (Exception $e) {
|
||||
@@ -72,6 +70,7 @@ function unicode_decode($str) {
|
||||
* @return array
|
||||
*/
|
||||
function getAllClasses($dir, $indoor_name) {
|
||||
if(!is_dir($dir)) return [];
|
||||
$list = scandir($dir);
|
||||
$classes = [];
|
||||
unset($list[0], $list[1]);
|
||||
@@ -224,3 +223,5 @@ function zm_timer_tick($ms, callable $callable) {
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
use Swoole\Coroutine\Http\Client;
|
||||
|
||||
Co\run(function (){
|
||||
hello:
|
||||
global $terminal_id, $port;
|
||||
$client = new Client("127.0.0.1", $port);
|
||||
$client->set(['websocket_mask' => true]);
|
||||
@@ -17,8 +18,9 @@ Co\run(function (){
|
||||
break;
|
||||
}
|
||||
if($r === false) {
|
||||
echo "Unable to connect framework terminal, connection closed.\n";
|
||||
break;
|
||||
echo "Unable to connect framework terminal, connection closed. Trying to reconnect after 5s.\n";
|
||||
sleep(5);
|
||||
goto hello;
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
|
||||
@@ -10,7 +10,6 @@ use ZM\Annotation\Http\Middleware;
|
||||
use ZM\Annotation\Http\RequestMapping;
|
||||
use ZM\Annotation\Swoole\SwooleEventAt;
|
||||
use ZM\Connection\CQConnection;
|
||||
use ZM\ModBase;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
/**
|
||||
@@ -18,7 +17,7 @@ use ZM\Utils\ZMUtil;
|
||||
* @package Module\Example
|
||||
* @since 1.0
|
||||
*/
|
||||
class Hello extends ModBase
|
||||
class Hello
|
||||
{
|
||||
/**
|
||||
* 在机器人连接后向终端输出信息
|
||||
@@ -29,9 +28,18 @@ class Hello extends ModBase
|
||||
Console::info("机器人 " . $conn->getQQ() . " 已连接!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 在机器人连接后向终端输出信息
|
||||
* @SwooleEventAt("close",rule="connectType:qq")
|
||||
*/
|
||||
public function onDisconnect() {
|
||||
$conn = ctx()->getConnection();
|
||||
Console::info("机器人 " . $conn->getQQ() . " 已断开连接!");
|
||||
}
|
||||
|
||||
/**
|
||||
* 向机器人发送"你好",即可回复这句话
|
||||
* @CQCommand("你好")
|
||||
* @CQCommand(match="你好",alias={"你好啊","你是谁"})
|
||||
*/
|
||||
public function hello() {
|
||||
return "你好啊,我是由炸毛框架构建的机器人!";
|
||||
|
||||
@@ -56,7 +56,7 @@ class AnnotationParser
|
||||
$class_prefix = '';
|
||||
$methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC);
|
||||
$class_annotations = $reader->getClassAnnotations($reflection_class);
|
||||
$middleware_addon = null;
|
||||
$middleware_addon = [];
|
||||
foreach ($class_annotations as $vs) {
|
||||
if ($vs instanceof Closed) {
|
||||
continue 2;
|
||||
@@ -98,18 +98,24 @@ class AnnotationParser
|
||||
ZMBuf::$events[MiddlewareClass::class][$result["name"]] = $result;
|
||||
continue 2;
|
||||
} elseif ($vs instanceof Middleware) {
|
||||
$middleware_addon = $vs;
|
||||
$middleware_addon[] = $vs;
|
||||
} elseif ($vs instanceof CustomAnnotation) {
|
||||
$vs->class = $reflection_class->getName();
|
||||
ZMBuf::$events[get_class($vs)][] = $vs;
|
||||
}
|
||||
}
|
||||
foreach ($methods as $vs) {
|
||||
if ($middleware_addon !== null) {
|
||||
Console::debug("Added middleware " . $middleware_addon->middleware . " to $v -> " . $vs->getName());
|
||||
ZMBuf::$events[MiddlewareInterface::class][$v][$vs->getName()][] = $middleware_addon->middleware;
|
||||
if ($middleware_addon !== []) {
|
||||
foreach($middleware_addon as $value){
|
||||
Console::debug("Added middleware " . $value->middleware . " to $v -> " . $vs->getName());
|
||||
ZMBuf::$events[MiddlewareInterface::class][$v][$vs->getName()][] = $value->middleware;
|
||||
}
|
||||
}
|
||||
$method_annotations = $reader->getMethodAnnotations($vs);
|
||||
foreach ($method_annotations as $vss) {
|
||||
if ($vss instanceof Rule) $vss = self::registerRuleEvent($vss, $vs, $reflection_class);
|
||||
else $vss = self::registerMethod($vss, $vs, $reflection_class);
|
||||
Console::debug("寻找 " . $vs->getName() . " -> " . get_class($vss));
|
||||
|
||||
if ($vss instanceof SwooleEventAt) ZMBuf::$events[SwooleEventAt::class][] = $vss;
|
||||
elseif ($vss instanceof SwooleEventAfter) ZMBuf::$events[SwooleEventAfter::class][] = $vss;
|
||||
@@ -168,7 +174,7 @@ class AnnotationParser
|
||||
switch ($asp_name) {
|
||||
case "connectType": //websocket连接类型
|
||||
$func = function (?WSConnection $connection) use ($rest) {
|
||||
if($connection === null) return false;
|
||||
if ($connection === null) return false;
|
||||
return $connection->getType() == $rest ? true : false;
|
||||
};
|
||||
break;
|
||||
@@ -326,6 +332,7 @@ class AnnotationParser
|
||||
$class = getAllClasses(DataProvider::getWorkingDir() . "/src/Custom/Annotation/", "Custom\\Annotation");
|
||||
foreach ($class as $v) {
|
||||
$s = DataProvider::getWorkingDir() . '/src/' . str_replace("\\", "/", $v) . ".php";
|
||||
Console::debug("Requiring custom annotation " . $s);
|
||||
require_once $s;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,6 +19,18 @@ class CQCommand extends AnnotationBase implements Level
|
||||
public $match = "";
|
||||
/** @var string */
|
||||
public $regexMatch = "";
|
||||
/** @var string */
|
||||
public $fullMatch = "";
|
||||
/** @var string[] */
|
||||
public $alias = [];
|
||||
/** @var string */
|
||||
public $message_type = "";
|
||||
/** @var int */
|
||||
public $user_id = 0;
|
||||
/** @var int */
|
||||
public $group_id = 0;
|
||||
/** @var int */
|
||||
public $discuss_id = 0;
|
||||
/** @var int */
|
||||
public $level = 20;
|
||||
|
||||
@@ -32,4 +44,4 @@ class CQCommand extends AnnotationBase implements Level
|
||||
*/
|
||||
public function setLevel(int $level) { $this->level = $level; }
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
24
src/ZM/Annotation/Swoole/OnEvent.php
Normal file
24
src/ZM/Annotation/Swoole/OnEvent.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Annotation\Swoole;
|
||||
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
|
||||
/**
|
||||
* Class OnEvent
|
||||
* @package ZM\Annotation\Swoole
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
class OnEvent extends AnnotationBase
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @Required()
|
||||
*/
|
||||
public $event;
|
||||
}
|
||||
15
src/ZM/Annotation/Swoole/OnTaskWorkerStart.php
Normal file
15
src/ZM/Annotation/Swoole/OnTaskWorkerStart.php
Normal file
@@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Annotation\Swoole;
|
||||
|
||||
/**
|
||||
* Class OnTaskWorkerStart
|
||||
* @package ZM\Annotation\Swoole
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
class OnTaskWorkerStart
|
||||
{
|
||||
|
||||
}
|
||||
@@ -7,6 +7,7 @@ namespace ZM\DB;
|
||||
use Exception;
|
||||
use framework\Console;
|
||||
use framework\ZMBuf;
|
||||
use PDOException;
|
||||
use PDOStatement;
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Database\PDOStatementProxy;
|
||||
@@ -89,10 +90,11 @@ class DB
|
||||
/**
|
||||
* @param string $line
|
||||
* @param array $params
|
||||
* @param int $fetch_mode
|
||||
* @return mixed
|
||||
* @throws DbException
|
||||
*/
|
||||
public static function rawQuery(string $line, $params = []) {
|
||||
public static function rawQuery(string $line, $params = [], $fetch_mode = ZM_DEFAULT_FETCH_MODE) {
|
||||
if (ZMBuf::get("sql_log") === true) {
|
||||
$starttime = microtime(true);
|
||||
}
|
||||
@@ -130,9 +132,9 @@ class DB
|
||||
"] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND);
|
||||
}
|
||||
return $ps->fetchAll();
|
||||
return $ps->fetchAll($fetch_mode);
|
||||
}
|
||||
} catch (DBException $e) {
|
||||
} catch (DbException $e) {
|
||||
if (ZMBuf::get("sql_log") === true) {
|
||||
$log =
|
||||
"[" . date("Y-m-d H:i:s") .
|
||||
@@ -147,6 +149,21 @@ class DB
|
||||
}
|
||||
Console::warning($e->getMessage());
|
||||
throw $e;
|
||||
} catch (PDOException $e) {
|
||||
if (ZMBuf::get("sql_log") === true) {
|
||||
$log =
|
||||
"[" . date("Y-m-d H:i:s") .
|
||||
" " . round(microtime(true) - $starttime, 4) .
|
||||
"] " . $line . " " . json_encode($params, JSON_UNESCAPED_UNICODE) . " (Error:" . $e->getMessage() . ")\n";
|
||||
Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND);
|
||||
}
|
||||
if(mb_strpos($e->getMessage(), "has gone away") !== false) {
|
||||
zm_sleep(0.2);
|
||||
Console::warning("Gone away of MySQL! retrying!");
|
||||
return self::rawQuery($line, $params);
|
||||
}
|
||||
Console::warning($e->getMessage());
|
||||
throw new DbException($e->getMessage(), $e->getCode(), $e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,21 @@ class SelectBody
|
||||
public function get() { return $this->fetchAll(); }
|
||||
|
||||
/**
|
||||
* @throws DbException
|
||||
*/
|
||||
public function count() {
|
||||
$this->select_thing = ["count(*)"];
|
||||
$str = $this->queryPrepare();
|
||||
$this->result = DB::rawQuery($str[0], $str[1]);
|
||||
return intval($this->result[0]["count(*)"]);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $fetch_mode
|
||||
* @return null
|
||||
* @throws DbException
|
||||
*/
|
||||
public function fetchAll() {
|
||||
public function fetchAll($fetch_mode = ZM_DEFAULT_FETCH_MODE) {
|
||||
if ($this->table->isCacheEnabled()) {
|
||||
$rr = md5(implode(",", $this->select_thing) . serialize($this->where_thing));
|
||||
if (array_key_exists($rr, $this->table->cache)) {
|
||||
@@ -42,7 +53,7 @@ class SelectBody
|
||||
return $this->table->cache[$rr]->getResult();
|
||||
}
|
||||
}
|
||||
$this->execute();
|
||||
$this->execute($fetch_mode);
|
||||
if ($this->table->isCacheEnabled() && !in_array($rr, $this->table->cache)) {
|
||||
$this->table->cache[$rr] = $this;
|
||||
}
|
||||
@@ -71,11 +82,12 @@ class SelectBody
|
||||
}
|
||||
|
||||
/**
|
||||
* @param int $fetch_mode
|
||||
* @throws DbException
|
||||
*/
|
||||
public function execute() {
|
||||
public function execute($fetch_mode = ZM_DEFAULT_FETCH_MODE) {
|
||||
$str = $this->queryPrepare();
|
||||
$this->result = DB::rawQuery($str[0], $str[1]);
|
||||
$this->result = DB::rawQuery($str[0], $str[1], $fetch_mode);
|
||||
}
|
||||
|
||||
public function getResult() { return $this->result; }
|
||||
|
||||
@@ -9,8 +9,8 @@ trait WhereBody
|
||||
protected $where_thing = [];
|
||||
|
||||
public function where($column, $operation_or_value, $value = null) {
|
||||
if (!in_array($operation_or_value, ['=', '!='])) $this->where_thing['='][$column] = $operation_or_value;
|
||||
elseif ($value !== null) $this->where_thing[$operation_or_value][$column] = $value;
|
||||
if ($value !== null) $this->where_thing[$operation_or_value][$column] = $value;
|
||||
elseif (!in_array($operation_or_value, ['=', '!=', '>', '<', '>=', '<=', 'IN', 'in'])) $this->where_thing['='][$column] = $operation_or_value;
|
||||
else $this->where_thing['='][$column] = $operation_or_value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -38,9 +38,9 @@ class MessageEvent
|
||||
* @throws AnnotationException
|
||||
*/
|
||||
public function onBefore() {
|
||||
$obj_list = ZMBuf::$events[CQBefore::class]["message"];
|
||||
$obj_list = ZMBuf::$events[CQBefore::class]["message"] ?? [];
|
||||
foreach ($obj_list as $v) {
|
||||
if($v->level < 200) break;
|
||||
if ($v->level < 200) break;
|
||||
EventHandler::callWithMiddleware(
|
||||
$v->class,
|
||||
$v->method,
|
||||
@@ -65,7 +65,7 @@ class MessageEvent
|
||||
}
|
||||
}
|
||||
foreach (ZMBuf::$events[CQBefore::class]["message"] ?? [] as $v) {
|
||||
if($v->level >= 200) continue;
|
||||
if ($v->level >= 200) continue;
|
||||
$c = $v->class;
|
||||
if (ctx()->getCache("level") != 0) continue;
|
||||
EventHandler::callWithMiddleware(
|
||||
@@ -100,8 +100,12 @@ class MessageEvent
|
||||
$obj = [];
|
||||
foreach (ZMBuf::$events[CQCommand::class] ?? [] as $v) {
|
||||
/** @var CQCommand $v */
|
||||
if ($v->match == "" && $v->regexMatch == "") continue;
|
||||
else {
|
||||
if ($v->match == "" && $v->regexMatch == "" && $v->fullMatch == "") continue;
|
||||
elseif (($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == context()->getData()["user_id"])) &&
|
||||
($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == (context()->getData()["group_id"] ?? 0))) &&
|
||||
($v->discuss_id == 0 || ($v->discuss_id != 0 && $v->discuss_id == (context()->getData()["discuss_id"] ?? 0))) &&
|
||||
($v->message_type == '' || ($v->message_type != '' && $v->message_type == context()->getData()["message_type"]))
|
||||
) {
|
||||
$c = $v->class;
|
||||
$class_construct = [
|
||||
"data" => context()->getData(),
|
||||
@@ -117,6 +121,13 @@ class MessageEvent
|
||||
return true;
|
||||
});
|
||||
return;
|
||||
} elseif (in_array($word[0], $v->alias)) {
|
||||
Console::debug("Calling $c -> {$v->method}");
|
||||
$this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, $class_construct, [$word], function ($r) {
|
||||
if (is_string($r)) context()->reply($r);
|
||||
return true;
|
||||
});
|
||||
return;
|
||||
} elseif ($v->regexMatch != "" && ($args = matchArgs($v->regexMatch, context()->getMessage())) !== false) {
|
||||
Console::debug("Calling $c -> {$v->method}");
|
||||
$this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, $class_construct, [$args], function ($r) {
|
||||
@@ -124,6 +135,14 @@ class MessageEvent
|
||||
return true;
|
||||
});
|
||||
return;
|
||||
} elseif ($v->fullMatch != "" && (preg_match("/".$v->fullMatch."/u", ctx()->getMessage(), $args)) != 0) {
|
||||
Console::debug("Calling $c -> {$v->method}");
|
||||
array_shift($args);
|
||||
$this->function_call = EventHandler::callWithMiddleware($obj[$c], $v->method, $class_construct, [$args], function ($r) {
|
||||
if (is_string($r)) context()->reply($r);
|
||||
return true;
|
||||
});
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,14 +39,17 @@ class EventHandler
|
||||
switch ($event_name) {
|
||||
case "workerstart":
|
||||
try {
|
||||
register_shutdown_function(function () {
|
||||
register_shutdown_function(function () use ($param0) {
|
||||
$error = error_get_last();
|
||||
if ($error["type"] != 0) {
|
||||
Console::error("Internal fatal error: " . $error["message"] . " at " . $error["file"] . "({$error["line"]})");
|
||||
}
|
||||
DataProvider::saveBuffer();
|
||||
ZMBuf::$server->shutdown();
|
||||
/** @var Server $param0 */
|
||||
if (ZMBuf::$server === null) $param0->shutdown();
|
||||
else ZMBuf::$server->shutdown();
|
||||
});
|
||||
ZMBuf::$server = $param0;
|
||||
$r = (new WorkerStartEvent($param0, $param1))->onActivate();
|
||||
Console::log("\n=== Worker #" . $param0->worker_id . " 已启动 ===\n", "gold");
|
||||
$r->onAfter();
|
||||
@@ -57,7 +60,9 @@ class EventHandler
|
||||
ZMUtil::stop();
|
||||
return;
|
||||
} catch (Error $e) {
|
||||
var_export($e);
|
||||
Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
|
||||
Console::error("Maybe it caused by your own code if in your own Module directory.");
|
||||
Console::log($e->getTraceAsString(), 'gray');
|
||||
ZMUtil::stop();
|
||||
}
|
||||
break;
|
||||
@@ -85,7 +90,7 @@ class EventHandler
|
||||
" [" . $param1->getStatusCode() . "] " . $param0->server["request_uri"]
|
||||
);
|
||||
if (!$param1->isEnd()) $param1->end("Internal server error: " . $e->getMessage());
|
||||
Console::error("Internal server error (500), caused by uncaught exception.");
|
||||
Console::error("Internal server exception (500), caused by " . get_class($e));
|
||||
Console::log($e->getTraceAsString(), "gray");
|
||||
} catch (Error $e) {
|
||||
/** @var Response $param1 */
|
||||
@@ -134,7 +139,7 @@ class EventHandler
|
||||
* @throws AnnotationException
|
||||
*/
|
||||
public static function callCQEvent($event_data, $conn_or_response, int $level = 0) {
|
||||
ctx()->setCache("level",$level);
|
||||
ctx()->setCache("level", $level);
|
||||
if ($level >= 5) {
|
||||
Console::warning("Recursive call reached " . $level . " times");
|
||||
Console::stackTrace();
|
||||
@@ -172,7 +177,7 @@ class EventHandler
|
||||
* @throws AnnotationException
|
||||
*/
|
||||
public static function callCQResponse($req) {
|
||||
Console::debug("收到来自API连接的回复:".json_encode($req, 128|256));
|
||||
Console::debug("收到来自API连接的回复:" . json_encode($req, 128 | 256));
|
||||
$status = $req["status"];
|
||||
$retcode = $req["retcode"];
|
||||
$data = $req["data"];
|
||||
|
||||
@@ -42,6 +42,7 @@ class RequestEvent implements SwooleEvent
|
||||
$this->response->setHeader($k, $v);
|
||||
}
|
||||
$uri = $this->request->server["request_uri"];
|
||||
Console::verbose($this->request->server["remote_addr"] . " request " . $uri);
|
||||
$uri = explode("/", $uri);
|
||||
$uri = array_diff($uri, ["..", "", "."]);
|
||||
$node = ZMBuf::$req_mapping;
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
namespace ZM\Event\Swoole;
|
||||
|
||||
|
||||
use Closure;
|
||||
use Doctrine\Common\Annotations\AnnotationException;
|
||||
use Framework\ZMBuf;
|
||||
use Swoole\Server;
|
||||
@@ -11,8 +12,6 @@ use ZM\Annotation\Swoole\SwooleEventAfter;
|
||||
use ZM\Annotation\Swoole\SwooleEventAt;
|
||||
use ZM\Connection\ConnectionManager;
|
||||
use ZM\Event\EventHandler;
|
||||
use ZM\ModBase;
|
||||
use ZM\ModHandleType;
|
||||
use ZM\Utils\ZMUtil;
|
||||
|
||||
class WSCloseEvent implements SwooleEvent
|
||||
@@ -32,8 +31,7 @@ class WSCloseEvent implements SwooleEvent
|
||||
*/
|
||||
public function onActivate() {
|
||||
ZMUtil::checkWait();
|
||||
ConnectionManager::close($this->fd);
|
||||
set_coroutine_params(["server" => $this->server, "fd" => $this->fd]);
|
||||
set_coroutine_params(["server" => $this->server, "fd" => $this->fd, "connection" => ConnectionManager::get($this->fd)]);
|
||||
foreach(ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
|
||||
if(strtolower($v->type) == "close" && $this->parseSwooleRule($v)) {
|
||||
$c = $v->class;
|
||||
@@ -41,6 +39,7 @@ class WSCloseEvent implements SwooleEvent
|
||||
if(context()->getCache("block_continue") === true) break;
|
||||
}
|
||||
}
|
||||
ConnectionManager::close($this->fd);
|
||||
return $this;
|
||||
}
|
||||
|
||||
@@ -60,6 +59,11 @@ class WSCloseEvent implements SwooleEvent
|
||||
}
|
||||
|
||||
private function parseSwooleRule($v) {
|
||||
switch (explode(":", $v->rule)[0]) {
|
||||
case "connectType": //websocket连接类型
|
||||
if ($v->callback instanceof Closure) return call_user_func($v->callback, ConnectionManager::get($this->fd));
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace ZM\Event\Swoole;
|
||||
use Co;
|
||||
use Doctrine\Common\Annotations\AnnotationException;
|
||||
use Exception;
|
||||
use PDO;
|
||||
use ReflectionException;
|
||||
use Swoole\Coroutine;
|
||||
use Swoole\Database\PDOConfig;
|
||||
@@ -101,6 +102,7 @@ class WorkerStartEvent implements SwooleEvent
|
||||
->withCharset('utf8mb4')
|
||||
->withUsername($sql["sql_username"])
|
||||
->withPassword($sql["sql_password"])
|
||||
->withOptions($sql["sql_options"] ?? [PDO::ATTR_STRINGIFY_FETCHES => false])
|
||||
);
|
||||
DB::initTableList();
|
||||
}
|
||||
@@ -174,13 +176,8 @@ class WorkerStartEvent implements SwooleEvent
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//加载composer类
|
||||
Console::info("加载composer资源中");
|
||||
if (file_exists(DataProvider::getWorkingDir() . "/vendor/autoload.php")) {
|
||||
require_once DataProvider::getWorkingDir() . "/vendor/autoload.php";
|
||||
}
|
||||
if (isPharMode()) require_once WORKING_DIR . "/vendor/autoload.php";
|
||||
//remove stupid duplicate code
|
||||
|
||||
//加载各个模块的注解类,以及反射
|
||||
Console::info("检索Module中");
|
||||
|
||||
@@ -15,6 +15,11 @@ use ZM\Http\Response;
|
||||
use Swoole\WebSocket\Frame;
|
||||
use Swoole\WebSocket\Server;
|
||||
|
||||
/**
|
||||
* Class ModBase
|
||||
* @package ZM
|
||||
* @deprecated
|
||||
*/
|
||||
abstract class ModBase
|
||||
{
|
||||
/** @var Server */
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
namespace ZM\Utils;
|
||||
|
||||
|
||||
use Framework\Console;
|
||||
use Swlib\Saber;
|
||||
use Swoole\Coroutine\Http\Client;
|
||||
|
||||
@@ -11,18 +12,24 @@ class ZMRequest
|
||||
{
|
||||
/**
|
||||
* 使用Swoole协程客户端发起HTTP GET请求
|
||||
* @version 1.1
|
||||
* 返回请求后的body
|
||||
* 如果请求失败或返回状态不是200,则返回 false
|
||||
* @param $url
|
||||
* @param array $headers
|
||||
* @param array $set
|
||||
* @param bool $return_body
|
||||
* @return bool|string|Client
|
||||
* @version 1.1
|
||||
* 返回请求后的body
|
||||
* 如果请求失败或返回状态不是200,则返回 false
|
||||
*/
|
||||
public static function get($url, $headers = [], $set = [], $return_body = true) {
|
||||
$parse = parse_url($url);
|
||||
$cli = new Client($parse["host"], ($parse["scheme"] == "https" ? 443 : (isset($parse["port"]) ? $parse["port"] : 80)), ($parse["scheme"] == "https" ? true : false));
|
||||
if (!isset($parse["host"])) {
|
||||
Console::warning("ZMRequest: url must contains scheme such as \"http(s)\"");
|
||||
return false;
|
||||
}
|
||||
if(!isset($parse["path"])) $parse["path"] = "/";
|
||||
$port = $parse["port"] ?? (($parse["scheme"] ?? "http") == "https" ? 443 : 80);
|
||||
$cli = new Client($parse["host"], $port, (($parse["scheme"] ?? "http") == "https" ? true : false));
|
||||
$cli->setHeaders($headers);
|
||||
$cli->set($set == [] ? ['timeout' => 15.0] : $set);
|
||||
$cli->get($parse["path"] . (isset($parse["query"]) ? "?" . $parse["query"] : ""));
|
||||
@@ -50,7 +57,13 @@ class ZMRequest
|
||||
*/
|
||||
public static function post($url, array $header, $data, $set = [], $return_body = true) {
|
||||
$parse = parse_url($url);
|
||||
$cli = new Client($parse["host"], ($parse["scheme"] == "https" ? 443 : (isset($parse["port"]) ? $parse["port"] : 80)), ($parse["scheme"] == "https" ? true : false));
|
||||
if (!isset($parse["host"])) {
|
||||
Console::warning("ZMRequest: url must contains scheme such as \"http(s)://\"");
|
||||
return false;
|
||||
}
|
||||
if(!isset($parse["path"])) $parse["path"] = "/";
|
||||
$port = $parse["port"] ?? (($parse["scheme"] ?? "http") == "https" ? 443 : 80);
|
||||
$cli = new Client($parse["host"], $port, (($parse["scheme"] ?? "http") == "https" ? true : false));
|
||||
$cli->set($set == [] ? ['timeout' => 15.0] : $set);
|
||||
$cli->setHeaders($header);
|
||||
$cli->post($parse["path"] . (isset($parse["query"]) ? ("?" . $parse["query"]) : ""), $data);
|
||||
@@ -65,6 +78,17 @@ class ZMRequest
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $url
|
||||
* @param array $set
|
||||
* @param array $header
|
||||
* @return ZMWebSocket
|
||||
* @since 1.5
|
||||
*/
|
||||
public static function websocket($url, $set = ['websocket_mask' => true], $header = []) {
|
||||
return new ZMWebSocket($url, $set, $header);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $option
|
||||
* @return Saber
|
||||
@@ -72,4 +96,34 @@ class ZMRequest
|
||||
public static function session($option) {
|
||||
return Saber::session($option);
|
||||
}
|
||||
}
|
||||
|
||||
public static function request($url, $attribute = [], $return_body = true) {
|
||||
$parse = parse_url($url);
|
||||
if (!isset($parse["host"])) {
|
||||
Console::warning("ZMRequest: url must contains scheme such as \"http(s)://\"");
|
||||
return false;
|
||||
}
|
||||
if(!isset($parse["path"])) $parse["path"] = "/";
|
||||
$port = $parse["port"] ?? (($parse["scheme"] ?? "http") == "https" ? 443 : 80);
|
||||
$cli = new Client($parse["host"], $port, (($parse["scheme"] ?? "http") == "https" ? true : false));
|
||||
$cli->set($attribute["set"] ?? ["timeout" => 15.0]);
|
||||
$cli->setMethod($attribute["method"] ?? "GET");
|
||||
$cli->setHeaders($attribute["headers"] ?? []);
|
||||
if(isset($attribute["data"])) $cli->setData($attribute["data"]);
|
||||
if(isset($attribute["file"])) {
|
||||
foreach($attribute["file"] as $k => $v) {
|
||||
$cli->addFile($v["path"], $v["name"], $v["mime_type"] ?? null, $v["filename"] ?? null, $v["offset"] ?? 0, $v["length"] ?? 0);
|
||||
}
|
||||
}
|
||||
$cli->execute($parse["path"] . (isset($parse["query"]) ? "?" . $parse["query"] : ""));
|
||||
if ($return_body) {
|
||||
if ($cli->errCode != 0 || $cli->statusCode != 200) return false;
|
||||
$a = $cli->body;
|
||||
$cli->close();
|
||||
return $a;
|
||||
} else {
|
||||
$cli->close();
|
||||
return $cli;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,7 +24,7 @@ class ZMUtil
|
||||
|
||||
public static function stop($without_shutdown = false) {
|
||||
Console::info(Console::setColor("Stopping server...", "red"));
|
||||
foreach (ZMBuf::$server->connections as $v) {
|
||||
foreach ((ZMBuf::$server->connections ?? []) as $v) {
|
||||
ZMBuf::$server->close($v);
|
||||
}
|
||||
DataProvider::saveBuffer();
|
||||
|
||||
106
src/ZM/Utils/ZMWebSocket.php
Normal file
106
src/ZM/Utils/ZMWebSocket.php
Normal file
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Utils;
|
||||
|
||||
|
||||
use Framework\Console;
|
||||
use Swoole\Coroutine\Http\Client;
|
||||
use Swoole\WebSocket\Frame;
|
||||
|
||||
/**
|
||||
* Class ZMWebSocket
|
||||
* @package ZM\Utils
|
||||
* @since 1.5
|
||||
*/
|
||||
class ZMWebSocket
|
||||
{
|
||||
private $parse;
|
||||
private $client;
|
||||
|
||||
public $is_available = false;
|
||||
|
||||
private $close_func;
|
||||
private $message_func;
|
||||
|
||||
public function __construct($url, $set = ['websocket_mask' => true], $header = []) {
|
||||
$this->parse = parse_url($url);
|
||||
if (!isset($this->parse["host"])) {
|
||||
Console::warning("ZMRequest: url must contains scheme such as \"ws(s)://\"");
|
||||
return;
|
||||
}
|
||||
if (!isset($this->parse["path"])) $this->parse["path"] = "/";
|
||||
$port = $this->parse["port"] ?? (($this->parse["scheme"] ?? "ws") == "wss" ? 443 : 80);
|
||||
$this->client = new Client($this->parse["host"], $port, (($this->parse["scheme"] ?? "ws") == "wss" ? true : false));
|
||||
$this->client->set($set);
|
||||
if ($header != []) $this->client->setHeaders($header);
|
||||
$this->is_available = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return bool
|
||||
*/
|
||||
public function upgrade() {
|
||||
if (!$this->is_available) return false;
|
||||
$r = $this->client->upgrade($this->parse["path"] . (isset($this->parse["query"]) ? ("?" . $this->parse["query"]) : ""));
|
||||
if ($r) {
|
||||
go(function () {
|
||||
while (true) {
|
||||
$result = $this->client->recv(60);
|
||||
if ($result === false) {
|
||||
if ($this->client->connected === false) {
|
||||
go(function () {
|
||||
call_user_func($this->close_func, $this->client);
|
||||
});
|
||||
break;
|
||||
}
|
||||
} elseif ($result instanceof Frame) {
|
||||
go(function () use ($result) {
|
||||
$this->is_available = false;
|
||||
call_user_func($this->message_func, $result, $this->client);
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $callable
|
||||
* @return $this
|
||||
*/
|
||||
public function onMessage(callable $callable) {
|
||||
$this->message_func = $callable;
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callable $callable
|
||||
* @return $this
|
||||
*/
|
||||
public function onClose(callable $callable) {
|
||||
$this->close_func = $callable;
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
||||
if (!debug_backtrace()) {
|
||||
go(function () {
|
||||
require_once __DIR__ . "/../../Framework/Console.php";
|
||||
$cli = new ZMWebSocket("ws://127.0.0.1:20001/");
|
||||
if (!$cli->is_available) die("Error!\n");
|
||||
$cli->onMessage(function (Frame $frame) {
|
||||
var_dump($frame);
|
||||
});
|
||||
$cli->onClose(function () {
|
||||
echo "Connection closed.\n";
|
||||
});
|
||||
if ($cli->upgrade()) {
|
||||
echo "成功连接!\n";
|
||||
} else {
|
||||
echo "连接失败!\n";
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user