mirror of
https://github.com/zhamao-robot/zhamao-framework.git
synced 2026-07-03 06:45:36 +08:00
Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
517d258d61 | ||
|
|
61e3818563 | ||
|
|
776ec98a3e | ||
|
|
f3e844bb0a | ||
|
|
a55cd4ed05 | ||
|
|
8a985620f9 | ||
|
|
484ddf9dfa | ||
|
|
b611b4aad6 | ||
|
|
b9f973c718 | ||
|
|
cd6c971547 | ||
|
|
c68083484a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -10,3 +10,4 @@ composer.lock
|
||||
/bin/.phpunit.result.cache
|
||||
/resources/zhamao.service
|
||||
.phpunit.result.cache
|
||||
.daemon_pid
|
||||
@@ -3,7 +3,7 @@
|
||||
"description": "High performance QQ robot and web server development framework",
|
||||
"minimum-stability": "stable",
|
||||
"license": "Apache-2.0",
|
||||
"version": "2.1.6",
|
||||
"version": "2.2.3",
|
||||
"extra": {
|
||||
"exclude_annotate": [
|
||||
"src/ZM"
|
||||
@@ -37,7 +37,8 @@
|
||||
"zhamao/config": "^1.0",
|
||||
"zhamao/request": "*@dev",
|
||||
"symfony/routing": "^5.1",
|
||||
"symfony/polyfill-php80": "^1.20"
|
||||
"symfony/polyfill-php80": "^1.20",
|
||||
"ext-posix": "*"
|
||||
},
|
||||
"suggest": {
|
||||
"ext-ctype": "*",
|
||||
|
||||
@@ -36,13 +36,19 @@ $config['swoole'] = [
|
||||
|
||||
/** 轻量字符串缓存,默认开启 */
|
||||
$config['light_cache'] = [
|
||||
'size' => 1024, //最多允许储存的条数(需要2的倍数)
|
||||
'max_strlen' => 16384, //单行字符串最大长度(需要2的倍数)
|
||||
'size' => 512, //最多允许储存的条数(需要2的倍数)
|
||||
'max_strlen' => 32768, //单行字符串最大长度(需要2的倍数)
|
||||
'hash_conflict_proportion' => 0.6, //Hash冲突率(越大越好,但是需要的内存更多)
|
||||
'persistence_path' => $config['zm_data'].'_cache.json',
|
||||
'auto_save_interval' => 900
|
||||
];
|
||||
|
||||
/** 大容量跨进程变量存储(2.2.0可用) */
|
||||
$config["worker_cache"] = [
|
||||
"worker" => 0,
|
||||
"transaction_timeout" => 30000
|
||||
];
|
||||
|
||||
/** MySQL数据库连接信息,host留空则启动时不创建sql连接池 */
|
||||
$config['sql_config'] = [
|
||||
'sql_host' => '',
|
||||
@@ -72,7 +78,7 @@ $config["access_token"] = '';
|
||||
|
||||
/** HTTP服务器固定请求头的返回 */
|
||||
$config['http_header'] = [
|
||||
'X-Powered-By' => 'zhamao-framework',
|
||||
'Server' => 'zhamao-framework',
|
||||
'Content-Type' => 'text/html; charset=utf-8'
|
||||
];
|
||||
|
||||
|
||||
@@ -91,6 +91,18 @@ bin/start server # 通过源码模式启动框架
|
||||
- `--watch`:监控 `src/` 目录下的文件变化,有变化则自动重新载入代码。开启监控需要安装 PHP 扩展:inotify。使用 pecl 就可以安装:`pecl install inotify`。
|
||||
- `--env`:设置运行环境,设置运行环境后将优先加载指定环境的配置文件,支持 `--env=production`,`--env=staging`,`--env=development`,见 [基本配置](/guide/basic-config/#_2)。
|
||||
|
||||
## 守护进程操作命令
|
||||
|
||||
守护进程在 2.2.0 版本开始,可以使用命令行快速操作,如重启、停止、查看状态等。
|
||||
|
||||
注意,这里的守护进程操作命令是指 **使用 `--daemon` 方式启动的框架**,如使用 Docker、screen、tmux 等方式挂后台跑则此命令不可用!
|
||||
|
||||
```bash
|
||||
vendor/bin/start daemon:status # 查看守护进程的状态
|
||||
vendor/bin/start daemon:reload # 重载框架
|
||||
vendor/bin/start daemon:stop # 停止运行守护进程的框架
|
||||
```
|
||||
|
||||
## 独立启动其他组件
|
||||
|
||||
框架默认不止启动框架的 `server` 命令,还有 `init` 命令和 `simple-http-server` 命令。`init` 命令在上方 Composer 依赖模式中提到过,就是初始化各个文件的。
|
||||
|
||||
@@ -224,3 +224,97 @@ public function test() {
|
||||
|
||||
解决这一问题,就需要用到锁。这种情况下,我们首先考虑的是自旋锁,框架也因此内置了一个方便使用的自旋锁组件。详见下一章:自旋锁。
|
||||
|
||||
## 如何临时缓存大变量
|
||||
|
||||
由于 LightCache 需要提前声明最大大小,所以在某些情况下,比如第三方 API 接口结果临时缓存,可能不太适合使用,这时对于 2.x 版本的多进程炸毛框架是一个新的问题。
|
||||
|
||||
解决方案有三种:
|
||||
|
||||
- 将 `global.php` 中的 `swoole.worker_num` 调整为 `1` 即可,所有除所有主 handler 事件的用户类外其他类均可使用如 `Hello::$store` 类似的静态变量全局存取
|
||||
- 使用 WorkerCache(需要 2.2.0 以上版本)
|
||||
- 使用 Redis(需要安装 `redis` 扩展)
|
||||
|
||||
以上,WorkerCache 是为了弥补 LightCache 的不足而诞生的,以下就是 WorkerCache 的具体内容。
|
||||
|
||||
### WorkerCache 跨进程大缓存
|
||||
|
||||
WorkerCache 和 LightCache 几乎完全不同,WorkerCache 存储的方式说白了就是 PHP 的静态变量,不过框架支持使用封装好的进程间通信进行跨进程读取。但由于需要设置一个存储变量的进程,所以配置文件必须先指定要将数据存到哪个 Worker/TaskWorker 进程中。关于框架内多进程的说明,请见 [进阶 - 多进程 Hack](/advanced/multi-process/)。
|
||||
|
||||
定义:`ZM\Store\WorkerCache`。
|
||||
|
||||
#### 配置
|
||||
|
||||
见 [基本配置](/guide/basic-config/)。
|
||||
|
||||
#### WorkerCache::get()
|
||||
|
||||
定义:`get($key)`。
|
||||
|
||||
`$key` 为指定要获取的键值对的值,如果不存在则返回 null。
|
||||
|
||||
#### WorkerCache::set()
|
||||
|
||||
定义:`set($key, $value, $async = false)`。
|
||||
|
||||
设置变量,你懂的。
|
||||
|
||||
注意,`$value` 可以是被无损 `json_encode` 和 `json_decode` 的变量,闭包(Closure)、资源(resource)等类型不支持存储。
|
||||
|
||||
`$async` 默认为 false,当为 true 时候,不会返回是否成功设置与否,否则会协程等待是否目标进程存储成功。
|
||||
|
||||
#### WorkerCache::unset()
|
||||
|
||||
定义:`unset($key, $async = false)`
|
||||
|
||||
删除键对应的值。`$async` 的意义同上。
|
||||
|
||||
#### WorkerCache::add()
|
||||
|
||||
定义:`add($key, int $value, $async = false)`
|
||||
|
||||
给 int 类型的值加一,如果值不存在,则默认为 0 且加上目标的 `$value`。
|
||||
|
||||
#### WorkerCache::sub()
|
||||
|
||||
定义:`sub($key, int $value, $async = false)`
|
||||
|
||||
给 int 类型的值减一,如果值不存在,则默认为 0 且减去目标的 `$value`。
|
||||
|
||||
```php
|
||||
<?php
|
||||
namespace Module\Example;
|
||||
|
||||
use ZM\Store\WorkerCache;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
|
||||
class Hello {
|
||||
/**
|
||||
* @CQCommand("set_store")
|
||||
*/
|
||||
public function setStorage() {
|
||||
$arg1 = ctx()->getFullArg("请输入要设置的内容名称");
|
||||
$arg2 = ctx()->getFullArg("请输入要设置的内容");
|
||||
WorkerCache::set($arg1, $arg2);
|
||||
return "成功!";
|
||||
}
|
||||
|
||||
/**
|
||||
* @CQCommand("get_store")
|
||||
*/
|
||||
public function getStorage() {
|
||||
$arg1 = ctx()->getFullArg("请输入要获取的内容名称");
|
||||
$data = WorkerCache::get($arg1);
|
||||
return $data ?? "内容不存在!";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<chat-box>
|
||||
) set_store hello world
|
||||
( 成功!
|
||||
) get_store hello
|
||||
( world
|
||||
) get_store foo
|
||||
( 内容不存在!
|
||||
</chat-box>
|
||||
|
||||
|
||||
@@ -116,6 +116,44 @@
|
||||
|
||||
`$conn`: [ConnectionObject](/advanced/connect-ws-client/) 类型,返回一个当前 WS 连接的连接对象。
|
||||
|
||||
## OnPipeMessageEvent()
|
||||
|
||||
当有 其他 Worker 进程通信发来指令,激活响应。(2.2.0 版本可用)
|
||||
|
||||
### 属性
|
||||
|
||||
| 类型 | 值 |
|
||||
| ---------- | ------------------------------------------------------- |
|
||||
| 名称 | `@OnPipeMessageEvent` |
|
||||
| 触发前提 | 当有 WebSocket 连接接入框架后发送过来消息,触发注解事件 |
|
||||
| 命名空间 | `ZM\Annotation\Swoole\OnPipeMessageEvent` |
|
||||
| 适用位置 | 方法 |
|
||||
| 返回值处理 | 无 |
|
||||
|
||||
### 参数
|
||||
|
||||
| 参数名称 | 参数范围 | 用途 | 默认 |
|
||||
| -------- | -------- | ------------ | ---- |
|
||||
| action | `string` | 限定动作名称 | |
|
||||
|
||||
### 用法
|
||||
|
||||
```java
|
||||
@OnPipeMessageEvent("foo")
|
||||
@OnPipeMessageEvent(action="bar")
|
||||
```
|
||||
|
||||
### 事件绑定参数
|
||||
|
||||
`$data`: 数组,内容如下:
|
||||
|
||||
```php
|
||||
[
|
||||
"action" => "你的上面的名称",
|
||||
... //其他自己发送时随便定义,带什么都行
|
||||
]
|
||||
```
|
||||
|
||||
## OnSwooleEvent()
|
||||
|
||||
绑定 Swoole 所相关的事件,例如 WebSocket 接入、收到 WS 消息、关闭 WS 连接,HTTP 请求到达等。这个是旧的统一的 Swoole 事件分发注解。**请尽量使用上面几个新的注解**。
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
| `crash_dir` | 存放崩溃和运行日志的目录 | `zm_data` 下的 `crash/` |
|
||||
| `swoole` | 对应 Swoole server 中 set 的参数,参考Swoole文档 | 见子表 `swoole` |
|
||||
| `light_cache` | 轻量内置 key-value 缓存 | 见字表 `light_cache` |
|
||||
| `worker_cache` | 跨进程变量级缓存 | 见子表 `worker_cache` |
|
||||
| `sql_config` | MySQL 数据库连接信息 | 见子表 `sql_config` |
|
||||
| `redis_config` | Redis 连接信息 | 见子表 `redis_config` |
|
||||
| `access_token` | OneBot 客户端连接约定的token,留空则无 | 空 |
|
||||
@@ -82,6 +83,12 @@
|
||||
| `document_root` | 静态文件的根目录 | `{WORKING_DIR}/resources/html` |
|
||||
| `document_index` | 默认索引的文件名列表 | `["index.html"]` |
|
||||
|
||||
### 子表 worker_cache
|
||||
|
||||
| 配置名称 | 说明 | 默认值 |
|
||||
| -------- | --------------------------- | ------ |
|
||||
| `worker` | 跨进程缓存的存储工作进程 id | 0 |
|
||||
|
||||
## 多环境下的配置文件
|
||||
|
||||
炸毛框架的配置文件模块支持不同环境下的配置文件,主要结构为 `global.{环境}.php`。在一般情况下,炸毛框架默认从教程引导方式根据指令 `vendor/bin/start server` 启动的框架是不带环境控制的。这章将讲述如何根据不同的环境(production / development / staging)来编写配置文件。
|
||||
|
||||
@@ -33,7 +33,7 @@ public function index() {
|
||||
|
||||
首先,你需要了解你需要知道哪些事情才能开始着手使用框架:
|
||||
|
||||
1. Linux 命令行基础
|
||||
1. Linux 命令行(会跑 Linux 程序)
|
||||
2. php 7.2+ 开发环境
|
||||
3. HTTP 协议(可选)
|
||||
4. OneBot 机器人聊天接口标准(可选)
|
||||
|
||||
@@ -1,5 +1,40 @@
|
||||
# 更新日志(v2 版本)
|
||||
|
||||
## v2.2.3
|
||||
|
||||
> 更新时间:2021.1.30
|
||||
|
||||
- 修复:waitMessage() 在 v2.2.2 版本中不可用的 bug
|
||||
- 修复:access_token 无效的问题
|
||||
|
||||
## v2.2.2
|
||||
|
||||
> 更新时间:2021.1.29
|
||||
|
||||
- 修复:模块文件错误时避免循环报错
|
||||
- 优化:代码结构
|
||||
- 修复:在不同进程时调用机器人 API 无法返回且报错的 bug
|
||||
- **修复:机器人无法连接的问题(2.1.6 ~ 2.2.1 受影响)**
|
||||
|
||||
## v2.2.1
|
||||
|
||||
> 更新时间:2021.1.29
|
||||
|
||||
- 修复:配置文件兼容性问题
|
||||
|
||||
## v2.2.0
|
||||
|
||||
> 更新时间:2021.1.29
|
||||
|
||||
- 新增:`@OnPipeMessageEvent` 注解
|
||||
- 新增:进程管理器
|
||||
- 新增:`--daemon` 守护进程化后查看状态以及一系列操作的命令行
|
||||
- 新增:WorkerCache
|
||||
- 修复:路由问题
|
||||
- 修复:`http_header` 配置项不生效的 bug
|
||||
- 优化:框架内部所有异常全部基于 `ZMException`
|
||||
- 优化:SingletonTrait 支持扩展
|
||||
|
||||
## v2.1.6
|
||||
|
||||
> 更新时间:2021.1.18
|
||||
|
||||
@@ -94,6 +94,7 @@ nav:
|
||||
- 从 v1 升级: advanced/to-v2.md
|
||||
- 内部类文件手册: advanced/inside-class.md
|
||||
- 接入 WebSocket 客户端: advanced/connect-ws-client.md
|
||||
- 框架多进程: advanced/multi-process.md
|
||||
- FAQ: FAQ.md
|
||||
- 更新日志:
|
||||
- 更新日志(v2): update/v2.md
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace Custom\Command;
|
||||
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class CustomCommand extends Command
|
||||
{
|
||||
// the name of the command (the part after "bin/console")
|
||||
protected static $defaultName = 'custom';
|
||||
|
||||
protected function configure() {
|
||||
$this->setDescription("custom description | 自定义命令的描述字段");
|
||||
$this->addOption("failure", null, null, "以错误码为1返回结果");
|
||||
// ...
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
if ($input->getOption("failure")) {
|
||||
$output->writeln("<error>Hello error! I am wrong message.</error>");
|
||||
return Command::FAILURE;
|
||||
} else {
|
||||
$output->writeln("<comment>Hello world! I am successful message.</comment>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
<?php #plain
|
||||
<?php /** @noinspection PhpFullyQualifiedNameUsageInspection */ #plain
|
||||
|
||||
//这里写你的全局函数
|
||||
function pgo(callable $func, $name = "default") {
|
||||
|
||||
@@ -89,7 +89,7 @@ class Hello
|
||||
* @return string
|
||||
*/
|
||||
public function paramGet($param) {
|
||||
return "Your name: {$param["name"]}";
|
||||
return "Hello, ".$param["name"];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,17 +3,16 @@
|
||||
|
||||
namespace ZM\API;
|
||||
|
||||
use Co;
|
||||
use ZM\ConnectionManager\ConnectionObject;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\Lock\SpinLock;
|
||||
use ZM\Store\ZMAtomic;
|
||||
use ZM\Utils\CoMessage;
|
||||
|
||||
trait CQAPI
|
||||
{
|
||||
/**
|
||||
* @param ConnectionObject $connection
|
||||
* @param $connection
|
||||
* @param $reply
|
||||
* @param |null $function
|
||||
* @return bool|array
|
||||
@@ -35,21 +34,20 @@ trait CQAPI
|
||||
$r[$api_id] = [
|
||||
"data" => $reply,
|
||||
"time" => microtime(true),
|
||||
"self_id" => $connection->getOption("connect_id")
|
||||
"self_id" => $connection->getOption("connect_id"),
|
||||
"echo" => $api_id
|
||||
];
|
||||
if ($function === true) $r[$api_id]["coroutine"] = Co::getuid();
|
||||
LightCacheInside::set("wait_api", "wait_api", $r);
|
||||
SpinLock::unlock("wait_api");
|
||||
if (server()->push($connection->getFd(), json_encode($reply))) {
|
||||
if ($function === true) {
|
||||
Co::suspend();
|
||||
return CoMessage::yieldByWS($r[$api_id], ["echo"], 60);
|
||||
} else {
|
||||
SpinLock::lock("wait_api");
|
||||
$r = LightCacheInside::get("wait_api", "wait_api");
|
||||
$data = $r[$api_id];
|
||||
unset($r[$api_id]);
|
||||
LightCacheInside::set("wait_api", "wait_api", $r);
|
||||
SpinLock::unlock("wait_api");
|
||||
return isset($data['result']) ? $data['result'] : null;
|
||||
}
|
||||
return true;
|
||||
} else {
|
||||
|
||||
@@ -228,9 +228,9 @@ class ZMRobot
|
||||
/**
|
||||
* 群组单人禁言
|
||||
* @link https://github.com/howmanybots/onebot/blob/master/v11/specs/api/public.md#set_group_ban-%E7%BE%A4%E7%BB%84%E5%8D%95%E4%BA%BA%E7%A6%81%E8%A8%80
|
||||
* @param int $group_id
|
||||
* @param int $user_id
|
||||
* @param int $duration
|
||||
* @param $group_id
|
||||
* @param $user_id
|
||||
* @param $duration
|
||||
* @return array|bool|null
|
||||
*/
|
||||
public function setGroupBan($group_id, $user_id, $duration = 1800) {
|
||||
|
||||
@@ -9,11 +9,10 @@ use ZM\Console\Console;
|
||||
use ReflectionClass;
|
||||
use ReflectionException;
|
||||
use ReflectionMethod;
|
||||
use ZM\Annotation\Http\{HandleAfter, HandleBefore, Controller, HandleException, Middleware, MiddlewareClass, RequestMapping};
|
||||
use ZM\Annotation\Http\{HandleAfter, HandleBefore, HandleException, Middleware, MiddlewareClass, RequestMapping};
|
||||
use ZM\Annotation\Interfaces\Level;
|
||||
use ZM\Annotation\Module\Closed;
|
||||
use ZM\Http\RouteManager;
|
||||
use ZM\Utils\DataProvider;
|
||||
|
||||
class AnnotationParser
|
||||
{
|
||||
@@ -91,6 +90,7 @@ class AnnotationParser
|
||||
if ($vs instanceof ErgodicAnnotation) {
|
||||
foreach (($this->annotation_map[$v]["methods"] ?? []) as $method) {
|
||||
$copy = clone $vs;
|
||||
/** @noinspection PhpUndefinedFieldInspection */
|
||||
$copy->method = $method->getName();
|
||||
$this->annotation_map[$v]["methods_annotations"][$method->getName()][] = $copy;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
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;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
namespace ZM\Annotation\Http;
|
||||
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
|
||||
|
||||
@@ -5,7 +5,6 @@ namespace ZM\Annotation\Swoole;
|
||||
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use ZM\Annotation\Interfaces\Rule;
|
||||
|
||||
/**
|
||||
* @Annotation
|
||||
|
||||
24
src/ZM/Annotation/Swoole/OnPipeMessageEvent.php
Normal file
24
src/ZM/Annotation/Swoole/OnPipeMessageEvent.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Annotation\Swoole;
|
||||
|
||||
|
||||
use Doctrine\Common\Annotations\Annotation\Required;
|
||||
use Doctrine\Common\Annotations\Annotation\Target;
|
||||
use ZM\Annotation\AnnotationBase;
|
||||
|
||||
/**
|
||||
* Class OnPipeMessageEvent
|
||||
* @package ZM\Annotation\Swoole
|
||||
* @Annotation
|
||||
* @Target("METHOD")
|
||||
*/
|
||||
class OnPipeMessageEvent extends AnnotationBase
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
* @Required()
|
||||
*/
|
||||
public $action;
|
||||
}
|
||||
31
src/ZM/Command/DaemonCommand.php
Normal file
31
src/ZM/Command/DaemonCommand.php
Normal file
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Command;
|
||||
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ZM\Utils\DataProvider;
|
||||
|
||||
abstract class DaemonCommand extends Command
|
||||
{
|
||||
protected $daemon_file = null;
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
$pid_path = DataProvider::getWorkingDir() . "/.daemon_pid";
|
||||
if (!file_exists($pid_path)) {
|
||||
$output->writeln("<comment>没有检测到正在运行的守护进程!</comment>");
|
||||
die();
|
||||
}
|
||||
$file = json_decode(file_get_contents($pid_path), true);
|
||||
if ($file === null || posix_getsid(intval($file["pid"])) === false) {
|
||||
$output->writeln("<comment>未检测到正在运行的守护进程!</comment>");
|
||||
unlink($pid_path);
|
||||
die();
|
||||
}
|
||||
$this->daemon_file = $file;
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
24
src/ZM/Command/DaemonReloadCommand.php
Normal file
24
src/ZM/Command/DaemonReloadCommand.php
Normal file
@@ -0,0 +1,24 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class DaemonReloadCommand extends DaemonCommand
|
||||
{
|
||||
protected static $defaultName = 'daemon:reload';
|
||||
|
||||
protected function configure() {
|
||||
$this->setDescription("重载守护进程下的用户代码(仅限--daemon模式可用)");
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
parent::execute($input, $output);
|
||||
system("kill -USR1 " . intval($this->daemon_file["pid"]));
|
||||
$output->writeln("<info>成功重载!</info>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
30
src/ZM/Command/DaemonStatusCommand.php
Normal file
30
src/ZM/Command/DaemonStatusCommand.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
|
||||
class DaemonStatusCommand extends DaemonCommand
|
||||
{
|
||||
protected static $defaultName = 'daemon:status';
|
||||
|
||||
protected function configure() {
|
||||
$this->setDescription("查看守护进程框架的运行状态(仅限--daemon模式可用)");
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
parent::execute($input, $output);
|
||||
$output->writeln("<info>框架运行中,pid:" . $this->daemon_file["pid"] . "</info>");
|
||||
$output->writeln("<comment>----- 以下是stdout内容 -----</comment>");
|
||||
$stdout = file_get_contents($this->daemon_file["stdout"]);
|
||||
$stdout = explode("\n", $stdout);
|
||||
for ($i = 10; $i > 0; --$i) {
|
||||
if (isset($stdout[count($stdout) - $i]))
|
||||
echo $stdout[count($stdout) - $i] . PHP_EOL;
|
||||
}
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
26
src/ZM/Command/DaemonStopCommand.php
Normal file
26
src/ZM/Command/DaemonStopCommand.php
Normal file
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Command;
|
||||
|
||||
use Symfony\Component\Console\Command\Command;
|
||||
use Symfony\Component\Console\Input\InputInterface;
|
||||
use Symfony\Component\Console\Output\OutputInterface;
|
||||
use ZM\Utils\DataProvider;
|
||||
|
||||
class DaemonStopCommand extends DaemonCommand
|
||||
{
|
||||
protected static $defaultName = 'daemon:stop';
|
||||
|
||||
protected function configure() {
|
||||
$this->setDescription("停止守护进程下运行的框架(仅限--daemon模式可用)");
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
parent::execute($input, $output);
|
||||
system("kill -TERM ".intval($this->daemon_file["pid"]));
|
||||
unlink(DataProvider::getWorkingDir()."/.daemon_pid");
|
||||
$output->writeln("<info>成功停止!</info>");
|
||||
return Command::SUCCESS;
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,6 @@ use ZM\Framework;
|
||||
|
||||
class RunServerCommand extends Command
|
||||
{
|
||||
// the name of the command (the part after "bin/console")
|
||||
protected static $defaultName = 'server';
|
||||
|
||||
protected function configure() {
|
||||
@@ -31,27 +30,17 @@ class RunServerCommand extends Command
|
||||
]);
|
||||
$this->setDescription("Run zhamao-framework | 启动框架");
|
||||
$this->setHelp("直接运行可以启动");
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
protected function execute(InputInterface $input, OutputInterface $output) {
|
||||
if(($opt = $input->getOption("env")) !== null) {
|
||||
if(!in_array($opt, ["production", "staging", "development", ""])) {
|
||||
if (($opt = $input->getOption("env")) !== null) {
|
||||
if (!in_array($opt, ["production", "staging", "development", ""])) {
|
||||
$output->writeln("<error> \"--env\" option only accept production, development, staging and [empty] ! </error>");
|
||||
return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
// ... put here the code to run in your command
|
||||
// this method must return an integer number with the "exit status code"
|
||||
// of the command. You can also use these constants to make code more readable
|
||||
if (LOAD_MODE == 0) echo "* This is repository mode.\n";
|
||||
(new Framework($input->getOptions()))->start();
|
||||
// return this if there was no problem running the command
|
||||
// (it's equivalent to returning int(0))
|
||||
return Command::SUCCESS;
|
||||
|
||||
// or return this if some error happened during the execution
|
||||
// (it's equivalent to returning int(1))
|
||||
// return Command::FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,9 @@ namespace ZM;
|
||||
|
||||
|
||||
use Exception;
|
||||
use ZM\Command\DaemonReloadCommand;
|
||||
use ZM\Command\DaemonStatusCommand;
|
||||
use ZM\Command\DaemonStopCommand;
|
||||
use ZM\Command\InitCommand;
|
||||
use ZM\Command\PureHttpCommand;
|
||||
use ZM\Command\RunServerCommand;
|
||||
@@ -40,7 +43,6 @@ class ConsoleApplication extends Application
|
||||
* @noinspection RedundantSuppression
|
||||
*/
|
||||
require_once WORKING_DIR . "/vendor/autoload.php";
|
||||
echo "* This is repository mode.\n";
|
||||
$composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true);
|
||||
if (!isset($composer["autoload"]["psr-4"]["Module\\"])) {
|
||||
echo "框架源码模式需要在autoload文件中添加Module目录为自动加载,是否添加?[Y/n] ";
|
||||
@@ -64,6 +66,9 @@ class ConsoleApplication extends Application
|
||||
}
|
||||
|
||||
$this->addCommands([
|
||||
new DaemonStatusCommand(),
|
||||
new DaemonReloadCommand(),
|
||||
new DaemonStopCommand(),
|
||||
new RunServerCommand(), //运行主服务的指令控制器
|
||||
new InitCommand(), //初始化用的,用于项目初始化和phar初始化
|
||||
new PureHttpCommand() //纯HTTP服务器指令
|
||||
|
||||
@@ -145,7 +145,7 @@ class Context implements ContextInterface
|
||||
if ($prompt != "") $this->reply($prompt);
|
||||
|
||||
try {
|
||||
$r = CoMessage::yieldByWS($this->getData(), ["user_id", "self_id", "message_type", onebot_target_id_name($this->getMessageType())]);
|
||||
$r = CoMessage::yieldByWS($this->getData(), ["user_id", "self_id", "message_type", onebot_target_id_name($this->getMessageType())], $timeout);
|
||||
} catch (Exception $e) {
|
||||
$r = false;
|
||||
}
|
||||
|
||||
@@ -31,7 +31,6 @@ class DB
|
||||
|
||||
/**
|
||||
* @param $table_name
|
||||
* @param bool $enable_cache
|
||||
* @return Table
|
||||
* @throws DbException
|
||||
*/
|
||||
@@ -95,6 +94,7 @@ class DB
|
||||
$ps = $conn->prepare($line);
|
||||
if ($ps === false) {
|
||||
SqlPoolStorage::$sql_pool->put(null);
|
||||
/** @noinspection PhpUndefinedFieldInspection */
|
||||
throw new DbException("SQL语句查询错误," . $line . ",错误信息:" . $conn->error);
|
||||
} else {
|
||||
if (!($ps instanceof PDOStatement) && !($ps instanceof PDOStatementProxy)) {
|
||||
|
||||
@@ -47,7 +47,7 @@ class Table
|
||||
return new DeleteBody($this);
|
||||
}
|
||||
|
||||
public function statement($line){
|
||||
public function statement(){
|
||||
$this->cache = [];
|
||||
//TODO: 无返回的statement语句
|
||||
}
|
||||
|
||||
@@ -87,7 +87,6 @@ class EventDispatcher
|
||||
/**
|
||||
* @param mixed ...$params
|
||||
* @throws Exception
|
||||
* @throws InterruptException
|
||||
*/
|
||||
public function dispatchEvents(...$params) {
|
||||
try {
|
||||
@@ -104,10 +103,7 @@ class EventDispatcher
|
||||
} catch (InterruptException $e) {
|
||||
$this->store = $e->return_var;
|
||||
$this->status = self::STATUS_INTERRUPTED;
|
||||
} catch (Exception $e) {
|
||||
$this->status = self::STATUS_EXCEPTION;
|
||||
throw $e;
|
||||
} catch (Error $e) {
|
||||
} catch (Exception | Error $e) {
|
||||
$this->status = self::STATUS_EXCEPTION;
|
||||
throw $e;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
<?php /** @noinspection PhpComposerExtensionStubsInspection */
|
||||
<?php /** @noinspection PhpUnreachableStatementInspection */
|
||||
|
||||
/** @noinspection PhpComposerExtensionStubsInspection */
|
||||
|
||||
|
||||
namespace ZM\Event;
|
||||
@@ -14,12 +16,12 @@ use Swoole\Database\PDOConfig;
|
||||
use Swoole\Database\PDOPool;
|
||||
use Swoole\Event;
|
||||
use Swoole\Process;
|
||||
use Swoole\Timer;
|
||||
use ZM\Annotation\AnnotationParser;
|
||||
use ZM\Annotation\Http\RequestMapping;
|
||||
use ZM\Annotation\Swoole\OnCloseEvent;
|
||||
use ZM\Annotation\Swoole\OnMessageEvent;
|
||||
use ZM\Annotation\Swoole\OnOpenEvent;
|
||||
use ZM\Annotation\Swoole\OnPipeMessageEvent;
|
||||
use ZM\Annotation\Swoole\OnRequestEvent;
|
||||
use ZM\Annotation\Swoole\OnStart;
|
||||
use ZM\Annotation\Swoole\OnSwooleEvent;
|
||||
@@ -42,6 +44,7 @@ use ZM\Module\QQBot;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\MySQL\SqlPoolStorage;
|
||||
use ZM\Store\Redis\ZMRedisPool;
|
||||
use ZM\Store\WorkerCache;
|
||||
use ZM\Store\ZMBuf;
|
||||
use ZM\Utils\DataProvider;
|
||||
use ZM\Utils\HttpUtil;
|
||||
@@ -71,11 +74,18 @@ class ServerEventHandler
|
||||
}
|
||||
Process::signal(SIGINT, function () use ($r) {
|
||||
echo "\r";
|
||||
Console::warning("Server interrupted by keyboard on Master.");
|
||||
Console::warning("Server interrupted(SIGINT) on Master.");
|
||||
if ((Framework::$server->inotify ?? null) !== null)
|
||||
/** @noinspection PhpUndefinedFieldInspection */ Event::del(Framework::$server->inotify);
|
||||
ZMUtil::stop();
|
||||
});
|
||||
if (Framework::$argv["daemon"]) {
|
||||
$daemon_data = json_encode([
|
||||
"pid" => \server()->master_pid,
|
||||
"stdout" => ZMConfig::get("global")["swoole"]["log_file"]
|
||||
], 128 | 256);
|
||||
file_put_contents(DataProvider::getWorkingDir() . "/.daemon_pid", $daemon_data);
|
||||
}
|
||||
if (Framework::$argv["watch"]) {
|
||||
if (extension_loaded('inotify')) {
|
||||
Console::warning("Enabled File watcher, do not use in production.");
|
||||
@@ -84,11 +94,11 @@ class ServerEventHandler
|
||||
$this->addWatcher(DataProvider::getWorkingDir() . "/src", $fd);
|
||||
Event::add($fd, function () use ($fd) {
|
||||
$r = inotify_read($fd);
|
||||
var_dump($r);
|
||||
dump($r);
|
||||
ZMUtil::reload();
|
||||
});
|
||||
} else {
|
||||
Console::warning("You have not loaded inotify extension.");
|
||||
Console::warning("You have not loaded \"inotify\" extension, please install first.");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -228,7 +238,7 @@ class ServerEventHandler
|
||||
Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
|
||||
Console::error("Maybe it caused by your own code if in your own Module directory.");
|
||||
Console::log($e->getTraceAsString(), 'gray');
|
||||
ZMUtil::stop();
|
||||
posix_kill($server->master_pid, SIGINT);
|
||||
}
|
||||
} else {
|
||||
// 这里是TaskWorker初始化的内容部分
|
||||
@@ -245,7 +255,7 @@ class ServerEventHandler
|
||||
Console::error("PHP Error: " . $e->getMessage() . " in " . $e->getFile() . " on line " . $e->getLine());
|
||||
Console::error("Maybe it caused by your own code if in your own Module directory.");
|
||||
Console::log($e->getTraceAsString(), 'gray');
|
||||
ZMUtil::stop();
|
||||
posix_kill($server->master_pid, SIGINT);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -258,11 +268,12 @@ class ServerEventHandler
|
||||
public function onMessage($server, Frame $frame) {
|
||||
|
||||
Console::debug("Calling Swoole \"message\" from fd=" . $frame->fd . ": " . TermColor::ITALIC . $frame->data . TermColor::RESET);
|
||||
unset(Context::$context[\Swoole\Coroutine::getCid()]);
|
||||
unset(Context::$context[Coroutine::getCid()]);
|
||||
$conn = ManagerGM::get($frame->fd);
|
||||
set_coroutine_params(["server" => $server, "frame" => $frame, "connection" => $conn]);
|
||||
$dispatcher1 = new EventDispatcher(OnMessageEvent::class);
|
||||
$dispatcher1->setRuleFunction(function ($v) {
|
||||
/** @noinspection PhpUnreachableStatementInspection */
|
||||
return ctx()->getConnection()->getName() == $v->connect_type && eval("return " . $v->getRule() . ";");
|
||||
});
|
||||
|
||||
@@ -303,6 +314,9 @@ class ServerEventHandler
|
||||
*/
|
||||
public function onRequest(?Request $request, ?\Swoole\Http\Response $response) {
|
||||
$response = new Response($response);
|
||||
foreach (ZMConfig::get("global")["http_header"] as $k => $v) {
|
||||
$response->setHeader($k, $v);
|
||||
}
|
||||
unset(Context::$context[Co::getCid()]);
|
||||
Console::debug("Calling Swoole \"request\" event from fd=" . $request->fd);
|
||||
set_coroutine_params(["request" => $request, "response" => $response]);
|
||||
@@ -387,6 +401,14 @@ class ServerEventHandler
|
||||
Console::debug("Calling Swoole \"open\" event from fd=" . $request->fd);
|
||||
unset(Context::$context[Co::getCid()]);
|
||||
$type = strtolower($request->header["x-client-role"] ?? $request->get["type"] ?? "");
|
||||
$access_token = explode(" ", $request->header["authorization"] ?? $request->get["token"] ?? "")[1] ?? "";
|
||||
if (($a = ZMConfig::get("global", "access_token")) != "") {
|
||||
if ($access_token !== $a) {
|
||||
$server->close($request->fd);
|
||||
Console::warning("Unauthorized access_token: ".$access_token);
|
||||
return;
|
||||
}
|
||||
}
|
||||
$type_conn = ManagerGM::getTypeClassName($type);
|
||||
ManagerGM::pushConnect($request->fd, $type_conn);
|
||||
$conn = ManagerGM::get($request->fd);
|
||||
@@ -477,9 +499,10 @@ class ServerEventHandler
|
||||
|
||||
/**
|
||||
* @SwooleHandler("pipeMessage")
|
||||
* @param $server
|
||||
* @param Server $server
|
||||
* @param $src_worker_id
|
||||
* @param $data
|
||||
* @throws Exception
|
||||
*/
|
||||
public function onPipeMessage(Server $server, $src_worker_id, $data) {
|
||||
//var_dump($data, $server->worker_id);
|
||||
@@ -490,28 +513,75 @@ class ServerEventHandler
|
||||
$obj = $data["data"];
|
||||
Co::resume($obj["coroutine"]);
|
||||
break;
|
||||
case "stop":
|
||||
Console::verbose('正在清理 #' . $server->worker_id . ' 的计时器');
|
||||
Timer::clearAll();
|
||||
case "getWorkerCache":
|
||||
$r = WorkerCache::get($data["key"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "terminate":
|
||||
$server->stop();
|
||||
case "setWorkerCache":
|
||||
$r = WorkerCache::set($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case 'echo':
|
||||
Console::success('接收到来自 #' . $src_worker_id . ' 的消息');
|
||||
case "unsetWorkerCache":
|
||||
$r = WorkerCache::unset($data["key"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case 'send':
|
||||
$server->sendMessage(json_encode(["action" => "echo"]), $data["target"]);
|
||||
case "asyncAddWorkerCache":
|
||||
WorkerCache::add($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncSubWorkerCache":
|
||||
WorkerCache::sub($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncSetWorkerCache":
|
||||
WorkerCache::set($data["key"], $data["value"], true);
|
||||
break;
|
||||
case "asyncUnsetWorkerCache":
|
||||
WorkerCache::unset($data["key"], true);
|
||||
break;
|
||||
case "addWorkerCache":
|
||||
$r = WorkerCache::add($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "subWorkerCache":
|
||||
$r = WorkerCache::sub($data["key"], $data["value"]);
|
||||
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
|
||||
$server->sendMessage(json_encode($action, 256), $src_worker_id);
|
||||
break;
|
||||
case "returnWorkerCache":
|
||||
WorkerCache::$transfer[$data["cid"]] = $data["value"];
|
||||
zm_resume($data["cid"]);
|
||||
break;
|
||||
default:
|
||||
echo $data . PHP_EOL;
|
||||
$dispatcher = new EventDispatcher(OnPipeMessageEvent::class);
|
||||
$dispatcher->setRuleFunction(function (OnPipeMessageEvent $v) use ($data) {
|
||||
return $v->action == $data["action"];
|
||||
});
|
||||
$dispatcher->dispatchEvents($data);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @SwooleHandler("task")
|
||||
* @param Server|null $server
|
||||
* @param Server\Task $task
|
||||
* @return mixed
|
||||
* @noinspection PhpUnusedParameterInspection
|
||||
*/
|
||||
public function onTask() {
|
||||
public function onTask(?Server $server, Server\Task $task) {
|
||||
$data = $task->data;
|
||||
switch ($data["action"]) {
|
||||
case "runMethod":
|
||||
$c = $data["class"];
|
||||
$ss = new $c();
|
||||
$method = $data["method"];
|
||||
$ps = $data["params"];
|
||||
$task->finish($ss->$method(...$ps));
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -250,6 +250,7 @@ class Framework
|
||||
case 'daemon':
|
||||
if ($y) {
|
||||
$this->server_set["daemonize"] = 1;
|
||||
Console::$theme = "no-color";
|
||||
Console::log("已启用守护进程,输出重定向到 " . $this->server_set["log_file"]);
|
||||
$terminal_id = null;
|
||||
}
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
namespace ZM\Http;
|
||||
|
||||
|
||||
use ZM\Console\Console;
|
||||
|
||||
class Response
|
||||
{
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ use Symfony\Component\Routing\Route;
|
||||
use Symfony\Component\Routing\RouteCollection;
|
||||
use ZM\Annotation\Http\Controller;
|
||||
use ZM\Annotation\Http\RequestMapping;
|
||||
use ZM\Console\Console;
|
||||
|
||||
class RouteManager
|
||||
{
|
||||
@@ -25,8 +26,9 @@ class RouteManager
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$route_name = $prefix."/".$vss->route;
|
||||
$tail = trim($vss->route, "/");
|
||||
$route_name = $prefix.($tail === "" ? "" : "/").$tail;
|
||||
Console::debug("添加路由:".$route_name);
|
||||
$route = new Route($route_name, ['_class' => $class, '_method' => $method]);
|
||||
$route->setMethods($vss->request_method);
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
namespace ZM\Module;
|
||||
|
||||
use Swoole\Coroutine;
|
||||
use Exception;
|
||||
use ZM\Annotation\CQ\CQAPIResponse;
|
||||
use ZM\Annotation\CQ\CQBefore;
|
||||
use ZM\Annotation\CQ\CQCommand;
|
||||
@@ -14,8 +14,6 @@ use ZM\Annotation\CQ\CQRequest;
|
||||
use ZM\Event\EventDispatcher;
|
||||
use ZM\Exception\InterruptException;
|
||||
use ZM\Exception\WaitTimeoutException;
|
||||
use ZM\Store\LightCacheInside;
|
||||
use ZM\Store\Lock\SpinLock;
|
||||
use ZM\Utils\CoMessage;
|
||||
|
||||
/**
|
||||
@@ -26,27 +24,27 @@ class QQBot
|
||||
{
|
||||
/**
|
||||
* @throws InterruptException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function handle() {
|
||||
try {
|
||||
$data = json_decode(context()->getFrame()->data, true);
|
||||
set_coroutine_params(["data" => $data]);
|
||||
if (isset($data["post_type"])) {
|
||||
//echo TermColor::ITALIC.json_encode($data, 128|256).TermColor::RESET.PHP_EOL;
|
||||
set_coroutine_params(["data" => $data]);
|
||||
ctx()->setCache("level", 0);
|
||||
//Console::debug("Calling CQ Event from fd=" . ctx()->getConnection()->getFd());
|
||||
if ($data["post_type"] != "meta_event") {
|
||||
$r = $this->dispatchBeforeEvents($data); // before在这里执行,元事件不执行before为减少不必要的调试日志
|
||||
if ($r->store === "block") EventDispatcher::interrupt();
|
||||
}
|
||||
if (CoMessage::resumeByWS()) {
|
||||
EventDispatcher::interrupt();
|
||||
}
|
||||
//Console::warning("最上数据包:".json_encode($data));
|
||||
$this->dispatchEvents($data);
|
||||
} else {
|
||||
$this->dispatchAPIResponse($data);
|
||||
}
|
||||
if (isset($data["echo"]) || isset($data["post_type"])) {
|
||||
if (CoMessage::resumeByWS()) EventDispatcher::interrupt();
|
||||
}
|
||||
if (isset($data["post_type"])) $this->dispatchEvents($data);
|
||||
else $this->dispatchAPIResponse($data);
|
||||
} /** @noinspection PhpRedundantCatchClauseInspection */ catch (WaitTimeoutException $e) {
|
||||
$e->module->finalReply($e->getMessage());
|
||||
}
|
||||
@@ -55,7 +53,7 @@ class QQBot
|
||||
/**
|
||||
* @param $data
|
||||
* @return EventDispatcher
|
||||
* @throws InterruptException
|
||||
* @throws Exception
|
||||
*/
|
||||
public function dispatchBeforeEvents($data) {
|
||||
$before = new EventDispatcher(CQBefore::class);
|
||||
@@ -177,45 +175,16 @@ class QQBot
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param $req
|
||||
* @throws Exception
|
||||
*/
|
||||
private function dispatchAPIResponse($req) {
|
||||
$status = $req["status"];
|
||||
$retcode = $req["retcode"];
|
||||
$data = $req["data"];
|
||||
if (isset($req["echo"]) && is_numeric($req["echo"])) {
|
||||
$r = LightCacheInside::get("wait_api", "wait_api");
|
||||
if (isset($r[$req["echo"]])) {
|
||||
$origin = $r[$req["echo"]];
|
||||
$self_id = $origin["self_id"];
|
||||
$response = [
|
||||
"status" => $status,
|
||||
"retcode" => $retcode,
|
||||
"data" => $data,
|
||||
"self_id" => $self_id,
|
||||
"echo" => $req["echo"]
|
||||
];
|
||||
set_coroutine_params(["cq_response" => $response]);
|
||||
$dispatcher = new EventDispatcher(CQAPIResponse::class);
|
||||
$dispatcher->setRuleFunction(function (CQAPIResponse $response) {
|
||||
return $response->retcode == ctx()->getCQResponse()["retcode"];
|
||||
});
|
||||
$dispatcher->dispatchEvents($response);
|
||||
|
||||
$origin_ctx = ctx()->copy();
|
||||
set_coroutine_params($origin_ctx);
|
||||
if (($origin["coroutine"] ?? false) !== false) {
|
||||
SpinLock::lock("wait_api");
|
||||
$r = LightCacheInside::get("wait_api", "wait_api");
|
||||
$r[$req["echo"]]["result"] = $response;
|
||||
LightCacheInside::set("wait_api", "wait_api", $r);
|
||||
SpinLock::unlock("wait_api");
|
||||
Coroutine::resume($origin['coroutine']);
|
||||
}
|
||||
SpinLock::lock("wait_api");
|
||||
$r = LightCacheInside::get("wait_api", "wait_api");
|
||||
unset($r[$req["echo"]]);
|
||||
LightCacheInside::set("wait_api", "wait_api", $r);
|
||||
SpinLock::unlock("wait_api");
|
||||
}
|
||||
}
|
||||
set_coroutine_params(["cq_response" => $req]);
|
||||
$dispatcher = new EventDispatcher(CQAPIResponse::class);
|
||||
$dispatcher->setRuleFunction(function (CQAPIResponse $response) {
|
||||
return $response->retcode == ctx()->getCQResponse()["retcode"];
|
||||
});
|
||||
$dispatcher->dispatchEvents($req);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ namespace ZM\Store;
|
||||
use Exception;
|
||||
use Swoole\Table;
|
||||
use ZM\Console\Console;
|
||||
use ZM\Exception\ZMException;
|
||||
|
||||
class LightCache
|
||||
{
|
||||
@@ -19,6 +20,11 @@ class LightCache
|
||||
|
||||
public static $last_error = '';
|
||||
|
||||
/**
|
||||
* @param $config
|
||||
* @return bool|mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function init($config) {
|
||||
self::$config = $config;
|
||||
self::$kv_table = new Table($config["size"], $config["hash_conflict_proportion"]);
|
||||
@@ -50,11 +56,11 @@ class LightCache
|
||||
|
||||
/**
|
||||
* @param string $key
|
||||
* @return null|string
|
||||
* @throws Exception
|
||||
* @return null|mixed
|
||||
* @throws ZMException
|
||||
*/
|
||||
public static function get(string $key) {
|
||||
if (self::$kv_table === null) throw new Exception("not initialized LightCache");
|
||||
if (self::$kv_table === null) throw new ZMException("not initialized LightCache");
|
||||
self::checkExpire($key);
|
||||
$r = self::$kv_table->get($key);
|
||||
return $r === false ? null : self::parseGet($r);
|
||||
@@ -63,10 +69,10 @@ class LightCache
|
||||
/**
|
||||
* @param string $key
|
||||
* @return mixed|null
|
||||
* @throws Exception
|
||||
* @throws ZMException
|
||||
*/
|
||||
public static function getExpire(string $key) {
|
||||
if (self::$kv_table === null) throw new Exception("not initialized LightCache");
|
||||
if (self::$kv_table === null) throw new ZMException("not initialized LightCache");
|
||||
self::checkExpire($key);
|
||||
$r = self::$kv_table->get($key, "expire");
|
||||
return $r === false ? null : $r - time();
|
||||
@@ -77,10 +83,10 @@ class LightCache
|
||||
* @param string|array|int $value
|
||||
* @param int $expire
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
* @throws ZMException
|
||||
*/
|
||||
public static function set(string $key, $value, int $expire = -1) {
|
||||
if (self::$kv_table === null) throw new Exception("not initialized LightCache");
|
||||
if (self::$kv_table === null) throw new ZMException("not initialized LightCache");
|
||||
if (is_array($value)) {
|
||||
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||
if (strlen($value) >= self::$config["max_strlen"]) return false;
|
||||
@@ -93,7 +99,7 @@ class LightCache
|
||||
$data_type = "bool";
|
||||
$value = json_encode($value);
|
||||
} else {
|
||||
throw new Exception("Only can set string, array and int");
|
||||
throw new ZMException("Only can set string, array and int");
|
||||
}
|
||||
try {
|
||||
return self::$kv_table->set($key, [
|
||||
@@ -110,10 +116,10 @@ class LightCache
|
||||
* @param string $key
|
||||
* @param $value
|
||||
* @return bool|mixed
|
||||
* @throws Exception
|
||||
* @throws ZMException
|
||||
*/
|
||||
public static function update(string $key, $value) {
|
||||
if (self::$kv_table === null) throw new Exception("not initialized LightCache.");
|
||||
if (self::$kv_table === null) throw new ZMException("not initialized LightCache.");
|
||||
if (is_array($value)) {
|
||||
$value = json_encode($value, JSON_UNESCAPED_UNICODE);
|
||||
if (strlen($value) >= self::$config["max_strlen"]) return false;
|
||||
@@ -123,7 +129,7 @@ class LightCache
|
||||
} elseif (is_int($value)) {
|
||||
$data_type = "int";
|
||||
} else {
|
||||
throw new Exception("Only can set string, array and int");
|
||||
throw new ZMException("Only can set string, array and int");
|
||||
}
|
||||
try {
|
||||
if (self::$kv_table->get($key) === false) return false;
|
||||
|
||||
@@ -23,9 +23,10 @@ class LightCacheInside
|
||||
$result = self::$kv_table["wait_api"]->create() && self::$kv_table["connect"]->create();
|
||||
if ($result === false) {
|
||||
self::$last_error = '系统内存不足,申请失败';
|
||||
return $result;
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
86
src/ZM/Store/WorkerCache.php
Normal file
86
src/ZM/Store/WorkerCache.php
Normal file
@@ -0,0 +1,86 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Store;
|
||||
|
||||
|
||||
use ZM\Config\ZMConfig;
|
||||
|
||||
class WorkerCache
|
||||
{
|
||||
public static $config = null;
|
||||
|
||||
public static $store = [];
|
||||
|
||||
public static $transfer = [];
|
||||
|
||||
public static function get($key) {
|
||||
$config = self::$config ?? ZMConfig::get("global", "worker_cache") ?? ["worker" => 0];
|
||||
if ($config["worker"] === server()->worker_id) {
|
||||
return self::$store[$key] ?? null;
|
||||
} else {
|
||||
$action = ["action" => "getWorkerCache", "key" => $key, "cid" => zm_cid()];
|
||||
server()->sendMessage(json_encode($action, JSON_UNESCAPED_UNICODE), $config["worker"]);
|
||||
zm_yield();
|
||||
$p = self::$transfer[zm_cid()] ?? null;
|
||||
unset(self::$transfer[zm_cid()]);
|
||||
return $p;
|
||||
}
|
||||
}
|
||||
|
||||
public static function set($key, $value, $async = false) {
|
||||
$config = self::$config ?? ZMConfig::get("global", "worker_cache");
|
||||
if ($config["worker"] === server()->worker_id) {
|
||||
self::$store[$key] = $value;
|
||||
return true;
|
||||
} else {
|
||||
$action = ["action" => $async ? "asyncSetWorkerCache" : "setWorkerCache", "key" => $key, "value" => $value, "cid" => zm_cid()];
|
||||
return self::processRemote($action, $async, $config);
|
||||
}
|
||||
}
|
||||
|
||||
private static function processRemote($action, $async, $config) {
|
||||
$ss = server()->sendMessage(json_encode($action, JSON_UNESCAPED_UNICODE), $config["worker"]);
|
||||
if(!$ss) return false;
|
||||
if ($async) return true;
|
||||
zm_yield();
|
||||
$p = self::$transfer[zm_cid()] ?? null;
|
||||
unset(self::$transfer[zm_cid()]);
|
||||
return $p;
|
||||
}
|
||||
|
||||
public static function unset($key, $async = false) {
|
||||
$config = self::$config ?? ZMConfig::get("global", "worker_cache");
|
||||
if ($config["worker"] === server()->worker_id) {
|
||||
unset(self::$store[$key]);
|
||||
return true;
|
||||
} else {
|
||||
$action = ["action" => $async ? "asyncUnsetWorkerCache" : "unsetWorkerCache", "key" => $key, "cid" => zm_cid()];
|
||||
return self::processRemote($action, $async, $config);
|
||||
}
|
||||
}
|
||||
|
||||
public static function add($key, int $value, $async = false) {
|
||||
$config = self::$config ?? ZMConfig::get("global", "worker_cache");
|
||||
if ($config["worker"] === server()->worker_id) {
|
||||
if(!isset(self::$store[$key])) self::$store[$key] = 0;
|
||||
self::$store[$key] += $value;
|
||||
return true;
|
||||
} else {
|
||||
$action = ["action" => $async ? "asyncAddWorkerCache" : "addWorkerCache", "key" => $key, "value" => $value, "cid" => zm_cid()];
|
||||
return self::processRemote($action, $async, $config);
|
||||
}
|
||||
}
|
||||
|
||||
public static function sub($key, int $value, $async = false) {
|
||||
$config = self::$config ?? ZMConfig::get("global", "worker_cache");
|
||||
if ($config["worker"] === server()->worker_id) {
|
||||
if(!isset(self::$store[$key])) self::$store[$key] = 0;
|
||||
self::$store[$key] -= $value;
|
||||
return true;
|
||||
} else {
|
||||
$action = ["action" => $async ? "asyncSubWorkerCache" : "subWorkerCache", "key" => $key, "value" => $value, "cid" => zm_cid()];
|
||||
return self::processRemote($action, $async, $config);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,7 @@ class CoMessage
|
||||
* @param array $hang
|
||||
* @param array $compare
|
||||
* @param int $timeout
|
||||
* @return bool
|
||||
* @return mixed
|
||||
* @throws Exception
|
||||
*/
|
||||
public static function yieldByWS(array $hang, array $compare, $timeout = 600) {
|
||||
@@ -57,7 +57,8 @@ class CoMessage
|
||||
foreach ($all as $k => $v) {
|
||||
if(!isset($v["compare"])) continue;
|
||||
foreach ($v["compare"] as $vs) {
|
||||
if ($v[$vs] != ($dat[$vs] ?? null)) {
|
||||
if (!isset($v[$vs], $dat[$vs])) continue 2;
|
||||
if ($v[$vs] != $dat[$vs]) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
@@ -68,7 +69,7 @@ class CoMessage
|
||||
LightCacheInside::set("wait_api", "wait_api", $all);
|
||||
SpinLock::unlock("wait_api");
|
||||
if ($all[$last]["worker_id"] != server()->worker_id) {
|
||||
ZMUtil::sendActionToWorker($all[$k]["worker_id"], "resume_ws_message", $all[$last]);
|
||||
ZMUtil::sendActionToWorker($all[$last]["worker_id"], "resume_ws_message", $all[$last]);
|
||||
} else {
|
||||
Co::resume($all[$last]["coroutine"]);
|
||||
}
|
||||
|
||||
17
src/ZM/Utils/ProcessManager.php
Normal file
17
src/ZM/Utils/ProcessManager.php
Normal file
@@ -0,0 +1,17 @@
|
||||
<?php
|
||||
|
||||
|
||||
namespace ZM\Utils;
|
||||
|
||||
|
||||
class ProcessManager
|
||||
{
|
||||
public static function runOnTask($param, $timeout = 0.5, $dst_worker_id = -1) {
|
||||
return server()->taskwait([
|
||||
"action" => "runMethod",
|
||||
"class" => $param["class"],
|
||||
"method" => $param["method"],
|
||||
"params" => $param["params"]
|
||||
], $timeout, $dst_worker_id);
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,8 @@ trait SingletonTrait
|
||||
*/
|
||||
private static $instance;
|
||||
|
||||
protected static $cached = [];
|
||||
|
||||
/**
|
||||
* @return self
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,7 @@ function phar_classloader($p) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
/** @noinspection PhpIncludeInspection */
|
||||
require_once $filepath;
|
||||
} catch (Exception $e) {
|
||||
echo "Error when finding class: " . $p . PHP_EOL;
|
||||
@@ -75,7 +76,7 @@ function unicode_decode($str) {
|
||||
/**
|
||||
* 获取模块文件夹下的每个类文件的类名称
|
||||
* @param $dir
|
||||
* @param string $indoor_name
|
||||
* @param $indoor_name
|
||||
* @return array
|
||||
*/
|
||||
function getAllClasses($dir, $indoor_name) {
|
||||
@@ -95,6 +96,7 @@ function getAllClasses($dir, $indoor_name) {
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
if ($v == "global_function.php") continue;
|
||||
$class_name = $indoor_name . "\\" . mb_substr($v, 0, -4);
|
||||
$classes [] = $class_name;
|
||||
}
|
||||
|
||||
10
test/usage_test.php
Normal file
10
test/usage_test.php
Normal file
@@ -0,0 +1,10 @@
|
||||
<?php
|
||||
|
||||
use ZM\Exception\ZMException;
|
||||
use ZM\Store\LightCache;
|
||||
|
||||
LightCache::getMemoryUsage();
|
||||
try {
|
||||
LightCache::getExpire('1');
|
||||
} catch (ZMException $e) {
|
||||
}
|
||||
Reference in New Issue
Block a user