initial commit

This commit is contained in:
whale
2020-03-02 16:14:20 +08:00
parent 517082d159
commit 769b87af6a
132 changed files with 5424 additions and 2866 deletions

View File

@@ -0,0 +1,19 @@
<?php
namespace Custom\Annotation;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\Interfaces\CustomAnnotation;
/**
* Class CustomAnnotation
* @Annotation
* @Target("ALL")
* @package Custom\Annotation
*/
class Example implements CustomAnnotation
{
/** @var string */
public $str;
}

View File

@@ -0,0 +1,14 @@
<?php
namespace Custom\Connection;
use ZM\Connection\WSConnection;
class CustomConnection extends WSConnection
{
public function getType() {
return "custom";
}
}

212
src/Framework/Console.php Executable file
View File

@@ -0,0 +1,212 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/2/10
* Time: 下午6:13
*/
namespace Framework;
use co;
use ZM\Utils\ZMUtil;
use Exception;
class Console
{
static function setColor($string, $color = "") {
switch ($color) {
case "red":
return "\x1b[38;5;203m" . $string . "\x1b[m";
case "green":
return "\x1b[38;5;83m" . $string . "\x1b[m";
case "yellow":
return "\x1b[38;5;227m" . $string . "\x1b[m";
case "blue":
return "\033[34m" . $string . "\033[0m";
case "lightpurple":
return "\x1b[38;5;207m" . $string . "\x1b[m";
case "lightblue":
return "\x1b[38;5;87m" . $string . "\x1b[m";
case "gold":
return "\x1b[38;5;214m" . $string . "\x1b[m";
case "gray":
return "\x1b[38;5;59m" . $string . "\x1b[m";
case "pink":
return "\x1b[38;5;207m" . $string . "\x1b[m";
case "lightlightblue":
return "\x1b[38;5;63m" . $string . "\x1b[m";
default:
return $string;
}
}
static function error($obj, $head = null) {
if ($head === null) $head = date("[H:i:s ") . "ERROR] ";
if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) {
$trace = debug_backtrace()[1] ?? ['file' => '', 'function' => ''];
$trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] ";
}
if (!is_string($obj)) {
if (isset($trace)) {
var_dump($obj);
return;
} else $obj = "{Object}";
}
echo(self::setColor($head . ($trace ?? "") . $obj, "red") . "\n");
}
static function warning($obj, $head = null) {
if ($head === null) $head = date("[H:i:s") . " WARN] ";
if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) {
$trace = debug_backtrace()[1] ?? ['file' => '', 'function' => ''];
$trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] ";
}
if (!is_string($obj)) {
if (isset($trace)) {
var_dump($obj);
return;
} else $obj = "{Object}";
}
echo(self::setColor($head . ($trace ?? "") . $obj, "yellow") . "\n");
}
static function info($obj, $head = null) {
if ($head === null) $head = date("[H:i:s ") . "INFO] ";
if (ZMBuf::$info_level !== null && in_array(ZMBuf::$info_level->get(), [1, 2])) {
$trace = debug_backtrace()[1] ?? ['file' => '', 'function' => ''];
$trace = "[" . basename($trace["file"], ".php") . ":" . $trace["function"] . "] ";
}
if (!is_string($obj)) {
if (isset($trace)) {
var_dump($obj);
return;
} else $obj = "{Object}";
}
echo(self::setColor($head . ($trace ?? "") . $obj, "lightblue") . "\n");
}
static function log($obj, $color = "") {
if (!is_string($obj)) var_dump($obj);
else echo(self::setColor($obj, $color) . "\n");
}
static function msg($obj, $self_id = "") {
if (ZMBuf::$info_level !== null && ZMBuf::$info_level->get() == 3) {
if (!isset($obj["post_type"])) {
switch ($obj["action"]) {
case "send_private_msg":
$msg = Console::setColor(date("H:i:s ") . "[" . (ZMBuf::globals("robot_alias")[$self_id] ?? "Null") . "] ", "lightlightblue");
$msg .= Console::setColor("私聊↑(" . $obj["params"]["user_id"] . ")", "lightlightblue");
$msg .= Console::setColor(" > ", "gray");
$msg .= $obj["params"]["message"];
Console::log($msg);
break;
case "send_group_msg":
//TODO: 写新的控制台消息API消息处理
Console::log(Console::setColor("[" . date("H:i:s") . " GROUP:" . $obj["params"]["group_id"] . "] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? ""));
break;
case "send_discuss_msg":
Console::log(Console::setColor("[" . date("H:i:s") . " DISCUSS:" . $obj["params"]["discuss_id"] . "] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? ""));
break;
case "send_msg":
$obj["action"] = "send_" . $obj["message_type"] . "_msg";
self::msg($obj);
break;
case "send_wechat_msg":
Console::log(Console::setColor("[" . date("H:i:s") . " WECHAT] ", "blue") . Console::setColor($obj["params"]["user_id"] ?? "", "yellow") . Console::setColor(" > ", "gray") . ($obj["params"]["message"] ?? ""));
break;
default:
break;
}
} else {
if ($obj["post_type"] == "message") {
switch ($obj["message_type"]) {
case "group":
//
//TODO: 写新的控制台消息event处理
case "private":
case "discuss":
case "wechat":
}
}
}
}
}
static function stackTrace(){
$log = "Stack trace:\n";
$trace = debug_backtrace();
//array_shift($trace);
foreach ($trace as $i => $t) {
if (!isset($t['file'])) {
$t['file'] = 'unknown';
}
if (!isset($t['line'])) {
$t['line'] = 0;
}
if (!isset($t['function'])) {
$t['function'] = 'unknown';
}
$log .= "#$i {$t['file']}({$t['line']}): ";
if (isset($t['object']) and is_object($t['object'])) {
$log .= get_class($t['object']) . '->';
}
$log .= "{$t['function']}()\n";
}
$log = Console::setColor($log, "gray");
echo $log;
}
static function listenConsole(){
go(function () {
while (true) {
$cmd = trim(co::fread(STDIN));
if (self::executeCommand($cmd) === false) break;
}
});
}
/**
* @param string $cmd
* @return bool
*/
private static function executeCommand(string $cmd) {
$it = explodeMsg($cmd);
switch ($it[0] ?? '') {
case 'call':
$class_name = $it[1];
$function_name = $it[2];
$class = new $class_name([]);
call_user_func_array([$class, $function_name],[]);
return true;
case 'bc':
$code = base64_decode($it[1] ?? '', true);
try {
eval($code);
} catch (Exception $e) {
}
return true;
case 'echo':
Console::info($it[1]);
return true;
case 'stop':
ZMUtil::stop();
return false;
case 'reload':
case 'r':
ZMUtil::reload();
return false;
case '':
return true;
default:
Console::info("Command not found: " . $it[0]);
return true;
}
}
public static function withSleep(string $string, int $int) {
self::info($string);
sleep($int);
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace Framework;
use ZM\Event\EventHandler;
use Exception;
use Swoole\WebSocket\Server;
use ZM\Http\Response;
/**
* Class FrameworkLoader
* Everything is beginning from here
* @package Framework
*/
class FrameworkLoader
{
/** @var GlobalConfig */
public static $settings;
/** @var FrameworkLoader|null */
public static $instance = null;
/** @var float|string */
public static $run_time;
/** @var Server */
private $server;
public function __construct($args = []) {
if (self::$instance !== null) die("Cannot run two FrameworkLoader in one process!");
self::$instance = $this;
chdir(__DIR__ . '/../..');
define('WORKING_DIR', getcwd());
$this->requireGlobalFunctions();
$this->registerAutoloader('classLoader');
self::$settings = new GlobalConfig();
ZMBuf::$globals = self::$settings;
if (!self::$settings->success) die("Failed to load global config. Please check config/global.php file");
$this->defineProperties();
//start swoole Framework
$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");
$this->server->set(self::$settings->get("swoole"));
$this->server->on("WorkerStart", [$this, "onWorkerStart"]);
$this->server->on("message", function ($server, $frame) { EventHandler::callSwooleEvent("message", $server, $frame); });
$this->server->on("request", function ($request, $response) {
$response = new Response($response);
EventHandler::callSwooleEvent("request", $request, $response);
});
$this->server->on("open", function ($server, $request) { EventHandler::callSwooleEvent("open", $server, $request); });
$this->server->on("close", function ($server, $fd) { EventHandler::callSwooleEvent("close", $server, $fd); });
ZMBuf::initAtomic();
Console::info("host: ".self::$settings->get("host").", port: ".self::$settings->get("port"));
$this->server->start();
} catch (Exception $e) {
Console::error("Framework初始化出现错误请检查");
Console::error($e->getMessage());
die;
}
}
private function requireGlobalFunctions() {
require __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("CONFIG_DIR", self::$settings->get("config_dir"));
define("CRASH_DIR", self::$settings->get("crash_dir"));
@mkdir(ZM_DATA);
@mkdir(CONFIG_DIR);
@mkdir(CRASH_DIR);
define("ZM_MATCH_ALL", 0);
define("ZM_MATCH_FIRST", 1);
define("ZM_MATCH_NUMBER", 2);
define("ZM_MATCH_SECOND", 3);
}
private function selfCheck() {
if (!extension_loaded("swoole")) die("Can not find swoole extension.\n");
//if (!extension_loaded("gd")) die("Can not find gd extension.\n");
if (!extension_loaded("sockets")) die("Can not find sockets extension.\n");
if (!function_exists("mb_substr")) die("Can not find mbstring extension.\n");
if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n");
//if (!function_exists("curl_exec")) die("Can not find curl extension.\n");
//if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n");
//if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n");
return true;
}
public function onWorkerStart(\Swoole\Server $server, $worker_id) {
self::$instance = $this;
self::$run_time = microtime(true);
EventHandler::callSwooleEvent("WorkerStart", $server, $worker_id);
}
}

View File

@@ -0,0 +1,33 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2019-03-16
* Time: 13:58
*/
namespace Framework;
/**
* 请不要diss此class的语法。可能写的很糟糕。
* Class GlobalConfig
*/
class GlobalConfig
{
private $config = null;
public $success = false;
public function __construct() {
/** @noinspection PhpIncludeInspection */
include_once WORKING_DIR.'/config/global.php';
global $config;
$this->success = true;
$this->config = $config;
}
public function get($key) {
$r = $this->config[$key] ?? null;
if ($r === null) return null;
return $r;
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Framework;
class InfoLevel
{
const INFO = 0;
}

18
src/Framework/Logger.php Normal file
View File

@@ -0,0 +1,18 @@
<?php
namespace Framework;
use Swoole\Coroutine\System;
class Logger
{
private static function getTimeFormat($type = "I") {
return "[" . date("Y-m-d H:i:s") . "]\t[" . $type . "]\t";
}
public static function writeSwooleLog($log) {
System::writeFile(CRASH_DIR . "swoole_error.log", "\n" . self::getTimeFormat() . $log, FILE_APPEND);
}
}

View File

@@ -0,0 +1,264 @@
<?php
namespace Framework;
use Co;
use Exception;
use Swoole\Coroutine;
use swoole\server;
class RemoteShell
{
const STX = "DEBUG";
private static $contexts = array();
static $oriPipeMessageCallback = null;
/**
* @var server
*/
static $serv;
static $menu = array(
"p|print [variant]\t打印一个PHP变量的值",
"e|exec [code]\t执行一段PHP代码",
"w|worker [id]\t切换Worker进程",
"l|list\t打印服务器所有连接的fd",
"s|stats\t打印服务器状态",
"c|coros\t打印协程列表",
"b|bt\t打印协程调用栈",
"i|info [fd]\t显示某个连接的信息",
"h|help\t显示帮助界面",
"q|quit\t退出终端",
);
const PAGESIZE = 20;
/**
* @param $serv server
* @param string $host
* @param int $port
* @throws Exception
* @throws Exception
*/
static function listen($serv, $host = "127.0.0.1", $port = 9599) {
Console::warning("正在监听".$host.":".strval($port)."的调试接口,请注意安全");
$port = $serv->listen($host, $port, SWOOLE_SOCK_TCP);
if (!$port) {
throw new Exception("listen fail.");
}
$port->set(array(
"open_eof_split" => true,
'package_eof' => "\r\n",
));
$port->on("Connect", array(__CLASS__, 'onConnect'));
$port->on("Close", array(__CLASS__, 'onClose'));
$port->on("Receive", array(__CLASS__, 'onReceive'));
if (method_exists($serv, 'getCallback')) {
self::$oriPipeMessageCallback = $serv->getCallback('PipeMessage');
}
$serv->on("PipeMessage", array(__CLASS__, 'onPipeMessage'));
self::$serv = $serv;
}
static function onConnect($serv, $fd, $reactor_id) {
self::$contexts[$fd]['worker_id'] = $serv->worker_id;
self::output($fd, implode("\r\n", self::$menu));
}
static function output($fd, $msg) {
if (!isset(self::$contexts[$fd]['worker_id'])) {
$msg .= "\r\nworker#" . self::$serv->worker_id . "$ ";
} else {
$msg .= "\r\nworker#" . self::$contexts[$fd]['worker_id'] . "$ ";
}
self::$serv->send($fd, $msg);
}
static function onClose($serv, $fd, $reactor_id) {
unset(self::$contexts[$fd]);
}
static function onPipeMessage($serv, $src_worker_id, $message) {
//不是 debug 消息
if (!is_string($message) or substr($message, 0, strlen(self::STX)) != self::STX) {
if (self::$oriPipeMessageCallback == null) {
trigger_error("require swoole-4.3.0 or later.", E_USER_WARNING);
return true;
}
return call_user_func(self::$oriPipeMessageCallback, $serv, $src_worker_id, $message);
} else {
$request = unserialize(substr($message, strlen(self::STX)));
self::call($request['fd'], $request['func'], $request['args']);
}
return true ;
}
static protected function call($fd, $func, $args) {
ob_start();
call_user_func_array($func, $args);
self::output($fd, ob_get_clean());
}
static protected function exec($fd, $func, $args) {
//不在当前Worker进程
if (self::$contexts[$fd]['worker_id'] != self::$serv->worker_id) {
self::$serv->sendMessage(self::STX . serialize(['fd' => $fd, 'func' => $func, 'args' => $args]), self::$contexts[$fd]['worker_id']);
} else {
self::call($fd, $func, $args);
}
}
static function getCoros() {
var_export(iterator_to_array(Coroutine::listCoroutines()));
}
static function getBackTrace($_cid) {
$info = Co::getBackTrace($_cid);
if (!$info) {
echo "coroutine $_cid not found.";
} else {
echo get_debug_print_backtrace($info);
}
}
static function printVariant($var) {
$var = ltrim($var, '$ ');
var_dump($var);
var_dump($$var);
}
static function evalCode($code) {
eval($code . ';');
}
/**
* @param $serv server
* @param $fd
* @param $reactor_id
* @param $data
*/
static function onReceive($serv, $fd, $reactor_id, $data) {
$args = explode(" ", $data, 2);
$cmd = trim($args[0]);
unset($args[0]);
switch ($cmd) {
case 'w':
case 'worker':
if (!isset($args[1])) {
self::output($fd, "invalid command.");
break;
}
$dstWorkerId = intval($args[1]);
self::$contexts[$fd]['worker_id'] = $dstWorkerId;
self::output($fd, "[switching to worker " . self::$contexts[$fd]['worker_id'] . "]");
break;
case 'e':
case 'exec':
if (!isset($args[1])) {
self::output($fd, "invalid command.");
break;
}
$var = trim($args[1]);
self::exec($fd, 'self::evalCode', [$var]);
break;
case 'p':
case 'print':
$var = trim($args[1]);
self::exec($fd, 'self::printVariant', [$var]);
break;
case 'h':
case 'help':
self::output($fd, implode("\r\n", self::$menu));
break;
case 's':
case 'stats':
$stats = $serv->stats();
self::output($fd, var_export($stats, true));
break;
case 'c':
case 'coros':
self::exec($fd, 'self::getCoros', []);
break;
/**
* 查看协程堆栈
*/
case 'bt':
case 'b':
case 'backtrace':
if (empty($args[1])) {
self::output($fd, "invalid command [" . trim($args[1]) . "].");
break;
}
$_cid = intval($args[1]);
self::exec($fd, 'self::getBackTrace', [$_cid]);
break;
case 'i':
case 'info':
if (empty($args[1])) {
self::output($fd, "invalid command [" . trim($args[1]) . "].");
break;
}
$_fd = intval($args[1]);
$info = $serv->getClientInfo($_fd);
if (!$info) {
self::output($fd, "connection $_fd not found.");
} else {
self::output($fd, var_export($info, true));
}
break;
case 'l':
case 'list':
$tmp = array();
foreach ($serv->connections as $fd) {
$tmp[] = $fd;
if (count($tmp) > self::PAGESIZE) {
self::output($fd, json_encode($tmp));
$tmp = array();
}
}
if (count($tmp) > 0) {
self::output($fd, json_encode($tmp));
}
break;
case 'q':
case 'quit':
$serv->close($fd);
break;
default:
self::output($fd, "unknow command[$cmd]");
break;
}
}
}
function get_debug_print_backtrace($traces) {
$ret = array();
foreach ($traces as $i => $call) {
$object = '';
if (isset($call['class'])) {
$object = $call['class'] . $call['type'];
if (is_array($call['args'])) {
foreach ($call['args'] as &$arg) {
get_arg($arg);
}
}
}
$ret[] = '#' . str_pad($i, 3, ' ')
. $object . $call['function'] . '(' . implode(', ', $call['args'])
. ') called at [' . $call['file'] . ':' . $call['line'] . ']';
}
return implode("\n", $ret);
}
function get_arg(&$arg) {
if (is_object($arg)) {
$arr = (array)$arg;
$args = array();
foreach ($arr as $key => $value) {
if (strpos($key, chr(0)) !== false) {
$key = ''; // Private variable found
}
$args[] = '[' . $key . '] => ' . get_arg($value);
}
$arg = get_class($arg) . ' Object (' . implode(',', $args) . ')';
}
}

95
src/Framework/ZMBuf.php Executable file
View File

@@ -0,0 +1,95 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/2/25
* Time: 下午11:11
*/
namespace Framework;
use Swoole\Atomic;
use swoole_atomic;
use ZM\Annotation\MappingNode;
use ZM\connection\WSConnection;
use ZM\Utils\Scheduler;
use ZM\Utils\SQLPool;
class ZMBuf
{
//读写的缓存数据需要在worker_num = 1下才能正常使用
/** @var mixed[] ZMBuf的data */
private static $cache = [];
/** @var WSConnection[] */
static $connect = [];//储存连接实例的数组
//Scheduler计划任务连接实例只可以在单worker_num时使用
/** @var Scheduler|null */
static $scheduler = null;
//Swoole SQL连接池多进程下每个进程一个连接池
/** @var SQLPool */
static $sql_pool = null;//保存sql连接池的类
//只读的数据可以在多worker_num下使用
/** @var null|\Framework\GlobalConfig */
static $globals = null;
// swoole server操作对象每个进程均分配
/** @var swoole_websocket_server $server */
static $server;
/** @var MappingNode Http请求uri路径根节点 */
public static $req_mapping_node;
/** @var mixed TimeNLP初始化后的对象每个进程均可初始化 */
public static $time_nlp;
/** @var string[] $custom_connection_class */
public static $custom_connection_class = [];//保存自定义的ws connection连接类型的
// Atomic可跨进程读写的原子计数任何地方均可使用
/** @var null|swoole_atomic */
static $info_level = null;//保存log等级的原子计数
/** @var swoole_atomic $reload_time */
public static $events = [];
/** @var Atomic[] */
public static $atomics;
static function get($name, $default = null) { return self::$cache[$name] ?? $default; }
static function set($name, $value) { self::$cache[$name] = $value; }
static function append($name, $value) { self::$cache[$name][] = $value; }
static function appendKey($name, $key, $value) { self::$cache[$name][$key] = $value; }
static function appendKeyInKey($name, $key, $value) { self::$cache[$name][$key][] = $value; }
static function unsetCache($name) { unset(self::$cache[$name]); }
static function unsetByValue($name, $vale) {
$key = array_search($vale, self::$cache[$name]);
array_splice(self::$cache[$name], $key, 1);
}
static function isset($name) { return isset(self::$cache[$name]); }
static function array_key_exists($name, $key) { return isset(self::$cache[$name][$key]); }
static function in_array($name, $val) { return in_array($val, self::$cache[$name]); }
static function globals($key) { return self::$globals->get($key); }
public static function resetCache() {
self::$cache = [];
self::$connect = [];
self::$time_nlp = null;
}
/**
* 初始化atomic计数器
*/
public static function initAtomic() {
foreach(ZMBuf::globals("init_atomics") as $k => $v) {
self::$atomics[$k] = new Atomic($v);
}
}
}

View File

@@ -0,0 +1,146 @@
<?php
function classLoader($p) {
$filepath = getClassPath($p);
if ($filepath === null)
echo "F:Warning: get class path wrongs.$p\n";
//else echo "F:DBG: Found " . $p . "\n";
try {
require_once $filepath;
} catch (Exception $e) {
echo "Error when finding class: ".$p.PHP_EOL;
die;
}
}
function getClassPath($class_name) {
$dir = str_replace("\\", "/", $class_name);
$dir = WORKING_DIR . "/src/" . $dir . ".php";
//echo "@@@".$dir.PHP_EOL;
$dir = str_replace("\\", "/", $dir);
if (file_exists($dir)) return $dir;
else return null;
}
/**
* 使用自己定义的万san能分割函数
* @param $msg
* @param bool $ban_comma
* @return array
*/
function explodeMsg($msg, $ban_comma = false) {
$msg = str_replace(" ", "\n", trim($msg));
if (!$ban_comma) {
//$msg = str_replace("", "\n", $msg);
$msg = str_replace("\t", "\n", $msg);
}
$msgs = explode("\n", $msg);
$ls = [];
foreach ($msgs as $k => $v) {
if (trim($v) == "") continue;
$ls[] = trim($v);
}
return $ls;
}
function unicode_decode($str) {
return preg_replace_callback('/\\\\u([0-9a-f]{4})/i', function ($matches) {
return mb_convert_encoding(pack("H*", $matches[1]), "UTF-8", "UCS-2BE");
}, $str);
}
/**
* 获取模块文件夹下的每个类文件的类名称
* @param $dir
* @param string $indoor_name
* @return array
*/
function getAllClasses($dir, $indoor_name) {
$list = scandir($dir);
$classes = [];
unset($list[0], $list[1]);
foreach ($list as $v) {
//echo "Finding " . $dir . $v . PHP_EOL;
//echo "At " . $indoor_name . PHP_EOL;
if (is_dir($dir . $v)) $classes = array_merge($classes, getAllClasses($dir . $v . "/", $indoor_name . "\\" . $v));
elseif (mb_substr($v, -4) == ".php") {
$class_name = $indoor_name . "\\" . mb_substr($v, 0, -4);
$classes [] = $class_name;
}
}
return $classes;
}
function matchPattern($pattern, $context) {
if (mb_substr($pattern, 0, 1) == "" && mb_substr($context, 0, 1) == "")
return true;
if ('*' == mb_substr($pattern, 0, 1) && "" != mb_substr($pattern, 1, 1) && "" == mb_substr($context, 0, 1))
return false;
if (mb_substr($pattern, 0, 1) == mb_substr($context, 0, 1))
return matchPattern(mb_substr($pattern, 1), mb_substr($context, 1));
if (mb_substr($pattern, 0, 1) == "*")
return matchPattern(mb_substr($pattern, 1), $context) || matchPattern($pattern, mb_substr($context, 1));
return false;
}
function split_explode($del, $str, $divide_en = false) {
$str = explode($del, $str);
for ($i = 0; $i < mb_strlen($str[0]); $i++) {
if (
is_numeric(mb_substr($str[0], $i, 1)) &&
(
!is_numeric(mb_substr($str[0], $i - 1, 1)) &&
mb_substr($str[0], $i - 1, 1) != ' ' &&
ctype_alpha(mb_substr($str[0], $i - 1, 1)) === false
)
) {
$str[0] = mb_substr($str[0], 0, $i) . " " . mb_substr($str[0], $i);
} elseif (
$divide_en &&
ctype_alnum(mb_substr($str[0], $i, 1)) &&
!ctype_alnum(mb_substr($str[0], $i - 1, 1)) &&
mb_substr($str[0], $i - 1, 1) != ' '
) {
$str[0] = mb_substr($str[0], 0, $i) . ' ' . mb_substr($str[0], $i);
}
}
$str = implode($del, $str);
//echo $str."\n";
$ls = [];
foreach (explode($del, $str) as $k => $v) {
if (trim($v) == "") continue;
$ls[] = $v;
}
//var_dump($ls);
return $ls == [] ? [""] : $ls;
}
function matchArgs($pattern, $context) {
$result = [];
if (matchPattern($pattern, $context)) {
if (mb_strpos($pattern, "*") === false) return [];
$exp = explode("*", $pattern);
$i = 0;
foreach ($exp as $k => $v) {
//echo "[MATCH$k] " . $v . PHP_EOL;
if ($v == "" && $k == 0) continue;
elseif ($v == "" && $k == count($exp) - 1) {
$context = $context . "^EOL";
$v = "^EOL";
}
$cur_var = "";
//echo mb_substr($context, $i) . "|" . $v . PHP_EOL;
$ori = $i;
while (($a = mb_substr($context, $i, mb_strlen($v))) != $v && $a != "") {
$cur_var .= mb_substr($context, $i, 1);
++$i;
}
if ($i != $ori || $k == 1 || $k == count($exp) - 1) {
//echo $cur_var . PHP_EOL;
$result[] = $cur_var;
}
$i += mb_strlen($v);
}
return $result;
} else return false;
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Module\Example;
use Framework\Console;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Module\Closed;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Connection\CQConnection;
use ZM\ModBase;
/**
* Class Hello
* @package Module\Example
* @Closed()
*/
class Hello extends ModBase
{
/**
* @SwooleEventAt("open",rule="connectType:qq")
* @param $conn
*/
public function onConnect(CQConnection $conn){
Console::info("机器人 ".$conn->getQQ()." 已连接!");
}
/**
* @CQCommand("你好")
*/
public function hello(){
return "你好啊,我是由炸毛框架构建的机器人!";
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Module\TestMod;
use Framework\Console;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\CQ\CQNotice;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Connection\CQConnection;
use ZM\ModBase;
/**
* Class CQTest
* @package Module\TestMod
*/
class CQTest extends ModBase
{
/**
* @SwooleEventAt(type="open",rule="connectType:qq")
* @param CQConnection $conn
*/
public function onRobotConnect($conn){
Console::info("QQ robot: ".$conn->getQQ()." connected.");
}
/**
* @CQCommand("多命令a")
* @CQCommand(regexMatch="*是什么")
* @param $arg
* @return string
* @throws \ZM\Exception\DbException
*/
public function hello($arg) {
return "我也不知道".$arg[0]."是什么呀";
}
/**
* @CQNotice(notice_type="group_admin")
*/
public function onNotice(){
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Module\TestMod;
use Framework\Console;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Connection\WSConnection;
use ZM\ModBase;
/**
* Class Hola
* @package Module\TestMod
* @noinspection PhpUnused
*/
class Hola extends ModBase
{
/**
* @SwooleEventAt(type="open",rule="connectType:unknown")
* @param WSConnection $conn
*/
public function onUnknownConnect() {
Console::warning("Unknown websocket has been shutdown.");
Console::stackTrace();
$this->connection->close();
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace Scheduler;
use Swoole\Coroutine\Http\Client;
use Swoole\WebSocket\Frame;
class MessageEvent
{
/**
* @var Frame
*/
private $frame;
/**
* @var Client
*/
private $client;
public function __construct(Client $client, Frame $frame) {
$this->client = $client;
$this->frame = $frame;
}
public function onActivate() {
//TODO: 写Scheduler计时器内的处理逻辑
}
}

140
src/Scheduler/Scheduler.php Normal file
View File

@@ -0,0 +1,140 @@
<?php
namespace Scheduler;
use Exception;
use Framework\Console;
use Framework\GlobalConfig;
use Swoole\Coroutine;
use Swoole\Coroutine\Http\Client;
use Swoole\Process;
use Swoole\WebSocket\Frame;
class Scheduler
{
const PROCESS = 1;
const REMOTE = 2;
/**
* @var Process
*/
private $process = null;
/**
* @var int
*/
private $m_pid;
private $pid;
/**
* @var Scheduler
*/
private static $instance;
/**
* @var GlobalConfig
*/
private $settings;
/**
* @var Client
*/
private $client;
public function __construct($method = self::PROCESS, $option = []) {
if (self::$instance !== null) die("Cannot run two scheduler in on process!");
self::$instance = $this;
if ($method == self::PROCESS) $this->initProcess();
elseif ($method == self::REMOTE) $this->initRemote();
}
private function initProcess() { //TODO: 完成Process模式的代码
$m_pid = posix_getpid();
$this->process = new Process(function (Process $worker) use ($m_pid) { self::onWork($worker, $m_pid); }, false, 2, true);
$this->pid = $this->process->start();
while (1) {
$ret = Process::wait();
if ($ret) {
$this->process = new Process(function (Process $worker) use ($m_pid) { self::onWork($worker, $m_pid); }, false, 2, true);
$this->pid = $this->process->start();
echo "Reboot done.\n";
}
}
}
private function initRemote() {
define('WORKING_DIR', __DIR__ . '../..');
$this->requireGlobalFunctions();
$this->registerAutoloader('classLoader');
$this->settings = new GlobalConfig();
if (!$this->settings->success) die("Failed to load global config. Please check config/global.php file");
$this->defineProperties();
//start swoole Framework
$this->selfCheck();
try {
$host = $this->settings->get("scheduler")["host"];
$port = $this->settings->get("scheduler")["port"];
$token = $this->settings->get("scheduler")["token"];
$this->client = new Client($host, $port);
$path = "/" . ($token != "" ? ("?token=" . urlencode($token)) : "");
while (true) {
if ($this->client->upgrade($path)) {
while (true) {
$recv = $this->client->recv();
if ($recv instanceof Frame) {
(new MessageEvent($this->client, $recv))->onActivate();
} else {
break;
}
}
} else {
Console::warning("无法连接Framework将在5秒后重连...");
Coroutine::sleep(5);
}
}
} catch (Exception $e) {
Console::error($e);
}
}
private function requireGlobalFunctions() {
/** @noinspection PhpIncludeInspection */
require WORKING_DIR . '/src/Framework/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", $this->settings->get("zm_data"));
//define("CONFIG_DIR", $this->settings->get("config_dir"));
define("CRASH_DIR", $this->settings->get("crash_dir"));
}
private function selfCheck() {
if (!extension_loaded("swoole")) die("Can not find swoole extension.\n");
if (!extension_loaded("sockets")) die("Can not find sockets extension.\n");
if (!function_exists("mb_substr")) die("Can not find mbstring extension.\n");
if (substr(PHP_VERSION, 0, 1) != "7") die("PHP >=7 required.\n");
//if (!class_exists("ZipArchive")) die("Can not find Zip extension.\n");
if (!file_exists(CRASH_DIR . "last_error.log")) die("Can not find log file.\n");
return true;
}
private static function onWork(Process $worker, $m_pid) {
swoole_set_process_name('php-scheduler');
for ($j = 0; $j < 16000; $j++) {
self::checkMpid($worker, $m_pid);
echo "msg: {$j}\n";
sleep(1);
}
}
private static function checkMpid(Process $worker, $m_pid) {
if (!Process::kill($m_pid, 0)) {
$worker->exit(); //主进程死了我也死
// 这句提示,实际是看不到的.需要写到日志中
echo "Master process exited, I [{$worker['pid']}] also quit\n";
}
}
}

View File

@@ -1,10 +1,11 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/8/27
* Time: 7:17 PM
*/
namespace ZM\API;
use Framework\Console;
use ZM\Utils\ZMUtil;
class CQ
{
@@ -147,7 +148,7 @@ class CQ
case "qq":
case "163":
case "xiami":
return "[CQ:music,id=" . $id_or_url . "]";
return "[CQ:music,type=$type,id=$id_or_url]";
case "custom":
if ($title === null || $audio === null) {
Console::error("传入CQ码实例的标题和音频链接不能为空");
@@ -180,18 +181,6 @@ class CQ
return "[CQ:share,url=" . $url . ",title=" . $title . $c . $i . "]";
}
/**
* 将字符串中的CQ码敏感符号进行转义
* @param $str
* @return mixed
*/
public static function encode($str) {
$str = str_replace("&", "&amp;", $str);
$str = str_replace("[", "&#91;", $str);
$str = str_replace("]", "&#93;", $str);
return $str;
}
/**
* 反转义字符串中的CQ码敏感符号
* @param $str
@@ -210,22 +199,22 @@ class CQ
return $str;
}
static function getCQ($msg) {
if (($start = mb_strpos($msg, '[')) === false) return null;
if (($end = mb_strpos($msg, ']')) === false) return null;
$msg = mb_substr($msg, $start + 1, $end - $start - 1);
if (mb_substr($msg, 0, 3) != "CQ:") return null;
$msg = mb_substr($msg, 3);
$msg2 = explode(",", $msg);
$type = array_shift($msg2);
$array = [];
foreach ($msg2 as $k => $v) {
$ss = explode("=", $v);
$sk = array_shift($ss);
$array[$sk] = implode("=", $ss);
}
return ["type" => $type, "params" => $array, "start" => $start, "end" => $end];
/**
* 转义CQ码
* @param $msg
* @return mixed
*/
public static function escape($msg) {
$msg = str_replace("&", "&amp;", $msg);
$msg = str_replace("[", "&#91;", $msg);
$msg = str_replace("]", "&#93;", $msg);
return $msg;
}
public static function removeCQ($msg) {
while (($cq = ZMUtil::getCQ($msg)) !== null) {
$msg = str_replace(mb_substr($msg, $cq["start"], $cq["end"] - $cq["start"] + 1), "", $msg);
}
return $msg;
}
}

249
src/ZM/API/CQAPI.php Normal file
View File

@@ -0,0 +1,249 @@
<?php
namespace ZM\API;
use Co;
use Framework\Console;
use Framework\ZMBuf;
use ZM\Connection\ConnectionManager;
use ZM\Connection\CQConnection;
use ZM\Connection\WSConnection;
/**
* @method static send_private_msg($self_id, $params, $function = null)
* @method static send_group_msg($self_id, $params, $function = null)
* @method static send_discuss_msg($self_id, $params, $function = null)
* @method static send_msg($self_id, $params, $function = null)
* @method static delete_msg($self_id, $params, $function = null)
* @method static send_like($self_id, $params, $function = null)
* @method static set_group_kick($self_id, $params, $function = null)
* @method static set_group_ban($self_id, $params, $function = null)
* @method static set_group_anonymous_ban($self_id, $params, $function = null)
* @method static set_group_whole_ban($self_id, $params, $function = null)
* @method static set_group_admin($self_id, $params, $function = null)
* @method static set_group_anonymous($self_id, $params, $function = null)
* @method static set_group_card($self_id, $params, $function = null)
* @method static set_group_leave($self_id, $params, $function = null)
* @method static set_group_special_title($self_id, $params, $function = null)
* @method static set_discuss_leave($self_id, $params, $function = null)
* @method static set_friend_add_request($self_id, $params, $function = null)
* @method static set_group_add_request($self_id, $params, $function = null)
* @method static get_login_info($self_id, $params, $function = null)
* @method static get_stranger_info($self_id, $params, $function = null)
* @method static get_group_list($self_id, $params, $function = null)
* @method static get_group_member_info($self_id, $params, $function = null)
* @method static get_group_member_list($self_id, $params, $function = null)
* @method static get_cookies($self_id, $params, $function = null)
* @method static get_csrf_token($self_id, $params, $function = null)
* @method static get_credentials($self_id, $params, $function = null)
* @method static get_record($self_id, $params, $function = null)
* @method static get_status($self_id, $params, $function = null)
* @method static get_version_info($self_id, $params, $function = null)
* @method static set_restart($self_id, $params, $function = null)
* @method static set_restart_plugin($self_id, $params, $function = null)
* @method static clean_data_dir($self_id, $params, $function = null)
* @method static clean_plugin_log($self_id, $params, $function = null)
* @method static _get_friend_list($self_id, $params, $function = null)
* @method static _get_group_info($self_id, $params, $function = null)
* @method static _get_vip_info($self_id, $params, $function = null)
* @method static send_private_msg_async($self_id, $params, $function = null)
* @method static send_group_msg_async($self_id, $params, $function = null)
* @method static send_discuss_msg_async($self_id, $params, $function = null)
* @method static send_msg_async($self_id, $params, $function = null)
* @method static delete_msg_async($self_id, $params, $function = null)
* @method static set_group_kick_async($self_id, $params, $function = null)
* @method static set_group_ban_async($self_id, $params, $function = null)
* @method static set_group_anonymous_ban_async($self_id, $params, $function = null)
* @method static set_group_whole_ban_async($self_id, $params, $function = null)
* @method static set_group_admin_async($self_id, $params, $function = null)
* @method static set_group_anonymous_async($self_id, $params, $function = null)
* @method static set_group_card_async($self_id, $params, $function = null)
* @method static set_group_leave_async($self_id, $params, $function = null)
* @method static set_group_special_title_async($self_id, $params, $function = null)
* @method static set_discuss_leave_async($self_id, $params, $function = null)
* @method static set_friend_add_request_async($self_id, $params, $function = null)
* @method static set_group_add_request_async($self_id, $params, $function = null)
*/
class CQAPI
{
public static function quick_reply(WSConnection $conn, $data, $msg, $yield = null) {
switch ($data["message_type"]) {
case "group":
return self::send_group_msg($conn, ["group_id" => $data["group_id"], "message" => $msg], $yield);
case "private":
return self::send_private_msg($conn, ["user_id" => $data["user_id"], "message" => $msg], $yield);
case "discuss":
return self::send_discuss_msg($conn, ["discuss_id" => $data["discuss_id"], "message" => $msg], $yield);
}
return null;
}
public static function __callStatic($name, $arg) {
$all = self::getSupportedAPIs();
$find = null;
if (in_array($name, $all)) $find = $name;
else {
foreach ($all as $v) {
if (strtolower($name) == strtolower(str_replace("_", "", $v))) {
$find = $v;
break;
}
}
}
if ($find === null) {
Console::error("Unknown API " . $name);
return false;
}
$reply = ["action" => $find];
if (!is_array($arg[1])) {
Console::error("Error when parsing params. Please make sure your params is an array.");
return false;
}
if ($arg[1] != []) {
$reply["params"] = $arg[1];
}
if (!($arg[0] instanceof CQConnection)) {
$robot = ConnectionManager::getByType("qq", ["self_id" => $arg[0]]);
if ($robot == []) {
Console::error("发送错误,机器人连接不存在!");
return false;
}
$arg[0] = $robot[0];
}
return self::processAPI($arg[0], $reply, $arg[2] ?? null);
}
/********************** non-API Part **********************/
private static function getSupportedAPIs() {
return [
"send_private_msg",
"send_group_msg",
"send_discuss_msg",
"send_msg",
"delete_msg",
"send_like",
"set_group_kick",
"set_group_ban",
"set_group_anonymous_ban",
"set_group_whole_ban",
"set_group_admin",
"set_group_anonymous",
"set_group_card",
"set_group_leave",
"set_group_special_title",
"set_discuss_leave",
"set_friend_add_request",
"set_group_add_request",
"get_login_info",
"get_stranger_info",
"get_group_list",
"get_group_member_info",
"get_group_member_list",
"get_cookies",
"get_csrf_token",
"get_credentials",
"get_record",
"get_status",
"get_version_info",
"set_restart",
"set_restart_plugin",
"clean_data_dir",
"clean_plugin_log",
"_get_friend_list",
"_get_group_info",
"_get_vip_info",
//异步API
"send_private_msg_async",
"send_group_msg_async",
"send_discuss_msg_async",
"send_msg_async",
"delete_msg_async",
"set_group_kick_async",
"set_group_ban_async",
"set_group_anonymous_ban_async",
"set_group_whole_ban_async",
"set_group_admin_async",
"set_group_anonymous_async",
"set_group_card_async",
"set_group_leave_async",
"set_group_special_title_async",
"set_discuss_leave_async",
"set_friend_add_request_async",
"set_group_add_request_async"
];
}
public static function getLoggedAPIs() {
return [
"send_private_msg",
"send_group_msg",
"send_discuss_msg",
"send_msg",
"send_private_msg_async",
"send_group_msg_async",
"send_discuss_msg_async",
"send_msg_async"
];
}
/**
* @param WSConnection $connection
* @param $reply
* @param |null $function
* @return bool
*/
private static function processAPI($connection, $reply, $function = null) {
$api_id = ZMBuf::$atomics["wait_msg_id"]->get();
$reply["echo"] = $api_id;
ZMBuf::$atomics["wait_msg_id"]->add(1);
if (is_callable($function)) {
ZMBuf::appendKey("sent_api", $api_id, [
"data" => $reply,
"time" => microtime(true),
"func" => $function,
"self_id" => $connection->getQQ()
]);
} elseif ($function === true) {
ZMBuf::appendKey("sent_api", $api_id, [
"data" => $reply,
"time" => microtime(true),
"coroutine" => Co::getuid(),
"self_id" => $connection->getQQ()
]);
} else {
ZMBuf::appendKey("sent_api", $api_id, [
"data" => $reply,
"time" => microtime(true),
"self_id" => $connection->getQQ()
]);
}
if ($connection->push(json_encode($reply))) {
Console::msg($reply, $connection->getQQ());
ZMBuf::$atomics["out_count"]->add(1);
if ($function === true) {
Co::suspend();
$data = ZMBuf::get("sent_api")[$api_id];
ZMBuf::unsetByValue("sent_api", $reply["echo"]);
return isset($data['result']) ? $data['result'] : null;
}
return true;
} else {
$response = [
"status" => "failed",
"retcode" => 999,
"data" => null,
"self_id" => $connection->getQQ()
];
$s = ZMBuf::get("sent_api")[$reply["echo"]];
if (($s["func"] ?? null) !== null)
call_user_func($s["func"], $response, $reply);
ZMBuf::unsetByValue("sent_api", $reply["echo"]);
if ($function === true) return null;
return false;
}
}
}

View File

@@ -0,0 +1,29 @@
<?php
namespace ZM\Annotation;
use Closure;
abstract class AnnotationBase
{
public $method;
public $class;
public function __toString() {
$str = __CLASS__ . ": ";
foreach ($this as $k => $v) {
$str .= "\n\t" . $k . " => ";
if (is_string($v)) $str .= "\"$v\"";
elseif (is_numeric($v)) $str .= $v;
elseif (is_bool($v)) $str .= ($v ? "TRUE" : "FALSE");
elseif (is_array($v)) $str .= json_encode($v, JSON_UNESCAPED_UNICODE);
elseif ($v instanceof Closure) $str .= "@AnonymousFunction";
elseif (is_null($v)) $str .= "NULL";
else $str .= "@Unknown";
}
return $str;
}
}

View File

@@ -0,0 +1,223 @@
<?php
namespace ZM\Annotation;
use Doctrine\Common\Annotations\AnnotationException;
use Doctrine\Common\Annotations\AnnotationReader;
use Framework\ZMBuf;
use ReflectionClass;
use ReflectionException;
use ReflectionMethod;
use ZM\Annotation\CQ\{CQAfter, CQBefore, CQCommand, CQMessage, CQMetaEvent, CQNotice, CQRequest};
use ZM\Annotation\Http\Controller;
use ZM\Annotation\Http\RequestMapping;
use ZM\Annotation\Interfaces\CustomAnnotation;
use ZM\Annotation\Interfaces\Level;
use ZM\Annotation\Module\Closed;
use ZM\Annotation\Module\SaveBuffer;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Annotation\Interfaces\Rule;
use ZM\Connection\WSConnection;
use ZM\Utils\DataProvider;
class AnnotationParser
{
/**
* 注册各个模块类的注解和模块level的排序
* @throws ReflectionException
* @throws AnnotationException
*/
public static function registerMods() {
self::loadAnnotationClasses();
$all_class = getAllClasses(WORKING_DIR . "/src/Module/", "Module");
$reader = new AnnotationReader();
foreach ($all_class as $v) {
$reflection_class = new ReflectionClass($v);
$class_prefix = '';
$methods = $reflection_class->getMethods(ReflectionMethod::IS_PUBLIC);
$class_annotations = $reader->getClassAnnotations($reflection_class);
foreach ($class_annotations as $vs) {
if ($vs instanceof Closed) {
continue 2;
} elseif ($vs instanceof Controller) {
$class_prefix = $vs->prefix;
} elseif ($vs instanceof SaveBuffer) {
DataProvider::addSaveBuffer($vs->buf_name, $vs->sub_folder);
}
}
foreach ($methods as $vs) {
$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);
if ($vss instanceof SwooleEventAt) ZMBuf::$events[SwooleEventAt::class][] = $vss;
elseif ($vss instanceof SwooleEventAfter) ZMBuf::$events[SwooleEventAfter::class][] = $vss;
elseif ($vss instanceof CQMessage) ZMBuf::$events[CQMessage::class][] = $vss;
elseif ($vss instanceof CQNotice) ZMBuf::$events[CQNotice::class][] = $vss;
elseif ($vss instanceof CQRequest) ZMBuf::$events[CQRequest::class][] = $vss;
elseif ($vss instanceof CQMetaEvent) ZMBuf::$events[CQMetaEvent::class][] = $vss;
elseif ($vss instanceof CQCommand) ZMBuf::$events[CQCommand::class][] = $vss;
elseif ($vss instanceof RequestMapping) self::registerRequestMapping($vss, $vs, $reflection_class, $class_prefix);
elseif ($vss instanceof CustomAnnotation) ZMBuf::$events[get_class($vss)][] = $vss;
elseif ($vss instanceof CQBefore) ZMBuf::$events[CQBefore::class][$vss->cq_event][] = $vss;
elseif ($vss instanceof CQAfter) ZMBuf::$events[CQAfter::class][$vss->cq_event][] = $vss;
}
}
}
//给支持level的排个序
foreach (ZMBuf::$events as $class_name => $v) {
if ((new $class_name()) instanceof Level) {
for ($i = 0; $i < count(ZMBuf::$events[$class_name]) - 1; ++$i) {
for ($j = 0; $j < count(ZMBuf::$events[$class_name]) - $i - 1; ++$j) {
$l1 = ZMBuf::$events[$class_name][$j]->level;
$l2 = ZMBuf::$events[$class_name][$j + 1]->level;
if ($l1 < $l2) {
$t = ZMBuf::$events[$class_name][$j + 1];
ZMBuf::$events[$class_name][$j + 1] = ZMBuf::$events[$class_name][$j];
ZMBuf::$events[$class_name][$j] = $t;
}
}
}
}
}
}
private static function getRuleCallback($rule_str) {
$func = null;
$rule = $rule_str;
if ($rule != "") {
$asp = explode(":", $rule);
$asp_name = array_shift($asp);
$rest = implode(":", $asp);
//Swoole 事件时走此switch
switch ($asp_name) {
case "connectType": //websocket连接类型
$func = function (WSConnection $connection) use ($rest) {
return $connection->getType() == $rest ? true : false;
};
break;
case "containsGet": //handle http request事件时才能用
case "containsPost":
$get_list = explode(",", $rest);
if ($asp_name == "containsGet")
$func = function ($request) use ($get_list) {
foreach ($get_list as $v) if (!isset($request->get[$v])) return false;
return true;
};
else
$func = function ($request) use ($get_list) {
foreach ($get_list as $v) if (!isset($request->post[$v])) return false;
return true;
};
/*
if ($controller_prefix != '') {
$p = ZMBuf::$req_mapping_node;
$prefix_exp = explode("/", $controller_prefix);
foreach ($prefix_exp as $k => $v) {
if ($v == "" || $v == ".." || $v == ".") {
unset($prefix_exp[$k]);
}
}
while (($shift = array_shift($prefix_exp)) !== null) {
$p->addRoute($shift, new MappingNode($shift));
$p = $p->getRoute($shift);
}
if ($p->getNodeName() != "/") {
$p->setMethod($method->getName());
$p->setClass($class->getName());
$p->setRule($func);
return "mapped";
}
}*/
break;
case "containsJson": //handle http request事件时才能用
$json_list = explode(",", $rest);
$func = function ($json) use ($json_list) {
foreach ($json_list as $v) if (!isset($json[$v])) return false;
return true;
};
break;
case "dataEqual": //handle websocket message事件时才能用
$func = function ($data) use ($rest) { return $data == $rest; };
break;
}
switch ($asp_name) {
case "msgMatch": //handle cq message事件时才能用
$func = function ($msg) use ($rest) { return matchPattern($rest, $msg); };
break;
case "msgEqual": //handle cq message事件时才能用
$func = function ($msg) use ($rest) { return trim($msg) == $rest; };
break;
}
}
return $func;
}
private static function registerRuleEvent(?AnnotationBase $vss, ReflectionMethod $method, ReflectionClass $class) {
$vss->callback = self::getRuleCallback($vss->getRule());
$vss->method = $method->getName();
$vss->class = $class->getName();
return $vss;
}
private static function registerMethod(?AnnotationBase $vss, ReflectionMethod $method, ReflectionClass $class) {
$vss->method = $method->getName();
$vss->class = $class->getName();
return $vss;
}
private static function registerRequestMapping(RequestMapping $vss, ReflectionMethod $method, ReflectionClass $class, string $prefix) {
$prefix_exp = explode("/", $prefix);
$route_exp = explode("/", $vss->route);
foreach ($prefix_exp as $k => $v) {
if ($v == "" || $v == ".." || $v == ".") {
unset($prefix_exp[$k]);
}
}
foreach ($route_exp as $k => $v) {
if ($v == "" || $v == ".." || $v == ".") {
unset($route_exp[$k]);
}
}
$a = ZMBuf::$req_mapping_node;
$p = $a;
if ($prefix_exp == [] && $route_exp == []) {
$p->setMethod($method->getName());
$p->setClass($class->getName());
$p->setRequestMethod($vss->request_method);
return;
}
while (($shift = array_shift($prefix_exp)) !== null) {
$p->addRoute($shift, new MappingNode($shift));
$p = $p->getRoute($shift);
}
while (($shift = array_shift($route_exp)) !== null) {
if (mb_substr($shift, 0, 1) == "{" && mb_substr($shift, -1, 1) == "}") {
$p->removeAllRoute();
}
$p->addRoute($shift, new MappingNode($shift));
$p = $p->getRoute($shift);
}
$p->setMethod($method->getName());
$p->setClass($class->getName());
$p->setRequestMethod($vss->request_method);
}
private static function loadAnnotationClasses() {
$class = getAllClasses(WORKING_DIR . "/src/ZM/Annotation/", "ZM\\Annotation");
foreach ($class as $v) {
$s = WORKING_DIR . '/src/' . str_replace("\\", "/", $v) . ".php";
require_once $s;
}
$class = getAllClasses(WORKING_DIR . "/src/Custom/Annotation/", "Custom\\Annotation");
foreach ($class as $v) {
$s = WORKING_DIR . '/src/' . str_replace("\\", "/", $v) . ".php";
require_once $s;
}
}
}

View File

@@ -0,0 +1,22 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* Class CQAfter
* @Annotation
* @Target("METHOD")
* @package ZM\Annotation\CQ
*/
class CQAfter extends AnnotationBase
{
/**
* @var string
* @Required()
*/
public $cq_event;
}

View File

@@ -0,0 +1,42 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class CQBefore
* @Annotation
* @Target("METHOD")
* @package ZM\Annotation\CQ
*/
class CQBefore extends AnnotationBase implements Level
{
/**
* @var string
* @Required()
*/
public $cq_event;
public $level = 20;
/**
* @return mixed
*/
public function getLevel() {
return $this->level;
}
/**
* @param mixed $level
*/
public function setLevel($level): void {
$this->level = $level;
}
}

View File

@@ -0,0 +1,35 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class CQCommand
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\CQ
*/
class CQCommand extends AnnotationBase implements Level
{
/** @var string */
public $match = "";
/** @var string */
public $regexMatch = "";
/** @var int */
public $level = 20;
/**
* @return int
*/
public function getLevel(): int { return $this->level; }
/**
* @param int $level
*/
public function setLevel(int $level) { $this->level = $level; }
}

View File

@@ -0,0 +1,41 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class CQMessage
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\CQ
*/
class CQMessage extends AnnotationBase implements Level
{
/**
* @var string
*/
public $message_type = "";
/** @var int */
public $user_id = 0;
/** @var int */
public $group_id = 0;
/** @var int */
public $discuss_id = 0;
/** @var string */
public $message = "";
/** @var string */
public $raw_message = "";
/** @var int */
public $level = 20;
public function getLevel() { return $this->level; }
public function setLevel(int $level) {
$this->level = $level;
}
}

View File

@@ -0,0 +1,40 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class CQMetaEvent
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\CQ
*/
class CQMetaEvent extends AnnotationBase implements Level
{
/**
* @var string
* @Required()
*/
public $meta_event_type = '';
/** @var string */
public $sub_type = '';
/** @var int */
public $level;
/**
* @return mixed
*/
public function getLevel() { return $this->level; }
/**
* @param int $level
*/
public function setLevel(int $level): void {
$this->level = $level;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class CQNotice
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\CQ
*/
class CQNotice extends AnnotationBase implements Level
{
/** @var string */
public $notice_type = "";
/** @var string */
public $sub_type = "";
/** @var int */
public $group_id = 0;
/** @var int */
public $operator_id = 0;
/** @var int */
public $level = 20;
/**
* @return int
*/
public function getLevel(): int {
return $this->level;
}
/**
* @param int $level
*/
public function setLevel(int $level): void {
$this->level = $level;
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace ZM\Annotation\CQ;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
/**
* Class CQRequest
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\CQ
*/
class CQRequest extends AnnotationBase implements Level
{
/** @var string */
public $request_type = "";
/** @var string */
public $sub_type = "";
/** @var int */
public $user_id = 0;
/** @var string */
public $comment = "";
/** @var int */
public $level = 20;
/**
* @return int
*/
public function getLevel(): int {
return $this->level;
}
/**
* @param int $level
*/
public function setLevel(int $level): void {
$this->level = $level;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace ZM\Annotation\Http;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* Class Controller
* @Annotation
* @Target("CLASS")
* @package ZM\Annotation\Http
*/
class Controller extends AnnotationBase
{
/**
* @var string
* @Required()
*/
public $prefix = '';
}

View File

@@ -0,0 +1,39 @@
<?php
namespace ZM\Annotation\Http;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* Class RequestMapping
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\Http
*/
class RequestMapping extends AnnotationBase
{
/**
* @var string
* @Required()
*/
public $route = '';
/**
* @var string
*/
public $name = '';
/**
* @var array
*/
public $request_method = [RequestMethod::GET, RequestMethod::POST];
/**
* Routing path params binding. eg. {"id"="\d+"}
* @var array
*/
public $params = [];
}

View File

@@ -0,0 +1,30 @@
<?php
namespace ZM\Annotation\Http;
use Doctrine\Common\Annotations\Annotation\Required;
use ZM\Annotation\AnnotationBase;
/**
* Class RequestMethod
* @Annotation
*
* @package ZM\Annotation\Http
*/
class RequestMethod extends AnnotationBase
{
/**
* @var string
* @Required()
*/
public $method = self::GET;
public const GET = 'GET';
public const POST = 'POST';
public const PUT = 'PUT';
public const PATCH = 'PATCH';
public const DELETE = 'DELETE';
public const OPTIONS = 'OPTIONS';
public const HEAD = 'HEAD';
}

View File

@@ -0,0 +1,10 @@
<?php
namespace ZM\Annotation\Interfaces;
interface CustomAnnotation
{
}

View File

@@ -0,0 +1,12 @@
<?php
namespace ZM\Annotation\Interfaces;
interface Level
{
public function getLevel();
public function setLevel(int $level);
}

View File

@@ -0,0 +1,10 @@
<?php
namespace ZM\Annotation\Interfaces;
interface Rule
{
public function getRule();
}

View File

@@ -0,0 +1,103 @@
<?php
namespace ZM\Annotation;
use Closure;
class MappingNode
{
private $node;
/** @var MappingNode[] */
private $route = [];
private $method = null;
private $class = null;
private $request_method = [];
/** @var Closure|null */
private $rule = null;
public function __construct(string $node_name) { $this->node = $node_name; }
public function addRoute(string $route_name, MappingNode $route_node) { $this->route[$route_name] = $route_node; }
/**
* @param string $shift
* @return MappingNode|null
*/
public function getRoute(string $shift) {
return $this->route[$shift] ?? null;
}
public function getRealRoute(string $shift, array &$bind_params) {
if (mb_substr(key($this->route), 0, 1) == "{" && mb_substr(key($this->route), -1, 1) == "}") {
$param_name = mb_substr(current($this->route)->getNodeName(), 1, -1);
$bind_params[$param_name] = $shift;
return current($this->route);
} else return $this->route[$shift] ?? null;
}
public function setMethod($method) { $this->method = $method; }
public function setClass($class) { $this->class = $class; }
public function setRequestMethod($method) {
if (is_string($method)) $this->request_method = [$method];
else $this->request_method = $method;
}
public function getNodeName() { return $this->node; }
public function getRule() { return $this->rule; }
public function setRule(Closure $rule): void { $this->rule = $rule; }
public function removeAllRoute() { $this->route = []; }
/**
* @return null
*/
public function getMethod() {
return $this->method;
}
/**
* @return array
*/
public function getRequestMethod(): array {
return $this->request_method;
}
/**
* @return null
*/
public function getClass() {
return $this->class;
}
/**
* @return string
*/
public function getNode(): string {
return $this->node;
}
public function __toString() {
$str = "[" . $this->node . "] => ";
if ($this->class != "" && $this->class != null)
$str .= "\n\t" . $this->class . "->" . $this->method . ": " . implode(", ", $this->request_method);
$str .= "\n\t[Route] => [";
foreach ($this->route as $k => $v) {
$r = $v;
$r = explode("\n", $r);
foreach ($r as $ks => $vs) {
$r[$ks] = "\t" . $r[$ks];
}
$r = implode("\n", $r);
$str .= "\n\t" . $r;
}
$str .= "\n]";
return $str;
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace ZM\Annotation\Module;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
/**
* Class Closed
* @Annotation
* @Target("CLASS")
* @package ZM\Annotation\Module
*/
class Closed extends AnnotationBase
{
}

View File

@@ -0,0 +1,25 @@
<?php
namespace ZM\Annotation\Module;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
/**
* Class SaveBuffer
* @Annotation
* @Target("CLASS")
* @package ZM\Annotation\Module
*/
class SaveBuffer
{
/**
* @var string
*@Required()
*/
public $buf_name;
/** @var string|null $sub_folder */
public $sub_folder = null;
}

View File

@@ -0,0 +1,76 @@
<?php
namespace ZM\Annotation\Swoole;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
use ZM\Annotation\Interfaces\Rule;
/**
* Class SwooleEventAfter
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\Swoole
*/
class SwooleEventAfter extends AnnotationBase implements Rule, Level
{
/**
* @var string
* @Required
*/
public $type;
/** @var string */
public $rule = "";
/** @var int */
public $level = 20;
/**
* @return string
*/
public function getType(): string {
return $this->type;
}
/**
* @param string $type
*/
public function setType(string $type): void {
$this->type = $type;
}
/**
* @return string
*/
public function getRule(): string {
return $this->rule;
}
/**
* @param string $rule
*/
public function setRule(string $rule): void {
$this->rule = $rule;
}
/**
* @return int
*/
public function getLevel(): int {
return $this->level;
}
/**
* @param int $level
*/
public function setLevel(int $level): void {
$this->level = $level;
}
}

View File

@@ -0,0 +1,76 @@
<?php
namespace ZM\Annotation\Swoole;
use Doctrine\Common\Annotations\Annotation\Required;
use Doctrine\Common\Annotations\Annotation\Target;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\Interfaces\Level;
use ZM\Annotation\Interfaces\Rule;
/**
* Class SwooleEventAt
* @Annotation
* @Target("ALL")
* @package ZM\Annotation\Swoole
*/
class SwooleEventAt extends AnnotationBase implements Rule, Level
{
/**
* @var string
* @Required
*/
public $type;
/** @var string */
public $rule = "";
/** @var int */
public $level = 20;
public $callback = null;
/**
* @return string
*/
public function getType(): string {
return $this->type;
}
/**
* @param string $type
*/
public function setType(string $type): void {
$this->type = $type;
}
/**
* @return string
*/
public function getRule(): string {
return $this->rule;
}
/**
* @param string $rule
*/
public function setRule(string $rule): void {
$this->rule = $rule;
}
/**
* @return int
*/
public function getLevel(): int {
return $this->level;
}
/**
* @param int $level
*/
public function setLevel(int $level): void {
$this->level = $level;
}
}

View File

@@ -0,0 +1,23 @@
<?php
namespace ZM\Connection;
class CQConnection extends WSConnection
{
public $self_id = null;
public function __construct($server, $fd, $self_id) {
parent::__construct($server, $fd);
$this->self_id = $self_id;
}
public function getQQ(){
return $this->self_id;
}
public function getType() {
return "qq";
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace ZM\Connection;
use Framework\ZMBuf;
class ConnectionManager
{
/**
* 通过server的fd获取WSConnection实例化对象
* @param int $fd
* @return WSConnection
*/
public static function get(int $fd) {
foreach (ZMBuf::$connect as $v) {
if ($v->fd == $fd) return $v;
}
return null;
}
/**
* @param string $type
* @param array $option
* @return WSConnection[]
*/
public static function getByType(string $type, $option = []) {
$conn = [];
foreach (ZMBuf::$connect as $v) {
foreach ($option as $ks => $vs) {
if (($v->$ks ?? "") == $vs) continue;
else continue 2;
}
if ($v->getType() == $type) $conn[] = $v;
}
return $conn;
}
public static function getTypeClassName(string $type) {
switch (strtolower($type)) {
case "qq":
case "universal":
return CQConnection::class;
case "webconsole":
return WCConnection::class;
case "proxy":
return ProxyConnection::class;
default:
foreach (ZMBuf::$custom_connection_class as $v) {
/** @var WSConnection $r */
$r = new $v(ZMBuf::$server, -1);
if ($r->getType() == strtolower($type)) return $v;
}
return UnknownConnection::class;
}
}
public static function close($fd) {
foreach (ZMBuf::$connect as $k => $v) {
if ($v->fd == $fd) {
ZMBuf::$server->close($fd);
unset(ZMBuf::$connect[$k]);
break;
}
}
}
public static function registerCustomClass() {
$classes = getAllClasses(WORKING_DIR . "/src/Custom/Connection/", "Custom\\Connection");
ZMBuf::$custom_connection_class = $classes;
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace ZM\Connection;
class ProxyConnection extends WSConnection
{
public function getType() {
return "proxy";
}
}

View File

@@ -0,0 +1,13 @@
<?php
namespace ZM\Connection;
class UnknownConnection extends WSConnection
{
public function getType() {
return "unknown";
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace ZM\Connection;
class WCConnection extends WSConnection
{
public function getType() { return "wc"; }
}

View File

@@ -0,0 +1,52 @@
<?php
namespace ZM\Connection;
use Framework\Console;
use Framework\Logger;
use swoole_websocket_server;
abstract class WSConnection
{
public $fd;
/** @var swoole_websocket_server */
protected $server;
public $available = false;
public function __construct($server, $fd) {
$this->server = $server;
$this->fd = $fd;
}
public abstract function getType();
public function exists() {
return $this->available = $this->server->exist($this->fd);
}
public function close() {
ConnectionManager::close($this->fd);
}
public function push($data, $push_error_record = true) {
if ($data === null || $data == "") {
Console::warning("推送了空消息");
return false;
}
if (!$this->server->exist($this->fd)) {
Console::warning("Swoole 原生 websocket连接池中无此连接");
return false;
}
if ($this->server->push($this->fd, $data) === false) {
$data = unicode_decode($data);
if ($push_error_record) Logger::writeSwooleLog("API push failed. Data: " . $data);
Console::error("websocket数据未成功推送长度" . strlen($data));
return false;
}
return true;
}
}

120
src/ZM/DB/DB.php Normal file
View File

@@ -0,0 +1,120 @@
<?php
namespace ZM\DB;
use framework\Console;
use framework\ZMBuf;
use Swoole\Coroutine;
use Swoole\Coroutine\MySQL\Statement;
use ZM\Exception\DbException;
class DB
{
private static $table_list = [];
public static function initTableList() {
$result = self::rawQuery("select TABLE_NAME from INFORMATION_SCHEMA.TABLES where TABLE_SCHEMA='" . ZMBuf::globals("sql_config")["sql_database"] . "';", []);
foreach ($result as $v) {
self::$table_list[] = $v['TABLE_NAME'];
}
}
/**
* @param $table_name
* @param bool $enable_cache
* @return Table
* @throws DbException
*/
public static function table($table_name, $enable_cache = null) {
if (Table::getTableInstance($table_name) === null) {
if (in_array($table_name, self::$table_list))
return new Table($table_name, $enable_cache ?? ZMBuf::globals("sql_config")["sql_enable_cache"]);
elseif(ZMBuf::$sql_pool !== null){
throw new DbException("Table " . $table_name . " not exist in database.");
} else {
throw new DbException("Database connection not exist or connect failed. Please check sql configuration");
}
}
return Table::getTableInstance($table_name);
}
public static function statement($line) {
self::rawQuery($line, []);
}
public static function unprepared($line) {
if (ZMBuf::get("sql_log") === true) {
$starttime = microtime(true);
}
try {
$conn = ZMBuf::$sql_pool->get();
if ($conn === false) {
throw new DbException("无法连接SQL" . $line);
}
$result = $conn->query($line) === false ? false : ($conn->errno != 0 ? false : true);
ZMBuf::$sql_pool->put($conn);
return $result;
} catch (DBException $e) {
if (ZMBuf::get("sql_log") === true) {
$log =
"[" . date("Y-m-d H:i:s") .
" " . round(microtime(true) - $starttime, 5) .
"] " . $line . " (Error:" . $e->getMessage() . ")\n";
Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND);
}
Console::error($e->getMessage());
return false;
}
}
public static function rawQuery(string $line, $params) {
if (ZMBuf::get("sql_log") === true) {
$starttime = microtime(true);
}
try {
$conn = ZMBuf::$sql_pool->get();
if ($conn === false) {
throw new DbException("无法连接SQL" . $line);
}
$ps = $conn->prepare($line);
if ($ps === false) {
$conn->close();
ZMBuf::$sql_pool->connect_cnt -= 1;
throw new DbException("SQL语句查询错误" . $line . ",错误信息:" . $conn->error);
} else {
if (!($ps instanceof Statement)) {
throw new DbException("语句查询错误!" . $line);
}
if ($params == []) $result = $ps->execute();
elseif (!is_array($params)) {
$result = $ps->execute([$params]);
} else $result = $ps->execute($params);
ZMBuf::$sql_pool->put($conn);
if ($ps->errno != 0) {
throw new DBException("语句[$line]错误!" . $ps->error);
//echo json_encode(debug_backtrace(), 128 | 256);
}
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) . "\n";
Coroutine::writeFile(CRASH_DIR . "sql.log", $log, FILE_APPEND);
}
return $result;
}
} catch (DBException $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);
}
Console::error($e->getMessage());
return false;
}
}
}

28
src/ZM/DB/DeleteBody.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
namespace ZM\DB;
class DeleteBody
{
use WhereBody;
/**
* @var Table
*/
private $table;
/**
* DeleteBody constructor.
* @param Table $table
*/
public function __construct(Table $table) {
$this->table = $table;
}
public function save() {
list($sql, $param) = $this->getWhereSQL();
return DB::rawQuery("DELETE FROM " . $this->table->getTableName() . " WHERE " . $sql, $param);
}
}

28
src/ZM/DB/InsertBody.php Normal file
View File

@@ -0,0 +1,28 @@
<?php
namespace ZM\DB;
class InsertBody
{
/**
* @var Table
*/
private $table;
private $row;
/**
* InsertBody constructor.
* @param Table $table
* @param $row
*/
public function __construct(Table $table, $row) {
$this->table = $table;
$this->row = $row;
}
public function save() {
DB::rawQuery('INSERT INTO ' . $this->table->getTableName() . ' VALUES ('.implode(',', array_fill(0, 5, '?')).')', $this->row);
}
}

88
src/ZM/DB/SelectBody.php Normal file
View File

@@ -0,0 +1,88 @@
<?php
namespace ZM\DB;
use Framework\Console;
class SelectBody
{
use WhereBody;
/** @var Table */
private $table;
private $select_thing;
private $result = null;
public function __construct($table, $select_thing) {
$this->table = $table;
$this->select_thing = $select_thing;
}
public function get() { return $this->fetchAll(); }
public function fetchAll() {
if ($this->table->isCacheEnabled()) {
$rr = md5(implode(",", $this->select_thing) . serialize($this->where_thing));
if (array_key_exists($rr, $this->table->cache)) {
Console::info('SQL query cached: ' . $rr, date("[H:i:s ") . 'DB] ');
return $this->table->cache[$rr]->getResult();
}
}
$this->execute();
if ($this->table->isCacheEnabled() && !in_array($rr, $this->table->cache)) {
$this->table->cache[$rr] = $this;
}
return $this->getResult();
}
public function fetchFirst() {
return $this->fetchAll()[0] ?? null;
}
public function value() {
$r = $this->fetchFirst();
if ($r === null) return null;
return current($r);
}
public function execute() {
$str = $this->queryPrepare();
$this->result = DB::rawQuery($str[0], $str[1]);
}
public function getResult() { return $this->result; }
public function equals(SelectBody $body) {
if ($this->select_thing != $body->getSelectThing()) return false;
elseif ($this->where_thing == $body->getWhereThing()) return false;
else return true;
}
/**
* @return mixed
*/
public function getSelectThing() { return $this->select_thing; }
/**
* @return array
*/
public function getWhereThing() { return $this->where_thing; }
private function queryPrepare() {
$msg = "SELECT " . implode(", ", $this->select_thing) . " FROM " . $this->table->getTableName();
$sql = $this->table->paintWhereSQL($this->where_thing['='] ?? [], '=');
if ($sql[0] != '') {
$msg .= " WHERE " . $sql[0];
$array = $sql[1];
$sql = $this->table->paintWhereSQL($this->where_thing['!='] ?? [], '!=');
if ($sql[0] != '') $msg .= " AND " . $sql[0];
$array = array_merge($array, $sql[1]);
}
return [$msg, $array ?? []];
}
}

79
src/ZM/DB/Table.php Normal file
View File

@@ -0,0 +1,79 @@
<?php
namespace ZM\DB;
class Table
{
private $table_name;
/** @var SelectBody[] */
public $cache = [];
private static $table_instance = [];
private $enable_cache;
public function __construct($table_name, $enable_cache) {
$this->enable_cache = $enable_cache;
$this->table_name = $table_name;
self::$table_instance[$table_name] = $this;
}
public static function getTableInstance($table_name) {
if (isset(self::$table_instance[$table_name])) return self::$table_instance[$table_name];
else return null;
}
public function select($what = []) {
return new SelectBody($this, $what == [] ? ["*"] : $what);
}
public function where($column, $operation_or_value, $value = null){
return (new SelectBody($this, ["*"]))->where($column, $operation_or_value, $value);
}
public function insert($row) {
$this->cache = [];
return new InsertBody($this, $row);
}
public function update(array $set_value) {
$this->cache = [];
return new UpdateBody($this, $set_value);
}
public function delete() {
$this->cache = [];
return new DeleteBody($this);
}
public function statement($line){
$this->cache = [];
//TODO: 无返回的statement语句
}
public function paintWhereSQL($rule, $operator) {
if ($rule == []) return ["", []];
$msg = "";
$param = [];
foreach ($rule as $k => $v) {
if ($msg == "") {
$msg .= $k . " $operator ? ";
} else {
$msg .= "AND " . $k . " $operator ?";
}
$param[] = $v;
}
return [$msg, $param];
}
/**
* @return mixed
*/
public function getTableName() { return $this->table_name; }
public function isCacheEnabled() { return $this->enable_cache; }
}

50
src/ZM/DB/UpdateBody.php Normal file
View File

@@ -0,0 +1,50 @@
<?php
namespace ZM\DB;
use ZM\Exception\DbException;
class UpdateBody
{
use WhereBody;
/**
* @var Table
*/
private $table;
/**
* @var array
*/
private $set_value;
/**
* UpdateBody constructor.
* @param Table $table
* @param array $set_value
*/
public function __construct(Table $table, array $set_value) {
$this->table = $table;
$this->set_value = $set_value;
}
/**
* @throws DbException
*/
public function save(){
$arr = [];
$msg = [];
foreach($this->set_value as $k => $v) {
$msg []= $k .' = ?';
$arr[]=$v;
}
if(($msg ?? []) == []) throw new DbException('update value sets can not be empty!');
$line = 'UPDATE '.$this->table->getTableName().' SET '.implode(', ', $msg);
if($this->where_thing != []) {
list($sql, $param) = $this->getWhereSQL();
$arr = array_merge($arr, $param);
$line .= ' WHERE '.$sql;
}
return DB::rawQuery($line, $arr);
}
}

33
src/ZM/DB/WhereBody.php Normal file
View File

@@ -0,0 +1,33 @@
<?php
namespace ZM\DB;
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;
else $this->where_thing['='][$column] = $operation_or_value;
return $this;
}
protected function getWhereSQL(){
$param = [];
$msg = '';
foreach($this->where_thing as $k => $v) {
foreach($v as $ks => $vs) {
if($param != []) {
$msg .= ' AND '.$ks ." $k ?";
} else {
$msg .= "$ks $k ?";
}
$param []=$vs;
}
}
return [$msg, $param];
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace ZM\DBCache;
class CourseCache implements DBCache
{
/**
* @var array
*/
private static $data;
public static function reset() {
self::$data = [];
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace ZM\DBCache;
interface DBCache
{
public static function reset();
}

View File

@@ -0,0 +1,15 @@
<?php
namespace ZM\DBCache;
class DBCacheManager
{
public static function freeAllCache(){
DHUerCache::reset();
UserCache::reset();
GroupCache::reset();
CourseCache::reset();
}
}

View File

@@ -0,0 +1,17 @@
<?php
namespace ZM\DBCache;
class DHUerCache implements DBCache
{
/**
* @var array
*/
private static $data;
public static function reset() {
self::$data = [];
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace ZM\DBCache;
class GroupCache implements DBCache
{
/**
* @var array
*/
private static $data;
public static function reset() {
self::$data = [];
}
}

View File

@@ -0,0 +1,18 @@
<?php
namespace ZM\DBCache;
class UserCache implements DBCache
{
/**
* @var array
*/
private static $data;
public static function reset() {
self::$data = [];
}
}

View File

@@ -0,0 +1,149 @@
<?php
namespace ZM\Event\CQ;
use Co;
use Framework\ZMBuf;
use ZM\Annotation\CQ\CQAfter;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\CQ\CQMessage;
use ZM\Connection\ConnectionManager;
use ZM\Exception\WaitTimeoutException;
use ZM\ModBase;
use ZM\ModHandleType;
class MessageEvent
{
private $function_call = false;
private $data;
private $circle;
/**
* @var \ZM\Event\Swoole\MessageEvent
*/
private $swoole_event;
public function __construct($data, \ZM\Event\Swoole\MessageEvent $event, $circle = 0) {
$this->data = $data;
$this->swoole_event = $event;
$this->circle = $circle;
}
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class][CQMessage::class] ?? [] as $v) {
$c = $v->class;
$class = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], ModHandleType::CQ_MESSAGE);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
}
foreach (ZMBuf::get("wait_api", []) as $k => $v) {
if($this->data["user_id"] == $v["user_id"] &&
$this->data["self_id"] == $v["self_id"] &&
$this->data["message_type"] == $v["message_type"] &&
($this->data[$this->data["message_type"]."_id"] ?? $this->data["user_id"]) ==
($v[$v["message_type"]."_id"] ?? $v["user_id"])){
$v["result"] = $this->data["message"];
ZMBuf::appendKey("wait_api", $k, $v);
Co::resume($v["coroutine"]);
return false;
}
}
return true;
}
/** @noinspection PhpRedundantCatchClauseInspection */
public function onActivate() {
try {
$word = split_explode(" ", str_replace("\r", "", $this->data["message"]));
if (count(explode("\n", $word[0])) >= 2) {
$enter = explode("\n", $this->data["message"]);
$first = split_explode(" ", array_shift($enter));
$word = array_merge($first, $enter);
foreach ($word as $k => $v) {
$word[$k] = trim($word[$k]);
}
}
/** @var ModBase[] $obj */
$obj = [];
foreach (ZMBuf::$events[CQCommand::class] ?? [] as $v) {
/** @var CQCommand $v */
if ($v->match == "" && $v->regexMatch == "") continue;
else {
$c = $v->class;
if (!isset($obj[$c]))
$obj[$c] = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], ModHandleType::CQ_MESSAGE);
if ($word[0] != "" && $v->match == $word[0]) {
$r = call_user_func([$obj[$c], $v->method], $word);
if (is_string($r)) $obj[$c]->reply($r);
$this->function_call = true;
return;
} elseif (($args = matchArgs($v->regexMatch, $this->data["message"])) !== false) {
$r = call_user_func([$obj[$c], $v->method], $args);
if (is_string($r)) $obj[$c]->reply($r);
$this->function_call = true;
return;
}
}
}
foreach (ZMBuf::$events[CQMessage::class] ?? [] as $v) {
/** @var CQMessage $v */
if (
($v->message == '' || ($v->message != '' && $v->message == $this->data["message"])) &&
($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == $this->data["user_id"])) &&
($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == ($this->data["group_id"] ?? 0))) &&
($v->discuss_id == 0 || ($v->discuss_id != 0 && $v->discuss_id == ($this->data["discuss_id"] ?? 0))) &&
($v->message_type == '' || ($v->message_type != '' && $v->message_type == $this->data["message_type"])) &&
($v->raw_message == '' || ($v->raw_message != '' && $v->raw_message == $this->data["raw_message"]))) {
$c = $v->class;
if (!isset($obj[$c]))
$obj[$c] = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], ModHandleType::CQ_MESSAGE);
$r = call_user_func([$obj[$c], $v->method], $this->data["message"]);
if (is_string($r)) $obj[$c]->reply($r);
if ($obj[$c]->block_continue) return;
}
}
} catch (WaitTimeoutException $e) {
$e->module->finalReply($e->getMessage());
}
}
/**
* 在调用完事件后执行的
*/
public function onAfter() {
foreach (ZMBuf::$events[CQAfter::class][CQMessage::class] ?? [] as $v) {
$c = $v->class;
$class = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], ModHandleType::CQ_MESSAGE);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
}
return true;
}
public function hasReply() {
return $this->function_call;
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace ZM\Event\CQ;
use Framework\ZMBuf;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQMetaEvent;
use ZM\Connection\ConnectionManager;
use ZM\Exception\WaitTimeoutException;
use ZM\ModBase;
use ZM\ModHandleType;
class MetaEvent
{
private $data;
/** @var \ZM\Event\Swoole\MessageEvent */
private $swoole_event;
private $circle;
public function __construct($data, \ZM\Event\Swoole\MessageEvent $event, $circle = 0) {
$this->data = $data;
$this->swoole_event = $event;
$this->circle = $circle;
}
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class][CQMetaEvent::class] ?? [] as $v) {
$c = $v->class;
/** @var CQMetaEvent $v */
$class = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], ModHandleType::CQ_META_EVENT);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
}
return true;
}
/** @noinspection PhpRedundantCatchClauseInspection */
public function onActivate() {
try {
/** @var ModBase[] $obj */
$obj = [];
foreach (ZMBuf::$events[CQMetaEvent::class] ?? [] as $v) {
/** @var CQMetaEvent $v */
if (
($v->meta_event_type == '' || ($v->meta_event_type != '' && $v->meta_event_type == $this->data["meta_event_type"])) &&
($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"]))) {
$c = $v->class;
if (!isset($obj[$c]))
$obj[$c] = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], ModHandleType::CQ_META_EVENT);
$r = call_user_func([$obj[$c], $v->method]);
if (is_string($r)) $obj[$c]->reply($r);
if ($obj[$c]->block_continue) return;
}
}
} catch (WaitTimeoutException $e) {
$e->module->finalReply($e->getMessage());
}
}
}

View File

@@ -0,0 +1,88 @@
<?php
namespace ZM\Event\CQ;
use Framework\ZMBuf;
use ZM\Annotation\CQ\CQAfter;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQNotice;
use ZM\Connection\ConnectionManager;
use ZM\Exception\WaitTimeoutException;
use ZM\ModBase;
use ZM\ModHandleType;
class NoticeEvent
{
private $data;
/** @var \ZM\Event\Swoole\MessageEvent */
private $swoole_event;
private $circle;
public function __construct($data, \ZM\Event\Swoole\MessageEvent $event, $circle = 0) {
$this->data = $data;
$this->swoole_event = $event;
$this->circle = $circle;
}
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class][CQNotice::class] ?? [] as $v) {
$c = $v->class;
/** @var CQNotice $v */
$class = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], ModHandleType::CQ_NOTICE);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
}
return true;
}
public function onActivate() {
try {
/** @var ModBase[] $obj */
$obj = [];
foreach (ZMBuf::$events[CQNotice::class] ?? [] as $v) {
/** @var CQNotice $v */
if (
($v->notice_type == '' || ($v->notice_type != '' && $v->notice_type == $this->data["notice_type"])) &&
($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"])) &&
($v->group_id == 0 || ($v->group_id != 0 && $v->group_id == ($this->data["group_id"] ?? 0))) &&
($v->operator_id == 0 || ($v->operator_id != 0 && $v->operator_id == ($this->data["operator_id"] ?? 0)))) {
$c = $v->class;
if (!isset($obj[$c]))
$obj[$c] = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], ModHandleType::CQ_NOTICE);
$r = call_user_func([$obj[$c], $v->method]);
if (is_string($r)) $obj[$c]->reply($r);
if ($obj[$c]->block_continue) return;
}
}
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) {
$e->module->finalReply($e->getMessage());
}
}
public function onAfter() {
foreach (ZMBuf::$events[CQAfter::class][CQNotice::class] ?? [] as $v) {
$c = $v->class;
$class = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], ModHandleType::CQ_NOTICE);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
}
return true;
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace ZM\Event\CQ;
use Framework\ZMBuf;
use ZM\Annotation\CQ\CQAfter;
use ZM\Annotation\CQ\CQBefore;
use ZM\Annotation\CQ\CQRequest;
use ZM\Connection\ConnectionManager;
use ZM\Exception\WaitTimeoutException;
use ZM\ModBase;
use ZM\ModHandleType;
class RequestEvent
{
private $data;
/** @var \ZM\Event\Swoole\MessageEvent */
private $swoole_event;
private $circle;
public function __construct($data, \ZM\Event\Swoole\MessageEvent $event, $circle = 0) {
$this->data = $data;
$this->swoole_event = $event;
$this->circle = $circle;
}
public function onBefore() {
foreach (ZMBuf::$events[CQBefore::class][CQRequest::class] ?? [] as $v) {
$c = $v->class;
/** @var CQRequest $v */
$class = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], ModHandleType::CQ_REQUEST);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
}
return true;
}
/** @noinspection PhpRedundantCatchClauseInspection */
public function onActivate() {
try {
/** @var ModBase[] $obj */
$obj = [];
foreach (ZMBuf::$events[CQRequest::class] ?? [] as $v) {
/** @var CQRequest $v */
if (
($v->request_type == '' || ($v->request_type != '' && $v->request_type == $this->data["request_type"])) &&
($v->sub_type == 0 || ($v->sub_type != 0 && $v->sub_type == $this->data["sub_type"])) &&
($v->user_id == 0 || ($v->user_id != 0 && $v->user_id == ($this->data["user_id"] ?? 0))) &&
($v->comment == 0 || ($v->comment != 0 && $v->comment == ($this->data["comment"] ?? 0)))) {
$c = $v->class;
if (!isset($obj[$c]))
$obj[$c] = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], ModHandleType::CQ_REQUEST);
$r = call_user_func([$obj[$c], $v->method]);
if (is_string($r)) $obj[$c]->reply($r);
if ($obj[$c]->block_continue) return;
}
}
} catch (WaitTimeoutException $e) {
$e->module->finalReply($e->getMessage());
}
}
public function onAfter() {
foreach (ZMBuf::$events[CQAfter::class][CQRequest::class] ?? [] as $v) {
$c = $v->class;
$class = new $c([
"data" => $this->data,
"frame" => $this->swoole_event->frame,
"server" => $this->swoole_event->server,
"connection" => ConnectionManager::get($this->swoole_event->frame->fd)
], ModHandleType::CQ_REQUEST);
$r = call_user_func_array([$class, $v->method], []);
if (!$r || $class->block_continue) return false;
}
return true;
}
}

11
src/ZM/Event/Event.php Normal file
View File

@@ -0,0 +1,11 @@
<?php
namespace ZM\Event;
interface Event
{
const SWOOLE = 1;
const CQ = 2;
}

View File

@@ -0,0 +1,115 @@
<?php
namespace ZM\Event;
use Co;
use Exception;
use Framework\Console;
use Framework\ZMBuf;
use ZM\Event\Swoole\{MessageEvent, RequestEvent, WorkerStartEvent, WSCloseEvent, WSOpenEvent};
use ZM\Http\Response;
use ZM\Utils\ZMUtil;
class EventHandler
{
public static function callSwooleEvent($event_name, $param0, $param1 = null) {
$starttime = microtime(true);
$event_name = strtolower($event_name);
switch ($event_name) {
case "workerstart":
try {
(new WorkerStartEvent($param0, $param1))->onActivate()->onAfter();
Console::log("\n=== Worker #" . $param0->worker_id . " 已启动 ===\n", "gold");
} catch (Exception $e) {
Console::error("Worker加载出错停止服务");
Console::error($e->getMessage() . "\n" . $e->getTraceAsString());
ZMUtil::stop();
return;
}
break;
case "message":
(new MessageEvent($param0, $param1))->onActivate()->onAfter();
break;
case "request":
try {
(new RequestEvent($param0, $param1))->onActivate()->onAfter();
} catch (Exception $e) {
/** @var Response $param1 */
$param1->status(500);
if (!$param1->isEnd()) $param1->end("Internal server error: " . $e->getMessage());
Console::error("Internal server error (500), caused by uncaught exception.");
Console::log($e->getTraceAsString(), "gray");
}
break;
case "open":
(new WSOpenEvent($param0, $param1))->onActivate()->onAfter();
break;
case "close":
(new WSCloseEvent($param0, $param1))->onActivate()->onAfter();
break;
}
Console::info(Console::setColor("Event: " . $event_name . " 运行了 " . round(microtime(true) - $starttime, 5) . "", "gold"));
}
public static function callCQEvent($event_data, MessageEvent $event, $level = 0) {
if ($level >= 5) {
Console::warning("Recursive call reached " . $level . " times");
Console::stackTrace();
return false;
}
$starttime = microtime(true);
switch ($event_data["post_type"]) {
case "message":
$event = new CQ\MessageEvent($event_data, $event, $level);
if ($event->onBefore()) $event->onActivate();
$event->onAfter();
return $event->hasReply();
break;
case "notice":
$event = new CQ\NoticeEvent($event_data, $event, $level);
if($event->onBefore()) $event->onActivate();
$event->onAfter();
return true;
case "request":
$event = new CQ\RequestEvent($event_data, $event, $level);
if($event->onBefore()) $event->onActivate();
$event->onAfter();
return true;
case "meta_event":
$event = new CQ\MetaEvent($event_data, $event, $level);
if($event->onBefore()) $event->onActivate();
return true;
}
unset($starttime);
return false;
}
public static function callCQResponse($req) {
//Console::info("收到来自API连接的回复".json_encode($req, 128|256));
if(isset($req["echo"]) && ZMBuf::array_key_exists("sent_api", $req["echo"])) {
$status = $req["status"];
$retcode = $req["retcode"];
$data = $req["data"];
$origin = ZMBuf::get("sent_api")[$req["echo"]];
$self_id = $origin["self_id"];
$response = [
"status" => $status,
"retcode" => $retcode,
"data" => $data,
"self_id" => $self_id
];
if (($origin["func"] ?? null) !== null) {
call_user_func($origin["func"], $response, $origin["data"]);
} elseif (($origin["coroutine"] ?? false) !== false) {
$p = ZMBuf::get("sent_api");
$p[$req["echo"]]["result"] = $response;
ZMBuf::set("sent_api", $p);
Co::resume($origin['coroutine']);
}
ZMBuf::unsetByValue("sent_api", $req["echo"]);
}
}
}

View File

@@ -0,0 +1,94 @@
<?php
namespace ZM\Event\Swoole;
use Closure;
use Framework\Console;
use Framework\ZMBuf;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Connection\ConnectionManager;
use Exception;
use ZM\Event\EventHandler;
use ZM\ModBase;
use ZM\ModHandleType;
use ZM\Utils\ZMUtil;
class MessageEvent implements SwooleEvent
{
/**
* @var Server
*/
public $server;
/**
* @var Frame
*/
public $frame;
public function __construct(Server $server, Frame $frame) {
$this->server = $server;
$this->frame = $frame;
}
/**
* @inheritDoc
*/
public function onActivate() {
ZMUtil::checkWait();
try {
if (ConnectionManager::get($this->frame->fd)->getType() == "qq") {
$data = json_decode($this->frame->data, true);
if (isset($data["post_type"]))
EventHandler::callCQEvent($data, $this, 0);
else
EventHandler::callCQResponse($data);
}
foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
if (strtolower($v->type) == "message" && $this->parseSwooleRule($v)) {
$conn = ConnectionManager::get($this->frame->fd);
$c = $v->class;
/** @var ModBase $class */
$class = new $c(["server" => $this->server, "frame" => $this->frame, "connection" => $conn], ModHandleType::SWOOLE_MESSAGE);
call_user_func_array([$class, $v->method], [$conn]);
if ($class->block_continue) break;
}
}
} catch (Exception $e) {
Console::error("出现错误: " . $e->getMessage());
}
return $this;
}
/**
* @inheritDoc
*/
public function onAfter() {
foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
if (strtolower($v->type) == "message" && $this->parseSwooleRule($v) === true) {
$conn = ConnectionManager::get($this->frame->fd);
$c = $v->class;
/** @var ModBase $class */
$class = new $c(["server" => $this->server, "frame" => $this->frame, "connection" => $conn], ModHandleType::SWOOLE_MESSAGE);
call_user_func_array([$class, $v->method], []);
if ($class->block_continue) break;
}
}
return $this;
}
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->frame->fd));
break;
case "dataEqual": //handle websocket message事件时才能用
if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->frame->data);
break;
}
return true;
}
}

View File

@@ -0,0 +1,132 @@
<?php
namespace ZM\Event\Swoole;
use Closure;
use Framework\ZMBuf;
use Swoole\Http\Request;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Http\Response;
use ZM\ModBase;
use ZM\ModHandleType;
use ZM\Utils\ZMUtil;
class RequestEvent implements SwooleEvent
{
/**
* @var Request
*/
private $request;
/**
* @var Response
*/
private $response;
public function __construct(Request $request, Response $response) {
$this->request = $request;
$this->response = $response;
}
/**
* @inheritDoc
*/
public function onActivate() {
ZMUtil::checkWait();
foreach (ZMBuf::globals("http_header") as $k => $v) {
$this->response->setHeader($k, $v);
}
$uri = $this->request->server["request_uri"];
if ($uri != "/") {
$uri = explode("/", $uri);
$uri = array_diff($uri, ["..", "", "."]);
$node = ZMBuf::$req_mapping_node;
$params = [];
while (true) {
$r = array_shift($uri);
if ($r === null) break;
if (($node2 = $node->getRealRoute($r, $params)) !== null) {
$node = $node2;
continue;
} else {
$this->responseStatus(404);
return $this;
}
}
if ($node->getRule() === null || call_user_func($node->getRule(), $this->request) === true) { //判断规则是否存在,如果有规则则走一遍规则
if (in_array(strtoupper($this->request->server["request_method"]), $node->getRequestMethod())) { //判断目标方法在不在里面
$c_name = $node->getClass();
/** @var ModBase $class */
$class = new $c_name(["request" => $this->request, "response" => $this->response, "params" => $params], ModHandleType::SWOOLE_REQUEST);
$r = call_user_func_array([$class, $node->getMethod()], [$params]);
if (is_string($r) && !$this->response->isEnd()) $this->response->end($r);
if ($class->block_continue) return $this;
if ($this->response->isEnd()) return $this;
}
}
} else {
if (($node = ZMBuf::$req_mapping_node)->getMethod() !== null) {
if (in_array(strtoupper($this->request->server["request_method"]), $node->getRequestMethod())) { //判断目标方法在不在里面
$c_name = $node->getClass();
/** @var ModBase $class */
$class = new $c_name(["request" => $this->request, "response" => $this->response], ModHandleType::SWOOLE_REQUEST);
$r = call_user_func_array([$class, $node->getMethod()], []);
if (is_string($r) && !$this->response->isEnd()) $this->response->end($r);
if ($class->block_continue) return $this;
if ($this->response->isEnd()) return $this;
}
}
foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
if (strtolower($v->type) == "request" && $this->parseSwooleRule($v)) {
$c = $v->class;
$class = new $c(["request" => $this->request, "response" => $this->response]);
$r = call_user_func_array([$class, $v->method], []);
if ($class->block_continue) break;
}
}
}
if (!$this->response->isEnd()) {
$this->response->status(404);
$this->response->end(ZMUtil::getHttpCodePage(404));
}
return $this;
}
/**
* @inheritDoc
*/
public function onAfter() {
foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
if (strtolower($v->type) == "request" && $this->parseSwooleRule($v)) {
$c = $v->class;
$class = new $c(["request" => $this->request, "response" => $this->response]);
call_user_func_array([$class, $v->method], []);
if ($class->block_continue) break;
}
}
return $this;
}
private function responseStatus(int $int) {
$this->response->status($int);
$this->response->end();
}
private function parseSwooleRule($v) {
switch ($v->rule) {
case "containsGet":
case "containsPost":
if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->request);
break;
case "containsJson":
$content = $this->request->rawContent();
$content = json_decode($content, true);
if ($content === null) return false;
if ($v->callback instanceof Closure) return call_user_func($v->callback, $content);
break;
}
return true;
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace ZM\Event\Swoole;
use ZM\Event\Event;
interface SwooleEvent extends Event
{
/**
* @return SwooleEvent
*/
public function onActivate();
/**
* @return SwooleEvent
*/
public function onAfter();
}

View File

@@ -0,0 +1,61 @@
<?php
namespace ZM\Event\Swoole;
use Framework\ZMBuf;
use Swoole\Server;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\ModBase;
use ZM\ModHandleType;
use ZM\Utils\ZMUtil;
class WSCloseEvent implements SwooleEvent
{
public $server;
public $fd;
public function __construct(Server $server, int $fd) {
$this->server = $server;
$this->fd = $fd;
}
/**
* @inheritDoc
*/
public function onActivate() {
ZMUtil::checkWait();
foreach(ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
if(strtolower($v->type) == "close" && $this->parseSwooleRule($v)) {
$c = $v->class;
$class = new $c(["server" => $this->server, "fd" => $this->fd], ModHandleType::SWOOLE_CLOSE);
call_user_func_array([$class, $v->method], []);
if($class->block_continue) break;
}
}
return $this;
}
/**
* @inheritDoc
*/
public function onAfter() {
foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
if (strtolower($v->type) == "close" && $this->parseSwooleRule($v) === true) {
$c = $v->class;
/** @var ModBase $class */
$class = new $c(["server" => $this->server, "fd" => $this->fd], ModHandleType::SWOOLE_CLOSE);
call_user_func_array([$class, $v->method], []);
if($class->block_continue) break;
}
}
return $this;
}
private function parseSwooleRule($v) {
return true;
}
}

View File

@@ -0,0 +1,93 @@
<?php
namespace ZM\Event\Swoole;
use Closure;
use Framework\ZMBuf;
use Swoole\Http\Request;
use Swoole\WebSocket\Server;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Annotation\Swoole\SwooleEventAt;
use ZM\Connection\ConnectionManager;
use ZM\Connection\CQConnection;
use ZM\Connection\UnknownConnection;
use ZM\Connection\WSConnection;
use ZM\ModBase;
use ZM\ModHandleType;
use ZM\Utils\ZMUtil;
class WSOpenEvent implements SwooleEvent
{
/**
* @var Server
*/
private $server;
/**
* @var Request
*/
private $request;
/**
* @var WSConnection
*/
private $conn;
public function __construct(Server $server, Request $request) {
$this->server = $server;
$this->request = $request;
}
/**
* @inheritDoc
*/
public function onActivate() {
ZMUtil::checkWait();
$type = strtolower($this->request->get["type"] ?? $this->request->header["x-client-role"] ?? "");
$type_conn = ConnectionManager::getTypeClassName($type);
if ($type_conn == CQConnection::class) {
$qq = $this->request->get["qq"] ?? $this->request->header["x-self-id"] ?? "";
$self_token = ZMBuf::globals("access_token") ?? "";
$remote_token = $this->request->get["token"] ?? (isset($header["authorization"]) ? explode(" ", $this->request->header["authorization"])[1] : "");
if ($qq != "" && ($self_token == $remote_token)) $this->conn = new CQConnection($this->server, $this->request->fd, $qq);
else $this->conn = new UnknownConnection($this->server, $this->request->fd);
} else {
$this->conn = new $type_conn($this->server, $this->request->fd);
}
ZMBuf::$connect[$this->request->fd] = $this->conn;
foreach (ZMBuf::$events[SwooleEventAt::class] ?? [] as $v) {
if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) {
$c = $v->class;
$class = new $c(["server" => $this->server, "request" => $this->request, "connection" => $this->conn], ModHandleType::SWOOLE_OPEN);
call_user_func_array([$class, $v->method], [$this->conn]);
if ($class->block_continue) break;
}
}
return $this;
}
/**
* @inheritDoc
*/
public function onAfter() {
if (!$this->conn->exists()) return $this;
foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
if (strtolower($v->type) == "open" && $this->parseSwooleRule($v) === true) {
/** @var ModBase $class */
$class = new $v["class"](["server" => $this->server, "request" => $this->request, "connection" => $this->conn], ModHandleType::SWOOLE_OPEN);
call_user_func_array([$class, $v["method"]], [$this->conn]);
if ($class->block_continue) break;
}
}
return $this;
}
private function parseSwooleRule($v) {
switch (explode(":", $v->rule)[0]) {
case "connectType": //websocket连接类型
if ($v->callback instanceof Closure) return call_user_func($v->callback, $this->conn);
break;
}
return true;
}
}

View File

@@ -0,0 +1,137 @@
<?php
namespace ZM\Event\Swoole;
use Doctrine\Common\Annotations\AnnotationException;
use ReflectionException;
use Swoole\Coroutine;
use Swoole\Timer;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\MappingNode;
use ZM\Annotation\Swoole\SwooleEventAfter;
use ZM\Connection\ConnectionManager;
use ZM\DB\DB;
use ZM\DBCache\DBCacheManager;
use Framework\Console;
use Framework\GlobalConfig;
use Framework\ZMBuf;
use Swoole\Server;
use ZM\ModBase;
use ZM\ModHandleType;
use ZM\Utils\DataProvider;
use ZM\Utils\SQLPool;
class WorkerStartEvent implements SwooleEvent
{
private $worker_id;
/**
* @var Server
*/
private $server;
public function __construct(Server $server, $worker_id) {
$this->server = $server;
$this->worker_id = $worker_id;
}
/**
* @return WorkerStartEvent
* @throws AnnotationException
* @throws ReflectionException
*/
public function onActivate(): WorkerStartEvent {
Console::info("Worker启动中");
ZMBuf::resetCache(); //清空变量缓存
ZMBuf::set("wait_start", []); //添加队列在workerStart运行完成前先让其他协程等待执行
DBCacheManager::freeAllCache(); // 清空数据库缓存
$this->resetConnections();//释放所有与framework的连接
//设置炸毛buf中储存的对象
ZMBuf::$globals = new GlobalConfig();
if (ZMBuf::globals("sql_config")["sql_host"] != "") {
Console::info("新建SQL连接池中");
ZMBuf::$sql_pool = new SQLPool();
DB::initTableList();
}
ZMBuf::$server = $this->server;
ZMBuf::$atomics['reload_time']->add(1);
ZMBuf::$req_mapping_node = new MappingNode("/");
Console::info("监听console输入");
Console::listenConsole(); //这个方法只能在这里调用且如果worker_num不为1的话此功能不可用
$this->loadAllClass(); //加载composer资源、phar外置包、注解解析注册等
$this->setAutosaveTimer(ZMBuf::globals("auto_save_interval"));
return $this;
}
public function onAfter(): WorkerStartEvent {
foreach (ZMBuf::get("wait_start") as $v) {
Coroutine::resume($v);
}
ZMBuf::unsetCache("wait_start");
foreach (ZMBuf::$events[SwooleEventAfter::class] ?? [] as $v) {
/** @var AnnotationBase $v */
if (strtolower($v->type) == "workerstart") {
$class_name = $v->class;
/** @var ModBase $class */
$class = new $class_name(["server" => $this->server, "worker_id" => $this->worker_id], ModHandleType::SWOOLE_WORKER_START);
call_user_func_array([$class, $v->method], []);
if ($class->block_continue) break;
}
}
return $this;
}
private function resetConnections() {
foreach ($this->server->connections as $v) {
$this->server->close($v);
}
if (ZMBuf::$sql_pool instanceof SqlPool) {
ZMBuf::$sql_pool->destruct();
ZMBuf::$sql_pool = null;
}
}
/**
* @throws AnnotationException
* @throws ReflectionException
*/
private function loadAllClass() {
//加载phar包
Console::info("加载外部phar包中");
$dir = WORKING_DIR . "/resources/package/";
if (is_dir($dir)) {
$list = scandir($dir);
unset($list[0], $list[1]);
foreach ($list as $v) {
if (is_dir($dir . $v)) continue;
if (pathinfo($dir . $v, 4) == "phar") require_once($dir . $v);
}
}
//加载composer类
Console::info("加载composer资源中");
/** @noinspection PhpIncludeInspection */
require_once WORKING_DIR . "/vendor/autoload.php";
//加载各个模块的注解类,以及反射
Console::info("检索Module中");
AnnotationParser::registerMods();
//加载Custom目录下的自定义的内部类
ConnectionManager::registerCustomClass();
}
private function setAutosaveTimer($globals) {
DataProvider::$buffer_list = [];
Timer::tick($globals * 1000, function() {
DataProvider::saveBuffer();
});
}
}

View File

@@ -0,0 +1,12 @@
<?php
namespace ZM\Exception;
use Exception;
class DbException extends Exception
{
}

View File

@@ -0,0 +1,12 @@
<?php
namespace ZM\Exception;
use Exception;
class InvalidArgumentException extends Exception
{
}

View File

@@ -0,0 +1,20 @@
<?php
namespace ZM\Exception;
use Exception;
use Throwable;
use ZM\ModBase;
class WaitTimeoutException extends Exception
{
/** @var ModBase */
public $module;
public function __construct($module, $message = "", $code = 0, Throwable $previous = null) {
parent::__construct($message, $code, $previous);
$this->module = $module;
}
}

229
src/ZM/Http/Response.php Normal file
View File

@@ -0,0 +1,229 @@
<?php
namespace ZM\Http;
class Response
{
public $fd = 0;
public $socket = null;
public $header = null;
public $cookie = null;
public $trailer = null;
/**
* @var \Swoole\Http\Response
*/
private $response;
private $is_end = false;
public function __construct(\Swoole\Http\Response $response) {
$this->response = $response;
$this->fd = $response->fd;
$this->socket = $response->socket;
$this->header = $response->header;
$this->cookie = $response->cookie;
if (isset($response->trailer))
$this->trailer = $response->trailer;
}
/**
* @return mixed
*/
public function initHeader() {
return $this->response->initHeader();
}
/**
* @param $name
* @param $value
* @param $expires
* @param $path
* @param $domain
* @param $secure
* @param $httponly
* @param $samesite
* @return mixed
*/
public function cookie($name, $value = null, $expires = null, $path = null, $domain = null, $secure = null, $httponly = null, $samesite = null) {
return $this->response->rawcookie($name, $value, $expires, $path, $domain, $secure, $httponly, $samesite);
}
/**
* @param $name
* @param $value
* @param $expires
* @param $path
* @param $domain
* @param $secure
* @param $httponly
* @param $samesite
* @return mixed
*/
public function setCookie($name, $value = null, $expires = null, $path = null, $domain = null, $secure = null, $httponly = null, $samesite = null) {
return $this->response->setCookie($name, $value, $expires, $path, $domain, $secure, $httponly, $samesite);
}
/**
* @param $name
* @param $value
* @param $expires
* @param $path
* @param $domain
* @param $secure
* @param $httponly
* @param $samesite
* @return mixed
*/
public function rawcookie($name, $value = null, $expires = null, $path = null, $domain = null, $secure = null, $httponly = null, $samesite = null) {
return $this->response->rawcookie($name, $value, $expires, $path, $domain, $secure, $httponly, $samesite);
}
/**
* @param $http_code
* @param $reason
* @return mixed
*/
public function status($http_code, $reason = null) {
return $this->response->status($http_code, $reason);
}
/**
* @param $http_code
* @param $reason
* @return mixed
*/
public function setStatusCode($http_code, $reason = null) {
return $this->response->setStatusCode($http_code, $reason);
}
/**
* @param $key
* @param $value
* @param $ucwords
* @return mixed
*/
public function header($key, $value, $ucwords = null) {
return $this->response->header($key, $value, $ucwords);
}
/**
* @param $key
* @param $value
* @param $ucwords
* @return mixed
*/
public function setHeader($key, $value, $ucwords = null) {
return $this->response->setHeader($key, $value, $ucwords);
}
/**
* @param $key
* @param $value
* @return mixed
*/
public function trailer($key, $value) {
return $this->response->trailer($key, $value);
}
/**
* @return mixed
*/
public function ping() {
return $this->response->ping();
}
/**
* @param $content
* @return mixed
*/
public function write($content) {
return $this->response->write($content);
}
/**
* @param $content
* @return mixed
*/
public function end($content = null) {
$this->is_end = true;
return $this->response->end($content);
}
public function isEnd() { return $this->is_end; }
/**
* @param $filename
* @param $offset
* @param $length
* @return mixed
*/
public function sendfile($filename, $offset = null, $length = null) {
return $this->response->sendfile($filename, $offset, $length);
}
/**
* @param $location
* @param $http_code
* @return mixed
*/
public function redirect($location, $http_code = null) {
return $this->redirect($location, $http_code);
}
/**
* @return mixed
*/
public function detach() {
return $this->response->detach();
}
/**
* @param $fd
* @return mixed
*/
public static function create($fd) {
return \Swoole\Http\Response::create($fd);
}
/**
* @return mixed
*/
public function upgrade() {
return $this->response->upgrade();
}
/**
* @param $data
* @param null $opcode
* @param null $flags
* @return mixed
*/
public function push($data, $opcode = null, $flags = null) {
return $this->response->push($data, $opcode, $flags);
}
/**
* @return mixed
*/
public function recv() {
return $this->response->recv();
}
/**
* @return mixed
*/
public function close() {
return $this->response->close();
}
public function __destruct() {
}
}

165
src/ZM/ModBase.php Normal file
View File

@@ -0,0 +1,165 @@
<?php
namespace ZM;
use Co;
use Framework\ZMBuf;
use Swoole\Http\Request;
use ZM\API\CQAPI;
use ZM\Connection\WSConnection;
use ZM\Exception\InvalidArgumentException;
use ZM\Exception\WaitTimeoutException;
use ZM\Http\Response;
use Swoole\WebSocket\Frame;
use Swoole\WebSocket\Server;
abstract class ModBase
{
/** @var Server */
protected $server;
/** @var Frame */
protected $frame;
/** @var array */
protected $data;
/** @var Request */
protected $request;
/** @var Response */
protected $response;
/** @var int */
protected $fd;
/** @var int */
protected $worker_id;
/** @var WSConnection */
protected $connection;
protected $handle_type = ModHandleType::CQ_MESSAGE;
public $block_continue = false;
public function __construct($param0 = [], $handle_type = 0) {
if (isset($param0["server"])) $this->server = $param0["server"];
if (isset($param0["frame"])) $this->frame = $param0["frame"];
if (isset($param0["data"])) $this->data = $param0["data"];
if (isset($param0["request"])) $this->request = $param0["request"];
if (isset($param0["response"])) $this->response = $param0["response"];
if (isset($param0["fd"])) $this->fd = $param0["fd"];
if (isset($param0["worker_id"])) $this->worker_id = $param0["worker_id"];
if (isset($param0["connection"])) $this->connection = $param0["connection"];
$this->handle_type = $handle_type;
}
/**
* only can used by cq->message event function
* @param $msg
* @param bool $yield
* @return mixed
*/
public function reply($msg, $yield = false) {
switch ($this->data["message_type"]) {
case "group":
case "private":
case "discuss":
return CQAPI::quick_reply($this->connection, $this->data, $msg, $yield);
}
return false;
}
public function finalReply($msg, $yield = false) {
$this->block_continue = true;
if ($msg == "") return true;
return $this->reply($msg, $yield);
}
/**
* @param string $prompt
* @param int $timeout
* @param string $timeout_prompt
* @return string
* @throws InvalidArgumentException
* @throws WaitTimeoutException
*/
public function waitMessage($prompt = "", $timeout = 600, $timeout_prompt = "") {
if ($prompt != "") $this->reply($prompt);
if (!isset($this->data["user_id"], $this->data["message"], $this->data["self_id"]))
throw new InvalidArgumentException("协程等待参数缺失");
$cid = Co::getuid();
$api_id = ZMBuf::$atomics["wait_msg_id"]->get();
ZMBuf::$atomics["wait_msg_id"]->add(1);
$hang = [
"coroutine" => $cid,
"user_id" => $this->data["user_id"],
"message" => $this->data["message"],
"self_id" => $this->data["self_id"],
"message_type" => $this->data["message_type"],
"result" => null
];
if ($hang["message_type"] == "group" || $hang["message_type"] == "discuss") {
$hang[$hang["message_type"] . "_id"] = $this->data[$this->data["message_type"] . "_id"];
}
ZMBuf::appendKey("wait_api", $api_id, $hang);
$id = swoole_timer_after($timeout * 1000, function () use ($api_id, $timeout_prompt) {
$r = ZMBuf::get("wait_api")[$api_id] ?? null;
if ($r !== null) {
Co::resume($r["coroutine"]);
}
});
Co::suspend();
$sess = ZMBuf::get("wait_api")[$api_id];
ZMBuf::unsetByValue("wait_api", $api_id);
$result = $sess["result"];
if (isset($id)) swoole_timer_clear($id);
if ($result === null) throw new WaitTimeoutException($this, $timeout_prompt);
return $result;
}
/**
* @param $arg
* @param $mode
* @param $prompt_msg
* @return mixed|string
* @throws InvalidArgumentException
* @throws WaitTimeoutException
*/
public function getArgs(&$arg, $mode, $prompt_msg) {
switch ($mode) {
case ZM_MATCH_ALL:
$p = $arg;
array_shift($p);
return trim(implode(" ", $p)) == "" ? $this->waitMessage($prompt_msg) : trim(implode(" ", $p));
case ZM_MATCH_NUMBER:
foreach ($arg as $k => $v) {
if (is_numeric($v)) {
array_splice($arg, $k, 1);
return $v;
}
}
return $this->waitMessage($prompt_msg);
case ZM_MATCH_FIRST:
if (isset($arg[1])) {
$a = $arg[1];
array_splice($arg, 1, 1);
return $a;
} else {
return $this->waitMessage($prompt_msg);
}
}
throw new InvalidArgumentException();
}
public function getMessage() { return $this->data["message"] ?? null; }
public function getUserId() { return $this->data["user_id"] ?? null; }
public function getGroupId() { return $this->data["group_id"] ?? null; }
public function getMessageType() { return $this->data["message_type"] ?? null; }
public function getRobotId() { return $this->data["self_id"]; }
public function getConnection() { return $this->connection; }
public function setBlock($result = true) { $this->block_continue = $result; }
}

20
src/ZM/ModHandleType.php Normal file
View File

@@ -0,0 +1,20 @@
<?php
namespace ZM;
class ModHandleType
{
const CQ_MESSAGE = 0;
const CQ_REQUEST = 1;
const CQ_NOTICE = 2;
const CQ_META_EVENT = 3;
const SWOOLE_OPEN = 4;
const SWOOLE_CLOSE = 5;
const SWOOLE_MESSAGE = 6;
const SWOOLE_REQUEST = 7;
const SWOOLE_WORKER_START = 8;
const SWOOLE_WORKER_STOP = 9;
}

View File

@@ -0,0 +1,46 @@
<?php
namespace ZM\Utils;
use Co;
use Framework\Console;
use Framework\ZMBuf;
class DataProvider
{
public static $buffer_list = [];
public static function getResourceFolder() {
return WORKING_DIR . '/resources/';
}
public static function addSaveBuffer($buf_name, $sub_folder = null) {
$name = ($sub_folder ?? "") . "/" . $buf_name . ".json";
self::$buffer_list[$buf_name] = $name;
ZMBuf::set($buf_name, self::getJsonData($name));
}
public static function saveBuffer() {
$head = Console::setColor(date("[H:i:s ") . "INFO] Saving buffer......", "lightblue");
echo $head;
foreach(self::$buffer_list as $k => $v) {
self::setJsonData($v, ZMBuf::get($k));
}
echo Console::setColor("saved", "lightblue").PHP_EOL;
}
private static function getJsonData(string $string) {
if(!file_exists(self::getDataFolder().$string)) return [];
return json_decode(Co::readFile(self::getDataFolder().$string), true);
}
private static function setJsonData($filename, array $args) {
Co::writeFile(self::getDataFolder() . $filename, json_encode($args, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_BIGINT_AS_STRING));
}
private static function getDataFolder() {
return CONFIG_DIR;
}
}

101
src/ZM/Utils/SQLPool.php Normal file
View File

@@ -0,0 +1,101 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2019/1/5
* Time: 4:48 PM
*/
namespace ZM\Utils;
use framework\Console;
use framework\ZMBuf;
use SplQueue;
use Swoole\Coroutine;
use Swoole\Coroutine\Mysql;
class SQLPool
{
protected $available = true;
protected $pool;
private $info;
public $co_list = [];
public $connect_cnt = 0;
public function __construct() {
$this->pool = new SplQueue;
$this->info = [
"host" => ZMBuf::globals("sql_config")["sql_host"],
"port" => ZMBuf::globals("sql_config")["sql_port"],
"user" => ZMBuf::globals("sql_config")["sql_username"],
"password" => ZMBuf::globals("sql_config")["sql_password"],
"database" => ZMBuf::globals("sql_config")["sql_database"]
];
}
/**
* 将利用过的连接入队
* @param $mysql
*/
public function put($mysql) {
$this->pool->push($mysql);
if (($a = array_shift($this->co_list)) !== null) {
Coroutine::resume($a);
}
}
/**
* 获取队中的连接,如果不存在则创建新的
* @param bool $no_new_conn
* @return bool|mixed|Mysql
*/
public function get($no_new_conn = false) {
if (count($this->pool) == 0 && $this->connect_cnt <= 70) {
if($no_new_conn) return false;
$this->connect_cnt += 1;
$r = $this->newConnect();
if ($r !== false) {
return $r;
} else {
$this->connect_cnt -= 1;
return false;
}
} elseif (count($this->pool) > 0) {
$con = $this->pool->pop();
if ($con->connected !== false) return $con;
} elseif ($this->connect_cnt > 70) {
$this->co_list[]=Coroutine::getuid();
Console::warning("数据库连接过多,协程等待重复利用中...当前协程数 ".Coroutine::stats()["coroutine_num"]);
Coroutine::suspend();
return $this->get($no_new_conn);
}
return false;
}
public function getCount() {
return $this->pool->count();
}
public function destruct() {
// 连接池销毁, 置不可用状态, 防止新的客户端进入常驻连接池, 导致服务器无法平滑退出
$this->available = false;
while (!$this->pool->isEmpty()) {
$this->pool->pop();
}
}
private function newConnect() {
//无空闲连接,创建新连接
$mysql = new Mysql();
Console::info("创建SQL连接中当前有" . $this->connect_cnt . "个连接");
$res = $mysql->connect($this->info);
if ($res == false) {
echo $mysql->error . PHP_EOL;
return false;
} else {
return $mysql;
}
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace ZM\Utils;
class ScheduleManager
{
//TODO: 写framework部分的schedule控制代码
}

71
src/ZM/Utils/ZMUtil.php Normal file
View File

@@ -0,0 +1,71 @@
<?php
namespace ZM\Utils;
use Co;
use framework\Console;
use Framework\ZMBuf;
class ZMUtil
{
/**
* 检查workerStart是否运行结束
*/
public static function checkWait() {
if(ZMBuf::isset("wait_start")) {
ZMBuf::append("wait_start", Co::getCid());
Co::suspend();
}
}
public static function stop() {
Console::info(Console::setColor("Stopping server...", "red"));
foreach (ZMBuf::$server->connections as $v) {
ZMBuf::$server->close($v);
}
DataProvider::saveBuffer();
ZMBuf::$server->shutdown();
ZMBuf::$server->stop();
}
public static function getHttpCodePage(int $http_code) {
if(isset(ZMBuf::globals("http_default_code_page")[$http_code])) {
return Co::readFile(DataProvider::getResourceFolder()."html/".ZMBuf::globals("http_default_code_page")[$http_code]);
} else return null;
}
public static function reload() {
Console::info(Console::setColor("Reloading server...", "gold"));
foreach (ZMBuf::$server->connections as $v) {
ZMBuf::$server->close($v);
}
DataProvider::saveBuffer();
ZMBuf::$server->reload();
}
/**
* 解析CQ码
* @param $msg
* @return array|null
* 0123456
* [CQ:at]
*/
static function getCQ($msg) {
if (($start = mb_strpos($msg, '[')) === false) return null;
if (($end = mb_strpos($msg, ']')) === false) return null;
$msg = mb_substr($msg, $start + 1, $end - $start - 1);
if (mb_substr($msg, 0, 3) != "CQ:") return null;
$msg = mb_substr($msg, 3);
$msg2 = explode(",", $msg);
$type = array_shift($msg2);
$array = [];
foreach ($msg2 as $k => $v) {
$ss = explode("=", $v);
$sk = array_shift($ss);
$array[$sk] = implode("=", $ss);
}
return ["type" => $type, "params" => $array, "start" => $start, "end" => $end];
}
}

View File

@@ -1,142 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/4/12
* Time: 10:43
*/
class CQBot
{
/** @var Framework */
public $framework;
//传入数据
public $data = null;
//检测有没有回复过消息
private $function_called = false;
public $starttime;
public $endtime;
public $self_id;
public $circle;
public function __construct(Framework $framework, $circle, $package) {
$this->circle = $circle;
$this->starttime = microtime(true);
$this->framework = $framework;
$this->data = $package;
$this->self_id = $this->data["self_id"];
}
public function execute() {
if ($this->circle >= 5) return false;
if ($this->data === null) return false;
if (isset($it["user_id"]) && CQUtil::isRobot($this->data["user_id"])) return false;
if (isset($it["group_id"]) && $this->data["group_id"] == Cache::get("admin_group")) {
if ($this->getRobotId() != Cache::get("admin_active")) {
return false;
}
}
if ($this->data["message"] == "")
return false;
foreach (Cache::get("mods") as $v) {
/** @var ModBase $r */
$r = new $v($this, $this->data);
if ($r->split_execute === true) {
$msg = trim($this->data["message"]);
$msg = explodeMsg($msg);
$r->execute($msg);
}
}
$this->endtime = microtime(true);
return $this->function_called;
}
/**
* 快速回复消息
* @param $msg
* @param callable|null $callback
* @param bool $async
* @return bool
*/
public function reply($msg, callable $callback = null, $async = false) {
$this->function_called = true;
switch ($this->data["message_type"]) {
case "group":
$this->function_called = true;
if (!$async) return CQAPI::send_group_msg($this->getRobotId(), ["group_id" => $this->data["group_id"], "message" => $msg], $callback);
else return CQAPI::send_group_msg_async($this->getRobotId(), ["group_id" => $this->data["group_id"], "message" => $msg], $callback);
case "private":
$this->function_called = true;
if (!$async) return CQAPI::send_private_msg($this->getRobotId(), ["user_id" => $this->data["user_id"], "message" => $msg], $callback);
else return CQAPI::send_private_msg_async($this->getRobotId(), ["user_id" => $this->data["user_id"], "message" => $msg], $callback);
case "discuss":
$this->function_called = true;
if (!$async) return CQAPI::send_discuss_msg($this->getRobotId(), ["discuss_id" => $this->data["discuss_id"], "message" => $msg], $callback);
else return CQAPI::send_discuss_msg_async($this->getRobotId(), ["discuss_id" => $this->data["discuss_id"], "message" => $msg], $callback);
case "wechat":
//TODO: add wechat account support in the future
break;
}
return false;
}
public function isAdmin($user) {
if (in_array($user, Cache::get("admin"))) return true;
else return false;
}
public function replace($msg, $dat) {
$msg = str_replace("{at}", '[CQ:at,qq=' . $dat["user_id"] . ']', $msg);
$msg = str_replace("{and}", '&', $msg);
while (strpos($msg, '{') !== false && strpos($msg, '}') !== false) {
if (strpos($msg, '{') > strpos($msg, '}')) return $msg;
$start = strpos($msg, '{');
$end = strpos($msg, '}');
$sub = explode("=", substr($msg, $start + 1, $end - $start - 1));
switch ($sub[0]) {
case "at":
$qq = $sub[1];
$msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:at,qq=' . $qq . ']', $msg);
break;
case "image":
case "record":
$pictFile = $sub[1];
$msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:' . $sub[0] . ',file=' . $pictFile . ']', $msg);
break;
case "dice":
$file = $sub[1];
$msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:dice,type=' . $file . ']', $msg);
break;
case "shake":
$msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:shake]', $msg);
break;
case "music":
$id = $sub[1];
$msg = str_replace(substr($msg, $start, $end - $start + 1), '[CQ:music,type=163,id=' . $id . ']', $msg);
break;
case "internet":
array_shift($sub);
$id = implode("=", $sub);
if (substr($id, 0, 7) != "http://") $id = "http://" . $id;
$is = file_get_contents($id, false, NULL, 0, 1024);
if ($is == false) $is = "[请求时发生了错误] 如有疑问,请联系管理员";
$msg = str_replace(substr($msg, $start, $end - $start + 1), $is, $msg);
break 2;
default:
break 2;
}
}
return $msg;
}
/**
* 返回当前机器人的id
* @return string|null
*/
public function getRobotId() {
return $this->data["self_id"] ?? null;
}
}

View File

@@ -1,232 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/11/26
* Time: 9:23 AM
*/
/**
* Class CQAPI
* @method static send_private_msg($self_id, $params, callable $function = null)
* @method static send_group_msg($self_id, $params, callable $function = null)
* @method static send_discuss_msg($self_id, $params, callable $function = null)
* @method static send_msg($self_id, $params, callable $function = null)
* @method static delete_msg($self_id, $params, callable $function = null)
* @method static send_like($self_id, $params, callable $function = null)
* @method static set_group_kick($self_id, $params, callable $function = null)
* @method static set_group_ban($self_id, $params, callable $function = null)
* @method static set_group_anonymous_ban($self_id, $params, callable $function = null)
* @method static set_group_whole_ban($self_id, $params, callable $function = null)
* @method static set_group_admin($self_id, $params, callable $function = null)
* @method static set_group_anonymous($self_id, $params, callable $function = null)
* @method static set_group_card($self_id, $params, callable $function = null)
* @method static set_group_leave($self_id, $params, callable $function = null)
* @method static set_group_special_title($self_id, $params, callable $function = null)
* @method static set_discuss_leave($self_id, $params, callable $function = null)
* @method static set_friend_add_request($self_id, $params, callable $function = null)
* @method static set_group_add_request($self_id, $params, callable $function = null)
* @method static get_login_info($self_id, $params, callable $function = null)
* @method static get_stranger_info($self_id, $params, callable $function = null)
* @method static get_group_list($self_id, $params, callable $function = null)
* @method static get_group_member_info($self_id, $params, callable $function = null)
* @method static get_group_member_list($self_id, $params, callable $function = null)
* @method static get_cookies($self_id, $params, callable $function = null)
* @method static get_csrf_token($self_id, $params, callable $function = null)
* @method static get_credentials($self_id, $params, callable $function = null)
* @method static get_record($self_id, $params, callable $function = null)
* @method static get_status($self_id, $params, callable $function = null)
* @method static get_version_info($self_id, $params, callable $function = null)
* @method static set_restart($self_id, $params, callable $function = null)
* @method static set_restart_plugin($self_id, $params, callable $function = null)
* @method static clean_data_dir($self_id, $params, callable $function = null)
* @method static clean_plugin_log($self_id, $params, callable $function = null)
* @method static _get_friend_list($self_id, $params, callable $function = null)
* @method static _get_group_info($self_id, $params, callable $function = null)
* @method static _get_vip_info($self_id, $params, callable $function = null)
* @method static send_private_msg_async($self_id, $params, callable $function = null)
* @method static send_group_msg_async($self_id, $params, callable $function = null)
* @method static send_discuss_msg_async($self_id, $params, callable $function = null)
* @method static send_msg_async($self_id, $params, callable $function = null)
* @method static delete_msg_async($self_id, $params, callable $function = null)
* @method static set_group_kick_async($self_id, $params, callable $function = null)
* @method static set_group_ban_async($self_id, $params, callable $function = null)
* @method static set_group_anonymous_ban_async($self_id, $params, callable $function = null)
* @method static set_group_whole_ban_async($self_id, $params, callable $function = null)
* @method static set_group_admin_async($self_id, $params, callable $function = null)
* @method static set_group_anonymous_async($self_id, $params, callable $function = null)
* @method static set_group_card_async($self_id, $params, callable $function = null)
* @method static set_group_leave_async($self_id, $params, callable $function = null)
* @method static set_group_special_title_async($self_id, $params, callable $function = null)
* @method static set_discuss_leave_async($self_id, $params, callable $function = null)
* @method static set_friend_add_request_async($self_id, $params, callable $function = null)
* @method static set_group_add_request_async($self_id, $params, callable $function = null)
*/
class CQAPI
{
public static function debug($msg, $head = null, $self_id = null) {
if($head === null) $msg = date("[H:i:s") . " DEBUG] ".$msg;
if($self_id === null) $self_id = CQUtil::findRobot();
else $msg = $head.$msg;
if($self_id !== null){
return self::send_group_msg($self_id, ["message" => $msg, "group_id" => Cache::get("admin_group")]);
}
return false;
}
public static function __callStatic($name, $arg) {
if(mb_substr($name, -6) == "_after"){
$all = self::getSupportedAPIs();
$find = null;
$true_name = mb_substr($name, 0, -6);
if(!in_array($true_name, $all)){
Console::error("Unknown API " . $name);
return false;
}
$ms = array_shift($arg);
Scheduler::after($ms, function() use ($true_name, $arg){
$reply = ["action" => $true_name];
if (!is_array($arg[1])) {
Console::error("Error when parsing params. Please make sure your params is an array.");
return false;
}
if ($arg[1] != []) {
$reply["params"] = $arg[1];
}
return self::processAPI($arg[0], $reply, $arg[2]);
});
return true;
}
$all = self::getSupportedAPIs();
$find = null;
if (in_array($name, $all)) $find = $name;
else {
foreach ($all as $v) {
if (strtolower($name) == strtolower(str_replace("_", "", $v))) {
$find = $v;
break;
}
}
}
if ($find === null) {
Console::error("Unknown API " . $name);
return false;
}
$reply = ["action" => $find];
if (!is_array($arg[1])) {
Console::error("Error when parsing params. Please make sure your params is an array.");
return false;
}
if ($arg[1] != []) {
$reply["params"] = $arg[1];
}
return self::processAPI($arg[0], $reply, $arg[2]);
}
/********************** non-API Part **********************/
private static function getSupportedAPIs() {
return [
"send_private_msg",
"send_group_msg",
"send_discuss_msg",
"send_msg",
"delete_msg",
"send_like",
"set_group_kick",
"set_group_ban",
"set_group_anonymous_ban",
"set_group_whole_ban",
"set_group_admin",
"set_group_anonymous",
"set_group_card",
"set_group_leave",
"set_group_special_title",
"set_discuss_leave",
"set_friend_add_request",
"set_group_add_request",
"get_login_info",
"get_stranger_info",
"get_group_list",
"get_group_member_info",
"get_group_member_list",
"get_cookies",
"get_csrf_token",
"get_credentials",
"get_record",
"get_status",
"get_version_info",
"set_restart",
"set_restart_plugin",
"clean_data_dir",
"clean_plugin_log",
"_get_friend_list",
"_get_group_info",
"_get_vip_info",
//异步API
"send_private_msg_async",
"send_group_msg_async",
"send_discuss_msg_async",
"send_msg_async",
"delete_msg_async",
"set_group_kick_async",
"set_group_ban_async",
"set_group_anonymous_ban_async",
"set_group_whole_ban_async",
"set_group_admin_async",
"set_group_anonymous_async",
"set_group_card_async",
"set_group_leave_async",
"set_group_special_title_async",
"set_discuss_leave_async",
"set_friend_add_request_async",
"set_group_add_request_async"
];
}
public static function getLoggedAPIs() {
return [
"send_private_msg",
"send_group_msg",
"send_discuss_msg",
"send_msg",
"send_private_msg_async",
"send_group_msg_async",
"send_discuss_msg_async",
"send_msg_async"
];
}
/**
* @param $self_id
* @param $reply
* @param callable|null $function
* @return bool
*/
private static function processAPI($self_id, $reply, callable $function = null) {
$api_id = Cache::$api_id->get();
$reply["echo"] = $api_id;
Cache::$api_id->add(1);
if ($self_id instanceof RobotWSConnection) {
$connection = $self_id;
$self_id = $connection->getQQ();
} else $connection = ConnectionManager::getRobotConnection($self_id);
if ($connection instanceof NullConnection) {
Console::error("未找到qq号" . $self_id . "的API连接");
return false;
}
if ($connection->push(json_encode($reply))) {
Cache::appendKey("sent_api", $api_id, [
"data" => $reply,
"time" => microtime(true),
"func" => $function,
"self_id" => $self_id
]);
if (in_array($reply["action"], self::getLoggedAPIs())) {
Console::msg($reply);
Cache::$out_count->add(1);
}
return true;
} else return false;
}
}

View File

@@ -1,16 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/11/26
* Time: 10:01 PM
*/
class CustomWSConnection extends WSConnection
{
public function __construct(swoole_websocket_server $server, $fd, $request) {
parent::__construct($server, $fd, $request->server["remote_addr"]);
$this->create_success = true;
// Here to put your custom other websocket connection to manage.
}
}

View File

@@ -1,37 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/11/18
* Time: 11:11 AM
*/
class NullConnection extends WSConnection
{
private $qq = 0;
public function __construct(swoole_websocket_server $server, $fd, $remote, $qq = 0) {
parent::__construct($server, $fd, $remote);
$this->qq = $qq;
}
public function push($data, $push_error_record = true) {
$data = unicodeDecode($data);
CQUtil::errorlog("API推送失败未发送的消息: \n" . $data, "API ERROR", false);
if ($push_error_record) Cache::append("bug_msg_list", json_decode($data, true));
return false;
}
public function sendAPI($api, $params = [], $echo = []){
$data["action"] = $api;
if($params != []) $data["params"] = $params;
$echo_result["self_id"] = $this->qq;
if($echo != []){
if(count($echo) >= 1) $echo_result['type'] = array_shift($echo);
if(!empty($echo)) $echo_result["params"] = $echo;
}
$data["echo"] = $echo_result;
Console::debug("将要发送的API包" . json_encode($data, 128 | 256));
return $this->push(json_encode($data));
}
}

View File

@@ -1,133 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/11/14
* Time: 11:59 PM
*/
class RobotWSConnection extends WSConnection
{
const ALIAS_LIST = [
"10001" => "小马哥",
"838714432" => "鲸鱼的test机器人"
];
private $qq;
private $alias;//别名
private $sub_type;
public function __construct(swoole_websocket_server $server, $fd, $qq, swoole_http_request $request, $sub_type) {
parent::__construct($server, $fd, $request->server["remote_addr"]);
$this->qq = $qq;
$this->alias = self::ALIAS_LIST[$qq] ?? "机器人" . $qq;
$this->sub_type = $sub_type;
foreach (ConnectionManager::getAll("robot") as $k => $v) {
if ($v->getQQ() == $this->getQQ() && $k != $this->fd && $v->getSubType() == $this->getSubType()) {
$v->close();
}
}
if ($sub_type != "event") {
$obj = $this;
$r = $this->sendAPI("get_version_info", [], function ($response) use ($obj) {
Cache::set("version_info", $response["data"]);
});
if ($r)
$this->create_success = $this->sendAPI("send_group_msg", ["message" => "[CQBot] 机器人 " . $this->getAlias() . " 已连接链接fd" . $this->fd, "group_id" => Cache::get("admin_group")]);
} else $this->create_success = true;
}
/**
* 返回连接的QQ
* @return mixed
*/
public function getQQ() {
return $this->qq;
}
/**
* @return mixed|string
*/
public function getAlias() {
return $this->alias;
}
public function sendAPI($api, $params = [], callable $callback = null) {
$data["action"] = $api;
if ($params != []) $data["params"] = $params;
if ($this->sub_type == "event") {
$conns = ConnectionManager::getAll("robot");
foreach ($conns as $k => $v) {
if ($v->getSubType() == "api" && $v->getQQ() == $this->getQQ()) {
$api_id = Cache::$api_id->get();
$data["echo"] = $api_id;
Cache::$api_id->add(1);
Cache::appendKey("sent_api", $api_id, [
"data" => $data,
"time" => microtime(true),
"func" => $callback,
"self_id" => $this->getQQ()
]);
if ($v->push(json_encode($data))) {
if (in_array($data["action"], CQAPI::getLoggedAPIs())) {
Console::msg($data);
Cache::$out_count->add(1);
}
return true;
} else {
$response = [
"status" => "failed",
"retcode" => 998,
"data" => null,
"self_id" => $this->getQQ()
];
$s = Cache::get("sent_api")[$data["echo"]];
StatusParser::parse($response, $data);
if ($s["func"] !== null)
call_user_func($s["func"], $response, $data);
Cache::removeKey("sent_api", $data["echo"]);
return false;
}
}
}
return false;
}
$api_id = Cache::$api_id->get();
$data["echo"] = $api_id;
Cache::$api_id->add(1);
Cache::appendKey("sent_api", $api_id, [
"data" => $data,
"time" => microtime(true),
"func" => $callback,
"self_id" => $this->getQQ()
]);
if ($this->push(json_encode($data))) {
if (in_array($data["action"], CQAPI::getLoggedAPIs())) {
Console::msg($data);
Cache::$out_count->add(1);
}
return true;
} else {
$response = [
"status" => "failed",
"retcode" => 999,
"data" => null,
"self_id" => $this->getQQ()
];
$s = Cache::get("sent_api")[$data["echo"]];
StatusParser::parse($response, $data);
if ($s["func"] !== null)
call_user_func($s["func"], $response, $data);
Cache::removeKey("sent_api", $data["echo"]);
return false;
}
}
/**
* @return mixed
*/
public function getSubType() {
return $this->sub_type;
}
}

View File

@@ -1,64 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/7/27
* Time: 12:31 PM
*/
abstract class WSConnection
{
public $fd;
protected $server;
protected $remote_address;
public $create_success = false;
public function __construct(swoole_websocket_server $server, $fd, $remote) {
$this->server = $server;
$this->fd = $fd;
$this->remote_address = $remote;
}
/**
* 返回swoole server
* @return swoole_websocket_server
*/
public function getServer() {
return $this->server;
}
public function getType(){
if($this instanceof RobotWSConnection) return "robot";
elseif($this instanceof CustomWSConnection) return "custom";
else return "unknown";
}
public function push($data, $push_error_record = true) {
if ($data === null || $data == "") {
Console::error("Empty data pushed.");
return false;
}
if ($this->server->push($this->fd, $data) === false) {
$data = unicodeDecode($data);
CQUtil::errorlog("API推送失败未发送的消息: \n" . $data, "API ERROR", false);
if ($push_error_record) Cache::append("bug_msg_list", json_decode($data, true));
return false;
}
return true;
}
public function close() {
$this->server->close($this->fd);
ConnectionManager::remove($this->fd);
}
/**
* @return mixed
*/
public function getRemoteAddress() {
return $this->remote_address;
}
}

View File

@@ -1,17 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/5/26
* Time: 下午3:10
*/
abstract class Event
{
/**
* @return Framework
*/
public function getFramework(){
return Framework::getInstance();
}
}

View File

@@ -1,31 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/10/15
* Time: 10:35 AM
*/
class MessageEvent extends Event
{
public function __construct($req) {
if (CQUtil::isRobot($req["user_id"])) return;
$in_count = Cache::$in_count->get();
Cache::$in_count->add(1);
if (Cache::$data["info_level"] >= 1) {
$num = CQUtil::getRobotAlias($req["self_id"]);
$type = $req["post_type"] == "message" ? ($req["message_type"] == "group" ? "GROUP_MSG" . $num . ":" . $req["group_id"] : ($req["message_type"] == "private" ? "PRIVATE_MSG" . $num : "DISCUSS_MSG" . $num . ":" . $req["discuss_id"])) : strtoupper($req["post_type"]);
Console::put(Console::setColor(date("H:i:s"), "green") . Console::setColor(" [$in_count]" . $type, "lightlightblue") . Console::setColor(" " . $req["user_id"], "yellow") . Console::setColor(" > ", "gray") . $req["message"]);
}
CQUtil::updateMsg();//更新消息速度
//传入消息处理的逻辑
$c = new CQBot($this->getFramework(), 0, $req);
$c->execute();
$value = $c->endtime - $c->starttime;
Console::debug("Using time: " . $value);
}
}

View File

@@ -1,29 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/10/15
* Time: 11:06 AM
*/
class MetaEvent extends Event
{
public function __construct($req) {
switch ($req["meta_event_type"]) {
case "lifecycle":
//插件生命周期事件
switch ($req["sub_type"]) {
case "enable":
Console::info("机器人" . $req["self_id"] . "的HTTP插件" . Console::setColor("启动", "green") . "运行");
break;
case "disable":
Console::info("机器人" . $req["self_id"] . "的HTTP插件" . Console::setColor("停止", "red") . "运行");
break;
}
break;
case "heartbeat":
//处理心跳包中的status事件目前炸毛不需要。
break;
}
}
}

View File

@@ -1,19 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/10/15
* Time: 10:36 AM
*/
class NoticeEvent extends Event
{
public function __construct($req) {
foreach(Cache::get("mods") as $k => $v){
if(in_array("onNotice", get_class_methods($v))){
/** @var ModBase $v */
$v::onNotice($req);
}
}
}
}

View File

@@ -1,19 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/10/15
* Time: 10:36 AM
*/
class RequestEvent extends Event
{
public function __construct($req) {
foreach(Cache::get("mods") as $k => $v){
if(in_array("onRequest", get_class_methods($v))){
/** @var ModBase $v */
$v::onRequest($req);
}
}
}
}

View File

@@ -1,14 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/10/15
* Time: 10:40 AM
*/
class UnknownEvent extends Event
{
public function __construct($req) {
Console::info("Unknown event.");
}
}

View File

@@ -1,39 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/5/16
* Time: 下午2:05
*/
class HTTPEvent extends ServerEvent
{
public $content;
public $isValid = false;
/**
* HTTPEvent constructor.
* @param swoole_http_request $request
* @param swoole_http_response $response
*/
public function __construct(swoole_http_request $request, swoole_http_response $response) {
parent::__construct(Cache::$server);
$response->end("Hello world!");
//此为HTTP请求的回复更多设置回复头、传送文件、POST、GET请求解析等内容请查阅文档https://wiki.swoole.com
}
/**
* 此函数为一个示例的检测有效的函数,为此预留
* 作用是判断传入的请求数据是合法的
* @param $param
* @return bool
*/
public function isValidParam($param) {
if ($param === null) return false;
if (!isset($param["event"])) return false;
if (!isset($param["timestamp"])) return false;
if (!isset($param[$param["event"]])) return false;
if(($param["timestamp"] > (time() + 10)) || ($param["timestamp"] < (time() - 10))) return false;
return true;
}
}

View File

@@ -1,16 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/11/26
* Time: 9:30 AM
*/
abstract class ServerEvent extends Event
{
public $server;
public function __construct(\swoole_server $server) {
$this->server = $server;
}
}

View File

@@ -1,21 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/5/26
* Time: 下午4:14
*/
class WSCloseEvent extends ServerEvent
{
public function __construct(swoole_server $server, int $fd) {
parent::__construct($server);
$connect = ConnectionManager::get($fd);
if ($connect !== null) {
ConnectionManager::remove($fd);
Console::info("WebSocket Connection closed. fd: " . $fd);
} else {
Console::info("Unknown WS or HTTP Connection closed. fd: ".$fd);
}
}
}

View File

@@ -1,67 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/5/26
* Time: 下午4:04
*/
class WSMessageEvent extends ServerEvent
{
public function __construct(swoole_websocket_server $server, swoole_websocket_frame $frame) {
parent::__construct($server);
$fd = $frame->fd;
$req = json_decode($frame->data, true);
$conn = ConnectionManager::get($fd);
if ($conn === null) {
Console::info("收到一个未知链接发来的消息。" . $fd);
return;
}
switch ($conn->getType()) {
case "robot":
//处理酷Q HTTP事件接口消息(Event)
if (isset($req["post_type"])) {
switch ($req["post_type"]) {
case "message":
new MessageEvent($req);
break 2;
case "notice":
new NoticeEvent($req);
break 2;
case "request":
new RequestEvent($req);
break 2;
case "meta_event":
new MetaEvent($req);
break 2;
default:
new UnknownEvent($req);
break 2;
}
} else {
Console::debug("收到来自API[" . $fd . "]连接的回复:" . json_encode($req, 128 | 256));
if (isset($req["echo"]) && Cache::array_key_exists("sent_api", $req["echo"])) {
$status = $req["status"];
$retcode = $req["retcode"];
$data = $req["data"];
$origin = Cache::get("sent_api")[$req["echo"]];
$self_id = $origin["self_id"];
$response = [
"status" => $status,
"retcode" => $retcode,
"data" => $data,
"self_id" => $self_id
];
StatusParser::parse($response, $origin);
if ($origin["func"] !== null)
call_user_func($origin["func"], $response, $origin, $conn);
Cache::removeKey("sent_api", $req["echo"]);
}
}
break;
default:
Console::info("收到未知链接的消息, 来自: " . $fd);
break;
}
}
}

View File

@@ -1,56 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/5/26
* Time: 下午4:10
*/
class WSOpenEvent extends ServerEvent
{
public function __construct(swoole_websocket_server $server, swoole_http_request $request) {
parent::__construct($server);
$fd = $request->fd;
$get = $request->get;
$header = $request->header;
//echo json_encode($header, 128|256);
$connect_type = $get["type"] ?? (isset($header["x-client-role"]) ? strtolower($header["x-client-role"]) : "");
$access_token = $get["token"] ?? (isset($header["authorization"]) ? explode(" ", $header["authorization"])[1] : "");
//Console::info("链接类型:".$connect_type."\n链接token".$access_token);
if ($connect_type == "") {
Console::info("未指定连接类型,关闭连接.");
$server->close($fd);
return;
}
//if (isset($request->header["authorization"])) {
//$tokens = explode(" ", $request->header["authorization"]);
//$tokens = trim($tokens[1]);
if ($access_token !== Cache::get("access_token") && Cache::get("access_token") != "") {
Console::info("监测到WS连接但是token不对无法匹配。");
$server->close($fd);
return;
}
switch ($connect_type) {
case "event":
case "api":
case "universal":
$self_id = $get["qq"] ?? ($header["x-self-id"] ?? "");
Console::info("收到 " . $connect_type . " 连接,来自机器人:" . $self_id . "fd" . $fd);
$conn = new RobotWSConnection($server, $fd, $self_id, $request, $connect_type);
if($conn->create_success) ConnectionManager::set($fd, $conn);
else {
Console::error("初始化WS连接失败fd".$fd."QQ".$self_id);
$server->close($fd);
return;
}
break;
case "custom":
$conn = new CustomWSConnection($server, $fd, $request);
if($conn->create_success) ConnectionManager::set($fd, $conn);
break;
default:
Console::info("Unknown WS Connection connected. I will close it.");
$server->close($fd);
}
}
}

View File

@@ -1,30 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/5/26
* Time: 下午3:15
*/
class WorkerStartEvent extends ServerEvent
{
public function __construct(swoole_server $server, $worker_id) {
parent::__construct($server);
Cache::$server = $server;
load_extensions();
Cache::$reload_time->add(1);
CQUtil::loadAllFiles();
$set = settings();
foreach (get_included_files() as $file)
Console::debug("Loaded " . $file);
//计时器ms
if ($set["swoole_use_tick"] === true) {
Cache::$scheduler = new Scheduler($this->getFramework(), time());
//$timer_id = $server->tick($set["swoole_tick_interval"], [Cache::$scheduler, "tick"]);
Console::info("已在worker #" . $worker_id . " 中成功启动了计时器!计时器间隔:" . $set["swoole_tick_interval"]);
}
Console::debug("master_pid = " . $server->master_pid);
Console::debug("worker_id = " . $worker_id);
Console::put("===== Worker " . Console::setColor("#" . $worker_id, "gold") . " 已启动 =====");
}
}

View File

@@ -1,136 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/4/14
* Time: 13:29
*/
use DataProvider as DP;
class User
{
private $id;
protected $nickname = "";
protected $permission = 0;
protected $friend = [];
protected $lexicon = [];
protected $user_type = 0;//预留位置0为QQ用户1为为微信公众号用户
protected $function = [];
public $buffer = null;
public function __construct($qid) {
$this->id = $qid;
if (strlen($qid) >= 15) $this->user_type = 1;
$this->permission = DP::getJsonData("permissions.json")[$qid] ?? 0;
$this->friend = [];
}
/**
* 获取用户QQ号
* @return mixed
*/
public function getId() { return $this->id; }
/**
* 获取用户权限值
* @return int
*/
public function getPermission() { return $this->permission; }
/**
* @param int $permission
* @return User
*/
public function setPermission(int $permission) {
$this->permission = $permission;
return $this;
}
public function getBuffer() {
return $this->buffer;
}
public function setBuffer($buffer) {
$this->buffer = $buffer;
return $this;
}
/**
* @return string
*/
public function getNickname() {
return $this->nickname;
}
/**
* @return array
*/
public function getFriend() {
return $this->friend;
}
/**
* @param array $friend
*/
public function setFriend(array $friend) {
$this->friend = $friend;
}
/**
* @param string $nickname
*/
public function setNickname(string $nickname) {
$this->nickname = $nickname;
}
/**
* @return array
*/
public function getLexicon() {
return $this->lexicon;
}
/**
* @param array $lexicon
*/
public function setLexicon(array $lexicon) {
$this->lexicon = $lexicon;
}
/**
* @return array
*/
public function getFunction() {
return $this->function;
}
/**
* @param array $function
*/
public function setFunction(array $function) {
$this->function = $function;
}
public function closeFunction($name) {
unset($this->function[$name]);
}
/**
* @return int
*/
public function getUserType() {
return $this->user_type;
}
public function toJson() {
$ls = [];
foreach ($this as $k => $v) {
$ls[$k] = $v;
}
return json_encode($ls, 128 | 256);
}
}

View File

@@ -1,71 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/4/19
* Time: 14:55
*/
/**
* Class Admin
* 框架管理模块,里面已经附带了一些查看状态、重载和停止的功能
*/
class Admin extends ModBase
{
public function __construct(CQBot $main, $data) {
parent::__construct($main, $data);
$this->split_execute = true;
}
public static function initValues() {
Cache::set("msg_speed", []);//消息速度列表(存的是时间戳)
Cache::set("admin_active", "");
Cache::set("admin", settings()["admin"]);
}
public static function onTick($tick) {
if ($tick % 900 == 0) CQUtil::saveAllFiles();//900秒储存一次数据
if (settings()["save_user_data"]) {
if ($tick % 21600 == 0) { //21600秒刷新一次好友列表
GroupManager::updateGroupList();
FriendManager::updateFriendList();
}
}
}
public static function onRequest($req) {
switch ($req["request_type"]) {
case "friend":
//好友添加请求处理
$comment = $req["comment"];//验证信息
$flag = $req["flag"];//添加好友用的flag用于处理好友
$user_id = $req["user_id"];
//如果不需要将好友添加信息发到群里,请注释下面这行
CQAPI::debug("有用户请求添加好友啦!\n用户QQ" . $user_id . "\n验证信息:" . $comment . "\n添加flag" . $flag, "");
//如果不需要要自动同意,请注释下面这几行
$params = ["flag" => $flag, "approve" => true];
CQAPI::set_friend_add_request($req["self_id"], $params, function ($response) use ($user_id) {
CQAPI::debug("已自动同意 " . $user_id, "", $response["self_id"]);
CQAPI::send_private_msg($user_id, ["message" => "你好,第一次见面请多关照!"]);
});
break;
}
}
public function execute($it) {
if (!$this->main->isAdmin($this->getUserId())) return false;
switch ($it[0]) {
case "reload": //管理员重载代码
$this->reply("正在重新启动...");
CQUtil::reload();
return true;
case "stop": //管理员停止server
$this->reply("正在停止服务器...");
CQUtil::stop();
return true;
}
return false;
}
}

View File

@@ -1,52 +0,0 @@
<?php
/**
* 这是一个示例模块php文件你可以直接复制此文件中的代码
* 然后修改其class的名字注意要和.php文件的文件名相同
* 例如新建一个Mailer模块则Mailer模块的文件名字为
* Mailer.php
* 如果要开启框架的切割函数激活请在__construct构造函数中
* 添加一句:$this->split_execute = true;
* 默认不会执行execute函数
*/
class Example extends ModBase
{
public function __construct(CQBot $main, $data) {
parent::__construct($main, $data);
//$data为CQHTTP插件上报的消息事件数组
//这里编写你的内容
$this->split_execute = true;
}
/**
* 分词函数,如果开启分词模式的话将调用此数组。
* 如果将一句话使用空格、换行和Tab进行分割用来处理多项参数的功能指令
* 例如:"随机数 1 100" 将被分割成数组$it ["随机数","1","100"]
* @param $it
* @return bool
*/
public function execute($it) {
switch ($it[0]) {
case "ping":
$this->reply("pong");
return true;
case "你好":
$this->reply("你好我是CQBot");
return true;
case "随机数":
if (!isset($it[1]) || !isset($it[2])) {
$this->reply("用法: 随机数 开始整数 结束整数");
return true;
}
$c1 = intval($it[1]);
$c2 = intval($it[2]);
if ($c1 > $c2) {
$this->reply("随机数范围错误!应该从小的一方到大的一方!例如:\n随机数 1 99");
return true;
}
$this->reply("生成的随机数是 " . mt_rand($c1, $c2));
return true;
}
return false;
}
}

View File

@@ -1,35 +0,0 @@
<?php
/**
* Created by PhpStorm.
* User: jerry
* Date: 2018/7/17
* Time: 2:21 PM
*/
class Help extends ModBase
{
public function __construct(CQBot $main, $data) {
parent::__construct($main, $data);
$this->split_execute = true;
}
public function execute($it) {
switch ($it[0]) {
case "帮助":
$msg = "「机器人帮助」";
$msg .= "\n随机数:生成一个随机数";
$this->reply($msg);
return true;
case "如何增加机器人功能":
$msg = "机器人功能是在框架中src/cqbot/mods/xxx.php文件中编写的。";
$msg .= "\nCQBot采用关键词系统你可以直接像现有源码一样添加case在switch里面";
$msg .= "\n也可以自己新建一个任意名称的Mod名称例如Entertain.php你可以在里面编写娱乐功能。";
$msg .= "\n你可以直接复制框架中Example.php文件的内容进行编辑。";
$msg .= "\n预先封装好的机器人函数均在CQUtil类中只需直接使用静态方法调用即可";
$msg .= "\n更多示例功能会逐渐添加到框架中,记得更新哦~";
$this->reply($msg);
return true;
}
return false;
}
}

Some files were not shown because too many files have changed in this diff Show More