Compare commits

..

11 Commits
2.2.4 ... 2.2.5

Author SHA1 Message Date
jerry
9ace85e604 update to 2.2.5 version again
add transaction for SpinLock.php
add getAllCQ() for CQ.php
fix CQ bug
update docs
2021-02-20 16:57:19 +08:00
jerry
f677b0e132 update to 2.2.5 version
add saveToJson and loadFromJson function for DataProvider.php
fix @OnSave annotation not working
adjust swoole timer tick
add hasKey() for WorkerCache.php
2021-02-15 15:15:26 +08:00
jerry
f137f044d0 Merge remote-tracking branch 'origin/master' 2021-02-09 17:09:26 +08:00
jerry
77c12db31a reformat code 2021-02-09 17:09:09 +08:00
Whale
b670cb29fe Update README.md 2021-02-09 11:12:29 +08:00
Whale
95d7bb071d Update README.md 2021-02-09 10:54:59 +08:00
Whale
eadb4c1dee Update README.md 2021-02-09 10:54:05 +08:00
Whale
6672a6c852 Update README.md 2021-02-09 10:53:52 +08:00
Whale
094feddda4 Update README.md 2021-02-09 10:53:15 +08:00
Whale
f86eddb298 Update README.md 2021-02-09 10:48:05 +08:00
Whale
a93b4917cd Update README.md 2021-02-09 10:47:40 +08:00
35 changed files with 643 additions and 140 deletions

View File

@@ -6,21 +6,19 @@
[![作者QQ](https://img.shields.io/badge/作者QQ-627577391-orange.svg)]()
[![zhamao License](https://img.shields.io/hexpm/l/plug.svg?maxAge=2592000)](https://github.com/zhamao-robot/zhamao-framework/blob/master/LICENSE)
[![Latest Stable Version](http://img.shields.io/packagist/v/zhamao/framework.svg)](https://packagist.org/packages/zhamao/framework)
[![Banner](https://img.shields.io/badge/CQHTTP-v11-black)]()
[![Banner](https://img.shields.io/badge/OneBot-v11-black)]()
[![stupid counter](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/stupid.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=stupid)
[![TODO counter](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/TODO.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO)
[![注解数量](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/AnnotationBase.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=AnnotationBase)
[![TODO 数量](https://img.shields.io/github/search/zhamao-robot/zhamao-framework/TODO.svg)](https://github.com/zhamao-robot/zhamao-framework/search?q=TODO)
</div>
## 开发者注意
**开发者 QQ 群670821194**
开发者 QQ 群:**670821194**
**当前 v2 版本已正式发布,此 master 分支为 2.0 版本,如需查看 v1 版本,请移步 `v1-legacy` 分支!**
当前 v2 版本已正式发布,此 master 分支为 2.0 版本,如需查看 v1 版本,请移步 `v1-legacy` 分支!
**2.0 版本如果有问题请第一时间加群反馈!**
有关 3.0 版本的最新情况,请看这里:[Issue #22](https://github.com/zhamao-robot/zhamao-framework/issues/22)
2.0 版本如果有问题请第一时间加群反馈!
## 简介
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务聊天机器人OneBot 兼容的 QQ 机器人对接),包含 Websocket、HTTP 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
@@ -53,16 +51,15 @@ public function index() {
自行构建文档:`mkdocs build -d distribute`
## 特点
- 支持多账号
- 原生为多账号设计,支持多个机器人负载均衡
- 使用 Swoole 多工作进程机制和协程加持,尽可能简单的情况下提升了性能
- 灵活的注解事件绑定机制
- 支持下断点调试Psysh
- 易用的上下文,模块内随处可用
- 采用模块化编写,可单独拆装功能
- 常驻内存,全局缓存变量随处使用
- 采用模块化编写,可自由搭配其他 composer 组件
- 常驻内存,全局缓存变量随处使用,提供多种缓存方案
- 自带 MySQL、Redis 等数据库连接池等数据库连接方案
- 自带 HTTP 服务器、WebSocket 服务器可复用,可以构建属于自己的 HTTP API 接口
- 静态文件服务器
- 本身为 HTTP 服务器、WebSocket 服务器,可以构建属于自己的 HTTP API 接口
- 静态文件服务器,可将前端合并到一起
## 从 v1 升级
炸毛框架 v2 相对 v1 版本改动了不少内容,其中包括框架底层机制、注解事件分发、调试、命名空间等变化,详情可查看上方文档。
@@ -70,11 +67,13 @@ public function index() {
如果旧版框架使用过程中无问题且对新功能暂无需求,可以继续使用 v1 版本,后续也将维护安全类更新和修复致命 bug。
## 贡献和捐赠
如果你在使用过程中发现任何问题,可以提交 Issue 或自行 Fork 后修改并提交 Pull Request。目前项目仅一人维护,耗费精力较大,所以非常欢迎对框架的贡献。
如果你在使用过程中发现任何问题,可以提交 Issue 或自行 Fork 后修改并提交 Pull Request。
目前项目仅一人维护,耗费精力较大,所以非常欢迎对框架的贡献。
本项目为作者闲暇时间开发,如果觉得好用,不妨进行捐助~你的捐助会让我更加有动力完善插件,感谢你的支持!
我们会将捐赠的资金用于本项目驱动的炸毛机器人和框架文档的服务器开销上。
我们会将捐赠的资金用于本项目驱动的炸毛机器人和框架文档的服务器开销上。[捐赠列表](https://github.com/zhamao-robot/thanks)
### 支付宝
![支付宝二维码](/resources/images/alipay_img.jpg)
@@ -94,4 +93,6 @@ public function index() {
**注意**:在你使用 mirai 等 `AGPL-3.0` 协议的机器人软件与框架连接时,使用本框架需要将你编写或修改的部分使用 `AGPL-3.0` 协议重新分发。
在贡献代码时,请保管好自己的全局配置文件中的敏感信息,请勿将带有个人信息的配置文件上传 GitHub 等网站。
![star](https://starchart.cc/zhamao-robot/zhamao-framework.svg)

View File

@@ -3,7 +3,7 @@
"description": "High performance QQ robot and web server development framework",
"minimum-stability": "stable",
"license": "Apache-2.0",
"version": "2.2.4",
"version": "2.2.5",
"extra": {
"exclude_annotate": [
"src/ZM"
@@ -53,6 +53,7 @@
]
},
"require-dev": {
"swoole/ide-helper": "@dev"
"swoole/ide-helper": "@dev",
"phpunit/phpunit": "^9.5"
}
}
}

View File

@@ -0,0 +1,231 @@
# 编写管理员专属功能
众所周知,如果大家使用炸毛框架来开发聊天机器人的话,会比较方便。但是有些地方你一定会感觉还是欠缺了点,比如下面这样,你想编写一个只能由机器人管理员,也就是你自己,才能触发的功能:
```php
/**
* @CQCommand(match="禁言",message_type="group")
*/
public function banSomeone() {
$r1 = ctx()->getNextArg("请输入禁言的人或at他");
$r2 = ctx()->getFullArg("请输入禁言的时间(秒)");
$cq = CQ::getCQ($r1);
if ($cq !== null) {
if ($cq["type"] != "at") return "请at或者输入正确的QQ号";
$r1 = $cq["params"]["qq"];
}
// 群内禁言用户
ctx()->getRobot()->setGroupBan(ctx()->getGroupId(), $r1, $r2);
return "禁言成功!";
}
```
这时候,如果只是自己有绝对的权利,可以将自己的 QQ 号写死在注解 `@CQCommand` 中,并限定 `user_id`(假设我的 QQ 号码为 123456
```php
/**
* @CQCommand(match="禁言",message_type="group",user_id=123456)
*/
```
但是,随着时间的推移,你的机器人伙伴群可能越来越大,这个命令可能不止需要绝对的你来使用,你还要将机器人的部分权利下发给更多的伙伴,怎么办呢?注解里面只能写死的。
答案很简单这时候我们就需要用到框架提供的中间件Middleware。中间件说白了就是在事件执行前、后、过程中抛出的异常对其进行阻断和插入代码比如我们上方在触发禁言这个注解事件前首先要判断执行这个命令的是不是钦定的管理员。
## 第一步:定义中间件
首先,我们需要定义一个中间件。在框架默认提供的脚手架中,包含了一个叫 `TimerMiddleware.php` 的示例中间件,这个示例中间件的目的是非常简单的,就是判断这个注解事件运行了多长时间。假设你有一个机器人功能,这个功能下的代码需要执行很长时间,可以使用这一注解轻松将事件执行的时间打印到终端上。
关于中间件的有关说明,见 [中间件](/event/middleware)。
下面我们假设你已经阅读过中间件注解的文档了,我们着手编写一个判断指令执行者是否是指定的管理员 QQ 的中间件。为了省事和让大家方便地复现,我先在脚手架下的目录 `src/Module/Middleware/` 下新建 PHP 类文件 `AdminMiddleware.php`(和 `TimerMiddleware.php` 在同一个目录)。
```php
<?php
namespace Module\Middleware;
use ZM\Annotation\Http\HandleBefore;
use ZM\Annotation\Http\MiddlewareClass;
use ZM\Exception\ZMException;
use ZM\Http\MiddlewareInterface;
use ZM\Store\LightCache;
/**
* Class AdminMiddleware
* 示例中间件:用于动态管理一些管理员指令的中间件
* @package Module\Middleware
* @MiddlewareClass("admin")
*/
class AdminMiddleware implements MiddlewareInterface
{
/**
* @HandleBefore()
* @return bool
* @throws ZMException
*/
public function onBefore(): bool {
$r = ctx()->getUserId(); // 从上下文获取发消息的用户 QQ
$admin_list = LightCache::get("admin_list") ?? []; // 从轻量缓存获取管理员列表
return in_array($r, $admin_list); // 返回这个 QQ 是否在管理员列表中
}
}
```
其中,`@MiddlewareClass("admin")` 的意思是,定义这个类为名字叫 `admin` 的中间件,同时,所有中间件的类**必须**带上 `implements MiddlewareInterface`,统一接口形式。
`@HandleBefore()` 代表的是这个类下的这个函数onBefore被标注为这个中间件的 `onBefore` 事件,也就是说,如果有别的注解事件插入了这个 `admin` 中间件,那么执行对应注解事件前都要执行一下 `@HandleBefore` 所绑定的这个函数。而这个绑定的函数只能返回 `bool` 类型的值哦!
## 第二步:使用中间件
使用中间件很简单,在需要阻断的注解事件绑定的函数上再加一个注解就好了!我们以上方的禁言例子说明:
```php
/**
* @Middleware("admin")
* @CQCommand(match="禁言",message_type="group")
*/
```
<chat-box>
^ 假设我是管理员
) 禁言 1234567 600
( 禁言成功!
^ 假设我不在管理员名单里
) 禁言 1234567 900
^ 机器人没有回复,因为中间件返回了 false不继续执行
</chat-box>
而这时候有朋友又要问了,如果我有一系列管理员命令,假设都在一个叫 `AdminFunc.php` 的模块类里,我是不是还得一个一个地给注解事件写 `@Middleware("admin")` 呢?当然不需要!如果你这个类所有的注解事件都是机器人的聊天事件(`@CQCommand``@CQMessage`)的话,可以直接给类注解这个中间件,效果等同于给每一个函数写一次中间件注解。
```php
<?php
namespace Module\Example;
use ZM\Annotation\Http\Middleware;
/**
* Class AdminFunc
* @package Module\Example
* @Middleware("admin")
*/
class AdminFunc
{
// ...这里是你的一堆注解事件的函数
}
```
## 第三步:补全代码
上面我们讲到了,中间件里面使用了 `LightCache` 轻量缓存来储存临时的管理员列表,那么我们将这部分的代码完善吧!
=== "src/Module/Example/AdminFunc.php"
```php
<?php
namespace Module\Example;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Http\Middleware;
use ZM\API\CQ;
/**
* Class AdminFunc
* @package Module\Example
* @Middleware("admin")
*/
class AdminFunc
{
/**
* @CQCommand(match="禁言",message_type="group")
*/
public function banSomeone() {
$r1 = ctx()->getNextArg("请输入禁言的人或at他");
$r2 = ctx()->getFullArg("请输入禁言的时间(秒)");
$cq = CQ::getCQ($r1);
if ($cq !== null) {
if ($cq["type"] != "at") return "请at或者输入正确的QQ号";
$r1 = $cq["params"]["qq"];
}
// 群内禁言用户
ctx()->getRobot()->setGroupBan(ctx()->getGroupId(), $r1, $r2);
return "禁言成功!";
}
/**
* @CQCommand(match="解除禁言",message_type="group")
*/
public function unbanSomeone() {
$r1 = ctx()->getNextArg("请输入禁言的人或at他");
$cq = CQ::getCQ($r1);
if ($cq !== null) {
if ($cq["type"] != "at") return "请at或者输入正确的QQ号";
$r1 = $cq["params"]["qq"];
}
// 群内禁言用户
ctx()->getRobot()->setGroupBan(ctx()->getGroupId(), $r1, 0);
return "解除禁言成功!";
}
}
```
=== "src/Module/Example/AdminManager.php"
```php
<?php
namespace Module\Example;
use ZM\Annotation\CQ\CQCommand;
use ZM\Annotation\Http\Middleware;
use ZM\Annotation\Swoole\OnStart;
use ZM\Store\LightCache;
use ZM\Store\Lock\SpinLock;
class AdminManager
{
/**
* @OnStart()
*/
public function onStart() {
if (!LightCache::isset("admin_list")) { //一次性代码首次执行才会执行if
LightCache::set("admin_list", [ // 框架启动时初始化管理员列表
"123456",
"234567"
], -2); // 这里用 -2 的原因是将这一列表持久化保存,避免关闭框架后丢失
}
}
/**
* @CQCommand(match="添加管理员")
* @Middleware("admin")
*/
public function addAdmin() { //只有管理员才能添加管理员
$qq = ctx()->getNextArg("请输入要添加管理员的QQ(qq号码不可at");
SpinLock::lock("admin_list"); //如果是多进程模式的话需要加锁
$ls = LightCache::get("admin_list");
if (!in_array($qq, $ls)) $ls[] = $qq;
LightCache::set("admin_list", $ls, -2);
SpinLock::unlock("admin_list"); //如果是多进程模式的话需要加锁
return "成功添加 $qq 到管理员列表!";
}
}
```
<chat-box>
^ 现在我是 123456
) 禁言 13579 60
( 禁言成功!
) 解除禁言 13579
( 解除禁言成功!
) 添加管理员 98765
( 成功添加 98765 到管理员列表!
^ 现在我是98765
) 禁言 13579
( 请输入禁言的时间(秒)
) 120
( 禁言成功!
</chat-box>

View File

@@ -82,15 +82,20 @@ class Hello {
CQ 码字符反转义。
定义:`CQ::encode($msg, $is_content = false)`
`$is_content` 为 true 时,会将 `&#44;` 转义为 `,`
| 反转义前 | 反转义后 |
| -------- | -------- |
| `&amp;` | `&` |
| `&#91;` | `[` |
| `&#93;` | `]` |
| `&#44;` | `,` |
```php
$str = CQ::decode("&#91;我只是一条普通的文本&#93;");
// 转换为 "[我只是一条普通的文本]"
$str = CQ::decode("&#91;CQ:at,qq=我只是一条普通的文本&#93;");
// 转换为 "[CQ:at,qq=我只是一条普通的文本]"
```
### CQ::encode()
@@ -102,6 +107,14 @@ $str = CQ::encode("[CQ:我只是一条普通的文本]");
// $str: "&#91;CQ:我只是一条普通的文本&#93;"
```
定义:`CQ::encode($msg, $is_content = false)`
`$is_content` 为 true 时,会将 `,` 转义为 `&#44;`
### CQ::escape()
`CQ::encode()`
### CQ::removeCQ()
去除字符串中所有的 CQ 码。
@@ -111,6 +124,48 @@ $str = CQ::removeCQ("[CQ:at,qq=all]这是带表情的全体消息[CQ:face,id=8]"
// $str: "这是带表情的全体消息"
```
### CQ::getCQ()
解析 CQ 码。
- 参数:`getCQ($msg);`:要解析出 CQ 码的消息。
- 返回:`数组 | null`,见下表
| 键名 | 说明 |
| ------ | ------------------------------------------------------------ |
| type | CQ码类型比如 `[CQ:at]` 中的 `at` |
| params | 参数列表,比如 `[CQ:image,file=123.jpg,url=http://a.com/a.jpg]`params 为 `["file" => "123","url" => "http://a.com/a.jpg"]` |
| start | 此 CQ 码在字符串中的起始位置 |
| end | 此 CQ 码在字符串中的结束位置 |
### CQ::getAllCQ()
解析 CQ 码,和 `getCQ()` 的区别是,这个会将字符串中的所有 CQ 码都解析出来,并以同样上方解析出来的数组格式返回。
```php
CQ::getAllCQ("[CQ:at,qq=123]你好啊[CQ:at,qq=456]");
/*
[
[
"type" => "at",
"params" => [
"qq" => "123",
],
"start" => 0,
"end" => 13,
],
[
"type" => "at",
"params" => [
"qq" => "456",
],
"start" => 17,
"end" => 30,
],
]
*/
```
## CQ 码列表
### CQ::face() - 发送 QQ 表情
@@ -449,11 +504,31 @@ public function xmlTest() {
发送 QQ 兼容的 JSON 多媒体消息。
定义:`CQ::json($data)`
定义:`CQ::json($data, $resid = 0)`
参数同上,内含 JSON 字符串即可。
其中 `$resid` 是面向 go-cqhttp 扩展的参数,默认不填为 0走小程序通道填了走富文本通道发送。
!!! tip "提示"
因为某些众所周知的原因XML 和 JSON 的返回不提供实例,有兴趣的可以自行研究如何编写,文档不含任何相关教程。
### CQ::_custom() - 扩展自定义 CQ 码
用于兼容各类含有被支持的扩展 CQ 码,比如 go-cqhttp 的 `[CQ:gift]` 礼物类型。
定义:`CQ::_custom(string $type_name, array $params)`
| 参数名 | 说明 |
| ----------- | --------------------------------------------------- |
| `type_name` | CQ 码类型,如 `music``at` |
| `params` | 发送的 CQ 码中的参数数组,例如 `["qq" => "123456"]` |
下面是一个例子:
```php
CQ::_custom("at",["qq" => "123456","qwe" => "asd"]);
// 返回:[CQ:at,qq=123456,qwe=asd]
```

View File

@@ -163,5 +163,5 @@ public function onThrowing(?Exception $e) {
这里的 `@HandleException` 中的参数为要捕获的类名,注意这里面的类名的命名空间需要写全称,不能上面 use 再使用,否则会无法找到异常类。
`context()` 为获取当前协程空间绑定的 `request``response` 对象。
`ctx()` 为获取当前协程空间绑定的 `request``response` 对象。

View File

@@ -11,10 +11,25 @@ QQ 机器人事件是指 CQHTTP 插件发来的 Event 事件,被框架处理
事件是用户需要从 OneBot 被动接收的数据,有以下几个大类:
- [消息事件](#cqmessage),包括私聊消息、群消息等,被 [`@CQCommand`](#cqcommand)`@CQMessage` 注解处理。
- [通知事件](#cqnotice),包括群成员变动、好友变动等,被 `@CQNotice` 注解事件处理。
- [请求事件](#cqrequest),包括加群请求、加好友请求等,被 `@CQRequest` 注解事件处理。
- [元事件](#cqmetaevent),包括 OneBot 生命周期、心跳等,被 `@CQMetaEvent` 注解事件处理。
## 注解事件参照表
| 注解名称 | 类所在命名全称 | 作用 |
| ------------------------------------------------------- | ---------------------------- | ------------------------------------------------------------ |
| [`@CQBefore`](/event/robot-annotations/#cqbefore) | `\ZM\Annotation\CQBefore` | OneBot 各类事件前触发的,可当作事件过滤器使用 |
| [`@CQAfter`](/event/robot-annotations/#cqafter) | `\ZM\Annotation\CQAfter` | OneBot 各类事件后触发的 |
| [`@CQMessage`](/event/robot-annotations/#cqmessage) | `\ZM\Annotation\CQMessage` | OneBot 中消息类事件的触发(机器人消息)事件 |
| [`@CQCommand`](/event/robot-annotations/#cqcommand) | `\ZM\Annotation\CQCommand` | OneBot 中消息类事件的触发(机器人消息)事件,但是被封装为指令型的,无需自己切割命令式 |
| [`@CQNotice`](/event/robot-annotations/#cqnotice) | `\ZM\Annotation\CQNotice` | OneBot 中通知类事件的触发(机器人消息)事件 |
| [`@CQRequest`](/event/robot-annotations/#cqrequest) | `\ZM\Annotation\CQRequest` | OneBot 中请求类事件的触发(机器人消息)事件,一般带有请求信息,可联动相关响应的 API 完成功能编写 |
| [`@CQMetaEvent`](/event/robot-annotations/#cqmetaevent) | `\ZM\Annotation\CQMetaEvent` | OneBot 中涉及 OneBot 实现本身的一些和机器人事件无关的元事件,比如 WS 连接的心跳包 |
## CQMessage()
QQ 收到消息后触发的事件对应注解。

View File

@@ -224,5 +224,14 @@ public function repeat() {
这样,一个简易的复读机就做好了!回到 QQ 机器人聊天,向机器人发送 `echo 你好啊`,它会回复你 `你好啊`。
<chat-box>
) echo 你好啊
( 你好啊
) echo
( 请输入你要回复的内容
) 哦豁,完蛋
( 哦豁,完蛋
</chat-box>
> 如果你只回复 `echo` 的话,它会先和你进入一个会话状态,并问你 `请输入你要回复的内容`,这时你再次说一些内容例如 `哦豁`,会回复你 `哦豁`。效果和直接输入 `echo 哦豁` 是一致的,这是炸毛框架内的一个封装好的命令参数对话询问功能。有关参数询问功能,请看后面的进阶模块。

View File

@@ -4,6 +4,11 @@
> 如果是从 v1.x 版本升级到 v2.x[点我看升级指南](/advanced/to-v2/)。
!!! tip "提示"
编写文档需要较大精力,你也可以参与到本文档的建设中来,比如找错字,增加或更正内容,每页文档可直接点击右上方铅笔图标直接跳转至 GitHub 进行编辑,编辑后自动 Fork 并生成 Pull Request以此来贡献此文档
炸毛框架使用 PHP 编写,采用 Swoole 扩展为基础,主要面向 API 服务聊天机器人CQHTTP 对接),包含 websocket、http 等监听和请求库,用户代码采用模块化处理,使用注解可以方便地编写各类功能。
框架主要用途为 HTTP 服务器,机器人搭建框架。尤其对于 QQ 机器人消息处理较为方便和全面,提供了众多会话机制和内部调用机制,可以以各种方式设计你自己的模块。

View File

@@ -10,8 +10,8 @@ theme:
favicon: assets/favicon.png
language: zh
palette:
primary: blue
accent: blue
primary: red
accent: red
features:
- navigation.tabs
extra_javascript:
@@ -95,6 +95,8 @@ nav:
- 内部类文件手册: advanced/inside-class.md
- 接入 WebSocket 客户端: advanced/connect-ws-client.md
- 框架多进程: advanced/multi-process.md
- 开发实战教程:
- 编写管理员才能触发的功能: advanced/example/admin.md
- FAQ: FAQ.md
- 更新日志:
- 更新日志v2: update/v2.md

View File

@@ -45,11 +45,11 @@ class CQ
*/
public static function image($file, $cache = true, $flash = false, $proxy = true, $timeout = -1) {
return
"[CQ:image,file=" . $file .
"[CQ:image,file=" . self::encode($file, true) .
(!$cache ? ",cache=0" : "") .
($flash ? ",type=flash" : "") .
(!$proxy ? ",proxy=false" : "") .
($timeout != -1 ? (",timeout=" . $timeout) : "") .
($timeout != -1 ? (",timeout=" . intval($timeout)) : "") .
"]";
}
@@ -64,11 +64,11 @@ class CQ
*/
public static function record($file, $magic = false, $cache = true, $proxy = true, $timeout = -1) {
return
"[CQ:record,file=" . $file .
"[CQ:record,file=" . self::encode($file, true) .
(!$cache ? ",cache=0" : "") .
($magic ? ",magic=1" : "") .
(!$proxy ? ",proxy=false" : "") .
($timeout != -1 ? (",timeout=" . $timeout) : "") .
($timeout != -1 ? (",timeout=" . intval($timeout)) : "") .
"]";
}
@@ -82,10 +82,10 @@ class CQ
*/
public static function video($file, $cache = true, $proxy = true, $timeout = -1) {
return
"[CQ:video,file=" . $file .
"[CQ:video,file=" . self::encode($file, true) .
(!$cache ? ",cache=0" : "") .
(!$proxy ? ",proxy=false" : "") .
($timeout != -1 ? (",timeout=" . $timeout) : "") .
($timeout != -1 ? (",timeout=" . intval($timeout)) : "") .
"]";
}
@@ -121,7 +121,7 @@ class CQ
* @return string
*/
public static function poke($type, $id, $name = "") {
return "[CQ:poke,type=$type,id=$id" . ($name != "" ? ",name=$name" : "") . "]";
return "[CQ:poke,type=$type,id=$id" . ($name != "" ? (",name=".self::encode($name, true)) : "") . "]";
}
/**
@@ -130,7 +130,7 @@ class CQ
* @return string
*/
public static function anonymous($ignore = 1) {
return "[CQ:anonymous".($ignore != 1 ? ",ignore=0" : "")."]";
return "[CQ:anonymous" . ($ignore != 1 ? ",ignore=0" : "") . "]";
}
/**
@@ -143,10 +143,10 @@ class CQ
*/
public static function share($url, $title, $content = null, $image = null) {
if ($content === null) $c = "";
else $c = ",content=" . $content;
else $c = ",content=" . self::encode($content, true);
if ($image === null) $i = "";
else $i = ",image=" . $image;
return "[CQ:share,url=" . $url . ",title=" . $title . $c . $i . "]";
else $i = ",image=" . self::encode($image, true);
return "[CQ:share,url=" . self::encode($url, true) . ",title=" . self::encode($title, true) . $c . $i . "]";
}
/**
@@ -159,8 +159,21 @@ class CQ
return "[CQ:contact,type=$type,id=$id]";
}
/**
* 发送位置
* @param $lat
* @param $lon
* @param string $title
* @param string $content
* @return string
*/
public static function location($lat, $lon, $title = "", $content = "") {
return "[CQ:location" .
",lat=".self::encode($lat, true) .
",lon=".self::encode($lon, true).
($title != "" ? (",title=".self::encode($title, true)) : "") .
($content != "" ? (",content=".self::encode($content, true)) : "") .
"]";
}
/**
@@ -193,10 +206,13 @@ class CQ
return " ";
}
if ($content === null) $c = "";
else $c = ",content=" . $content;
else $c = ",content=" . self::encode($content, true);
if ($image === null) $i = "";
else $i = ",image=" . $image;
return "[CQ:music,type=custom,url=" . $id_or_url . ",audio=" . $audio . ",title=" . $title . $c . $i . "]";
else $i = ",image=" . self::encode($image, true);
return "[CQ:music,type=custom,url=" .
self::encode($id_or_url, true) .
",audio=" . self::encode($audio, true) . ",title=" . self::encode($title, true) . $c . $i .
"]";
default:
Console::warning("传入的music type($type)错误!");
return " ";
@@ -208,19 +224,36 @@ class CQ
}
public static function node($user_id, $nickname, $content) {
return "[CQ:node,user_id=$user_id,nickname=$nickname,content=".self::escape($content)."]";
return "[CQ:node,user_id=$user_id,nickname=".self::encode($nickname, true).",content=" . self::encode($content, true) . "]";
}
public static function xml($data) {
return "[CQ:xml,data=" . self::encode($data, true) . "]";
}
public static function json($data, $resid = 0) {
return "[CQ:json,data=" . self::encode($data, true) . ",resid=" . intval($resid) . "]";
}
public static function _custom(string $type_name, $params) {
$code = "[CQ:" . $type_name;
foreach ($params as $k => $v) {
$code .= "," . $k . "=" . self::escape($v, true);
}
$code .= "]";
return $code;
}
/**
* 反转义字符串中的CQ码敏感符号
* @param $str
* @param $msg
* @param bool $is_content
* @return mixed
*/
public static function decode($str) {
$str = str_replace("&amp;", "&", $str);
$str = str_replace("&#91;", "[", $str);
$str = str_replace("&#93;", "]", $str);
return $str;
public static function decode($msg, $is_content = false) {
$msg = str_replace(["&amp;", "&#91;", "&#93;"], ["&", "[", "]"], $msg);
if ($is_content) $msg = str_replace("&#44;", ",", $msg);
return $msg;
}
public static function replace($str) {
@@ -230,42 +263,97 @@ class CQ
}
/**
* 转义CQ码
* 转义CQ码的特殊字符同encode
* @param $msg
* @param bool $is_content
* @return mixed
*/
public static function escape($msg) {
$msg = str_replace("&", "&amp;", $msg);
$msg = str_replace("[", "&#91;", $msg);
$msg = str_replace("]", "&#93;", $msg);
public static function escape($msg, $is_content = false) {
$msg = str_replace(["&", "[", "]"], ["&amp;", "&#91;", "&#93;"], $msg);
if ($is_content) $msg = str_replace(",", "&#44;", $msg);
return $msg;
}
public static function encode($str) {
return self::escape($str);
/**
* 转义CQ码的特殊字符
* @param $msg
* @param false $is_content
* @return mixed
*/
public static function encode($msg, $is_content = false) {
$msg = str_replace(["&", "[", "]"], ["&amp;", "&#91;", "&#93;"], $msg);
if ($is_content) $msg = str_replace(",", "&#44;", $msg);
return $msg;
}
/**
* 移除消息中所有的CQ码并返回移除CQ码后的消息
* @param $msg
* @return string
*/
public static function removeCQ($msg) {
while (($cq = self::getCQ($msg)) !== null) {
$msg = str_replace(mb_substr($msg, $cq["start"], $cq["end"] - $cq["start"] + 1), "", $msg);
$final = "";
$last_end = 0;
foreach(self::getAllCQ($msg) as $k => $v) {
$final .= mb_substr($msg, $last_end, $v["start"] - $last_end);
$last_end = $v["end"] + 1;
}
return $msg;
$final .= mb_substr($msg, $last_end);
return $final;
}
/**
* 获取消息中第一个CQ码
* @param $msg
* @return array|null
*/
public 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);
if (($head = mb_strpos($msg, "[CQ:")) !== false) {
$key_offset = mb_substr($msg, $head);
$close = mb_strpos($key_offset, "]");
if ($close === false) return null;
$content = mb_substr($msg, $head + 4, $close + $head - mb_strlen($msg));
$exp = explode(",", $content);
$cq["type"] = array_shift($exp);
foreach ($exp as $k => $v) {
$ss = explode("=", $v);
$sk = array_shift($ss);
$cq["params"][$sk] = self::decode(implode("=", $ss), true);
}
$cq["start"] = $head;
$cq["end"] = $close + $head;
return $cq;
} else {
return null;
}
return ["type" => $type, "params" => $array, "start" => $start, "end" => $end];
}
/**
* 获取消息中所有的CQ码
* @param $msg
* @return array
*/
public static function getAllCQ($msg) {
$cqs = [];
$offset = 0;
while (($head = mb_strpos(($submsg = mb_substr($msg, $offset)), "[CQ:")) !== false) {
$key_offset = mb_substr($submsg, $head);
$tmpmsg = mb_strpos($key_offset, "]");
if ($tmpmsg === false) break; // 没闭合不算CQ码
$content = mb_substr($submsg, $head + 4, $tmpmsg + $head - mb_strlen($submsg));
$exp = explode(",", $content);
$cq = [];
$cq["type"] = array_shift($exp);
foreach ($exp as $k => $v) {
$ss = explode("=", $v);
$sk = array_shift($ss);
$cq["params"][$sk] = self::decode(implode("=", $ss), true);
}
$cq["start"] = $offset + $head;
$cq["end"] = $offset + $tmpmsg + $head;
$offset += $tmpmsg + 1;
$cqs[] = $cq;
}
return $cqs;
}
}

View File

@@ -59,7 +59,7 @@ class ZMRobot
public static function getAllRobot() {
$r = ManagerGM::getAllByName('qq');
$obj = [];
foreach($r as $v) {
foreach ($r as $v) {
$obj[] = new ZMRobot($v);
}
return $obj;

View File

@@ -47,7 +47,7 @@ class AnnotationParser
*/
public function registerMods() {
foreach ($this->path_list as $path) {
Console::debug("parsing annotation in ".$path[0]);
Console::debug("parsing annotation in " . $path[0]);
$all_class = getAllClasses($path[0], $path[1]);
$this->reader = new AnnotationReader();
foreach ($all_class as $v) {

View File

@@ -31,7 +31,7 @@ class BuildCommand extends Command
$target_dir = $input->getOption("target") ?? (__DIR__ . '/../../../resources/');
if (mb_strpos($target_dir, "../")) $target_dir = realpath($target_dir);
if ($target_dir === false) {
$output->writeln(TermColor::color8(31) . "Error: No such file or directory (".__DIR__ . '/../../../resources/'.")" . TermColor::RESET);
$output->writeln(TermColor::color8(31) . "Error: No such file or directory (" . __DIR__ . '/../../../resources/' . ")" . TermColor::RESET);
return Command::FAILURE;
}
$output->writeln("Target: " . $target_dir . " , Version: " . ($version = json_decode(file_get_contents(__DIR__ . "/../../../composer.json"), true)["version"]));
@@ -51,7 +51,7 @@ class BuildCommand extends Command
return Command::SUCCESS;
}
private function build ($target_dir, $filename) {
private function build($target_dir, $filename) {
@unlink($target_dir . $filename);
$phar = new Phar($target_dir . $filename);
$phar->startBuffering();

View File

@@ -18,8 +18,8 @@ class DaemonStopCommand extends DaemonCommand
protected function execute(InputInterface $input, OutputInterface $output) {
parent::execute($input, $output);
system("kill -TERM ".intval($this->daemon_file["pid"]));
unlink(DataProvider::getWorkingDir()."/.daemon_pid");
system("kill -TERM " . intval($this->daemon_file["pid"]));
unlink(DataProvider::getWorkingDir() . "/.daemon_pid");
$output->writeln("<info>成功停止!</info>");
return Command::SUCCESS;
}

View File

@@ -36,8 +36,8 @@ class PureHttpCommand extends Command
protected function execute(InputInterface $input, OutputInterface $output) {
$tty_width = explode(" ", trim(exec("stty size")))[1];
if(realpath($input->getArgument('dir') ?? '.') === false) {
$output->writeln("<error>Directory error(".($input->getArgument('dir') ?? '.')."): no such file or directory.</error>");
if (realpath($input->getArgument('dir') ?? '.') === false) {
$output->writeln("<error>Directory error(" . ($input->getArgument('dir') ?? '.') . "): no such file or directory.</error>");
return self::FAILURE;
}
$global = ZMConfig::get("global");
@@ -60,15 +60,15 @@ class PureHttpCommand extends Command
"document_root" => realpath($input->getArgument('dir') ?? '.'),
"document_index" => $index
]);
echo "\r".Coroutine::stats()["coroutine_peak_num"];
echo "\r" . Coroutine::stats()["coroutine_peak_num"];
});
$server->on("start", function ($server) {
Process::signal(SIGINT, function () use ($server) {
Console::warning("Server interrupted by keyboard.");
for ($i = 0; $i < 32; ++$i) {
$num = ZMAtomic::$atomics["request"][$i]->get();
if($num != 0)
echo "[$i]: ".$num."\n";
if ($num != 0)
echo "[$i]: " . $num . "\n";
}
$server->shutdown();
$server->stop();

View File

@@ -38,7 +38,7 @@ class DB
if (Table::getTableInstance($table_name) === null) {
if (in_array($table_name, self::$table_list))
return new Table($table_name);
elseif(SqlPoolStorage::$sql_pool !== null){
elseif (SqlPoolStorage::$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");
@@ -84,7 +84,7 @@ class DB
* @throws DbException
*/
public static function rawQuery(string $line, $params = [], $fetch_mode = ZM_DEFAULT_FETCH_MODE) {
Console::debug("MySQL: ".$line." | ". implode(", ", $params));
Console::debug("MySQL: " . $line . " | " . implode(", ", $params));
try {
$conn = SqlPoolStorage::$sql_pool->get();
if ($conn === false) {
@@ -115,7 +115,7 @@ class DB
return $ps->fetchAll($fetch_mode);
}
} catch (DbException $e) {
if(mb_strpos($e->getMessage(), "has gone away") !== false) {
if (mb_strpos($e->getMessage(), "has gone away") !== false) {
zm_sleep(0.2);
Console::warning("Gone away of MySQL! retrying!");
return self::rawQuery($line, $params);
@@ -123,7 +123,7 @@ class DB
Console::warning($e->getMessage());
throw $e;
} catch (PDOException $e) {
if(mb_strpos($e->getMessage(), "has gone away") !== false) {
if (mb_strpos($e->getMessage(), "has gone away") !== false) {
zm_sleep(0.2);
Console::warning("Gone away of MySQL! retrying!");
return self::rawQuery($line, $params);

View File

@@ -28,6 +28,6 @@ class InsertBody
* @throws DbException
*/
public function save() {
DB::rawQuery('INSERT INTO ' . $this->table->getTableName() . ' VALUES ('.implode(',', array_fill(0, count($this->row), '?')).')', $this->row);
DB::rawQuery('INSERT INTO ' . $this->table->getTableName() . ' VALUES (' . implode(',', array_fill(0, count($this->row), '?')) . ')', $this->row);
}
}

View File

@@ -4,7 +4,6 @@
namespace ZM\DB;
class Table
{
private $table_name;
@@ -28,7 +27,7 @@ class Table
return new SelectBody($this, $what == [] ? ["*"] : $what);
}
public function where($column, $operation_or_value, $value = null){
public function where($column, $operation_or_value, $value = null) {
return (new SelectBody($this, ["*"]))->where($column, $operation_or_value, $value);
}
@@ -47,7 +46,7 @@ class Table
return new DeleteBody($this);
}
public function statement(){
public function statement() {
$this->cache = [];
//TODO: 无返回的statement语句
}

View File

@@ -18,6 +18,7 @@ class UpdateBody
* @var array
*/
private $set_value;
/**
* UpdateBody constructor.
* @param Table $table
@@ -31,19 +32,19 @@ class UpdateBody
/**
* @throws DbException
*/
public function save(){
public function save() {
$arr = [];
$msg = [];
foreach($this->set_value as $k => $v) {
$msg []= $k .' = ?';
$arr[]=$v;
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 != []) {
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;
$line .= ' WHERE ' . $sql;
}
return DB::rawQuery($line, $arr);
}

View File

@@ -15,17 +15,17 @@ trait WhereBody
return $this;
}
protected function getWhereSQL(){
protected function getWhereSQL() {
$param = [];
$msg = '';
foreach($this->where_thing as $k => $v) {
foreach($v as $ks => $vs) {
if($param != []) {
$msg .= ' AND '.$ks ." $k ?";
foreach ($this->where_thing as $k => $v) {
foreach ($v as $ks => $vs) {
if ($param != []) {
$msg .= ' AND ' . $ks . " $k ?";
} else {
$msg .= "$ks $k ?";
}
$param []=$vs;
$param [] = $vs;
}
}
if ($msg == '') $msg = 1;

View File

@@ -93,13 +93,13 @@ class EventDispatcher
foreach ((EventManager::$events[$this->class] ?? []) as $v) {
$this->dispatchEvent($v, $this->rule, ...$params);
if ($this->log) Console::verbose("[事件分发{$this->eid}] 单一对象 " . $v->class . "::" . $v->method . " 分发结束。");
if($this->status == self::STATUS_BEFORE_FAILED || $this->status == self::STATUS_RULE_FAILED) continue;
if ($this->status == self::STATUS_BEFORE_FAILED || $this->status == self::STATUS_RULE_FAILED) continue;
if (is_callable($this->return_func) && $this->status === self::STATUS_NORMAL) {
if ($this->log) Console::verbose("[事件分发{$this->eid}] 单一对象 " . $v->class . "::" . $v->method . " 正在执行返回值处理函数 ...");
($this->return_func)($this->store);
}
}
if($this->status === self::STATUS_RULE_FAILED) $this->status = self::STATUS_NORMAL;
if ($this->status === self::STATUS_RULE_FAILED) $this->status = self::STATUS_NORMAL;
} catch (InterruptException $e) {
$this->store = $e->return_var;
$this->status = self::STATUS_INTERRUPTED;
@@ -113,9 +113,9 @@ class EventDispatcher
* @param mixed $v
* @param null $rule_func
* @param mixed ...$params
* @throws AnnotationException
* @throws InterruptException
* @return bool
* @throws InterruptException
* @throws AnnotationException
*/
public function dispatchEvent($v, $rule_func = null, ...$params) {
$q_c = $v->class;

View File

@@ -9,8 +9,11 @@ use Exception;
use Swoole\Timer;
use ZM\Annotation\AnnotationBase;
use ZM\Annotation\AnnotationParser;
use ZM\Annotation\Swoole\OnSave;
use ZM\Annotation\Swoole\OnTick;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Store\LightCache;
use ZM\Store\ZMAtomic;
class EventManager
@@ -59,5 +62,11 @@ class EventManager
}
});
}
$conf = ZMConfig::get("global", "worker_cache") ?? ["worker" => 0];
if (server()->worker_id == $conf["worker"]) {
zm_timer_tick(ZMConfig::get("global", "light_cache")["auto_save_interval"] * 1000, function () {
LightCache::savePersistence();
});
}
}
}

View File

@@ -41,6 +41,7 @@ use ZM\Exception\InterruptException;
use ZM\Framework;
use ZM\Http\Response;
use ZM\Module\QQBot;
use ZM\Store\LightCache;
use ZM\Store\LightCacheInside;
use ZM\Store\MySQL\SqlPoolStorage;
use ZM\Store\Redis\ZMRedisPool;
@@ -121,6 +122,9 @@ class ServerEventHandler
* @param $worker_id
*/
public function onWorkerStop(Server $server, $worker_id) {
if ($worker_id == (ZMConfig::get("worker_cache")["worker"] ?? 0)) {
LightCache::savePersistence();
}
Console::debug(($server->taskworker ? "Task" : "") . "Worker #$worker_id 已停止");
}
@@ -533,6 +537,11 @@ class ServerEventHandler
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "hasKeyWorkerCache":
$r = WorkerCache::hasKey($data["key"], $data["subkey"]);
$action = ["action" => "returnWorkerCache", "cid" => $data["cid"], "value" => $r];
$server->sendMessage(json_encode($action, 256), $src_worker_id);
break;
case "asyncAddWorkerCache":
WorkerCache::add($data["key"], $data["value"], true);
break;

View File

@@ -94,7 +94,7 @@ class Framework
"version" => ZM_VERSION,
"config" => $args["env"] === null ? 'global.php' : $args["env"]
];
if(APP_VERSION !== "unknown") $out["app_version"] = APP_VERSION;
if (APP_VERSION !== "unknown") $out["app_version"] = APP_VERSION;
if (isset(ZMConfig::get("global", "swoole")["task_worker_num"])) {
$out["task_worker_num"] = ZMConfig::get("global", "swoole")["task_worker_num"];
}

View File

@@ -160,7 +160,7 @@ class Response
* @return mixed
*/
public function end($content = null) {
if(!$this->is_end) {
if (!$this->is_end) {
$this->is_end = true;
return $this->response->end($content);
} else {

View File

@@ -16,7 +16,7 @@ class RouteManager
public static $routes = null;
public static function importRouteByAnnotation(RequestMapping $vss, $method, $class, $methods_annotations) {
if(self::$routes === null) self::$routes = new RouteCollection();
if (self::$routes === null) self::$routes = new RouteCollection();
// 拿到所属方法的类上面有没有控制器的注解
$prefix = '';
@@ -27,8 +27,8 @@ class RouteManager
}
}
$tail = trim($vss->route, "/");
$route_name = $prefix.($tail === "" ? "" : "/").$tail;
Console::debug("添加路由:".$route_name);
$route_name = $prefix . ($tail === "" ? "" : "/") . $tail;
Console::debug("添加路由:" . $route_name);
$route = new Route($route_name, ['_class' => $class, '_method' => $method]);
$route->setMethods($vss->request_method);

View File

@@ -13,14 +13,14 @@ class StaticFileHandler
public function __construct($filename, $path) {
$full_path = realpath($path . "/" . $filename);
$response = ctx()->getResponse();
Console::debug("Full path: ".$full_path);
Console::debug("Full path: " . $full_path);
if ($full_path !== false) {
if (strpos($full_path, $path) !== 0) {
$response->status(403);
$response->end("403 Forbidden");
return true;
} else {
if(is_file($full_path)) {
if (is_file($full_path)) {
$exp = strtolower(pathinfo($full_path)['extension'] ?? "unknown");
$response->setHeader("Content-Type", ZMConfig::get("file_header")[$exp] ?? "application/octet-stream");
$response->end(file_get_contents($full_path));

View File

@@ -6,7 +6,10 @@ namespace ZM\Store;
use Exception;
use Swoole\Table;
use ZM\Annotation\Swoole\OnSave;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
use ZM\Event\EventDispatcher;
use ZM\Exception\ZMException;
class LightCache
@@ -175,7 +178,16 @@ class LightCache
return $r;
}
public static function savePersistence() {
public static function savePersistence($only_worker = false) {
// 下面将OnSave激活一下
if (server()->worker_id == (ZMConfig::get("global", "worker_cache")["worker"] ?? 0)) {
$dispatcher = new EventDispatcher(OnSave::class);
$dispatcher->dispatchEvents();
}
if($only_worker) return;
if (self::$kv_table === null) return;
$r = [];
foreach (self::$kv_table as $k => $v) {
@@ -184,11 +196,13 @@ class LightCache
$r[$k] = self::parseGet($v);
}
}
if(self::$config["persistence_path"] == "") return;
if (self::$config["persistence_path"] == "") return;
if (file_exists(self::$config["persistence_path"])) {
$r = file_put_contents(self::$config["persistence_path"], json_encode($r, 128 | 256));
if ($r === false) Console::error("Not saved, please check your \"persistence_path\"!");
}
}
private static function checkExpire($key) {

View File

@@ -15,18 +15,16 @@ class SpinLock
private static $delay = 1;
public static function init($key_cnt, $delay = 1)
{
public static function init($key_cnt, $delay = 1) {
self::$kv_lock = new Table($key_cnt, 0.7);
self::$delay = $delay;
self::$kv_lock->column('lock_num', Table::TYPE_INT, 8);
return self::$kv_lock->create();
}
public static function lock(string $key)
{
public static function lock(string $key) {
while (($r = self::$kv_lock->incr($key, 'lock_num')) > 1) { //此资源已经被锁上了
if(Coroutine::getCid() != -1) System::sleep(self::$delay / 1000);
if (Coroutine::getCid() != -1) System::sleep(self::$delay / 1000);
else usleep(self::$delay * 1000);
}
}
@@ -41,4 +39,10 @@ class SpinLock
public static function unlock(string $key) {
return self::$kv_lock->set($key, ['lock_num' => 0]);
}
public static function transaction(string $key, callable $function) {
SpinLock::lock($key);
$function();
SpinLock::unlock($key);
}
}

View File

@@ -16,7 +16,7 @@ class ZMRedis
* @throws NotInitializedException
*/
public static function call(callable $callable) {
if(ZMRedisPool::$pool === null) throw new NotInitializedException("Redis pool is not initialized.");
if (ZMRedisPool::$pool === null) throw new NotInitializedException("Redis pool is not initialized.");
$r = ZMRedisPool::$pool->get();
$result = $callable($r);
if (isset($r->wasted)) ZMRedisPool::$pool->put(null);
@@ -29,7 +29,7 @@ class ZMRedis
* @throws NotInitializedException
*/
public function __construct() {
if(ZMRedisPool::$pool === null) throw new NotInitializedException("Redis pool is not initialized.");
if (ZMRedisPool::$pool === null) throw new NotInitializedException("Redis pool is not initialized.");
$this->conn = ZMRedisPool::$pool->get();
}

View File

@@ -24,13 +24,13 @@ class ZMRedisPool
);
try {
$r = self::$pool->get()->ping('123');
if(strpos(strtolower($r), "123") !== false) {
if (strpos(strtolower($r), "123") !== false) {
Console::debug("成功连接redis连接池");
} else {
var_dump($r);
}
} catch (RedisException $e) {
Console::error("Redis init failed! ".$e->getMessage());
Console::error("Redis init failed! " . $e->getMessage());
self::$pool = null;
}
}

View File

@@ -38,10 +38,20 @@ class WorkerCache
return self::processRemote($action, $async, $config);
}
}
public static function hasKey($key, $subkey) {
$config = self::$config ?? ZMConfig::get("global", "worker_cache") ?? ["worker" => 0];
if ($config["worker"] === server()->worker_id) {
return isset(self::$store[$key][$subkey]);
} else {
$action = ["hasKeyWorkerCache", "key" => $key, "subkey" => $subkey, "cid" => zm_cid()];
return self::processRemote($action, false, $config);
}
}
private static function processRemote($action, $async, $config) {
$ss = server()->sendMessage(json_encode($action, JSON_UNESCAPED_UNICODE), $config["worker"]);
if(!$ss) return false;
if (!$ss) return false;
if ($async) return true;
zm_yield();
$p = self::$transfer[zm_cid()] ?? null;
@@ -63,7 +73,7 @@ class WorkerCache
public static function add($key, int $value, $async = false) {
$config = self::$config ?? ZMConfig::get("global", "worker_cache") ?? ["worker" => 0];
if ($config["worker"] === server()->worker_id) {
if(!isset(self::$store[$key])) self::$store[$key] = 0;
if (!isset(self::$store[$key])) self::$store[$key] = 0;
self::$store[$key] += $value;
return true;
} else {
@@ -75,7 +85,7 @@ class WorkerCache
public static function sub($key, int $value, $async = false) {
$config = self::$config ?? ZMConfig::get("global", "worker_cache") ?? ["worker" => 0];
if ($config["worker"] === server()->worker_id) {
if(!isset(self::$store[$key])) self::$store[$key] = 0;
if (!isset(self::$store[$key])) self::$store[$key] = 0;
self::$store[$key] -= $value;
return true;
} else {

View File

@@ -55,7 +55,7 @@ class CoMessage
SpinLock::lock("wait_api");
$all = LightCacheInside::get("wait_api", "wait_api") ?? [];
foreach ($all as $k => $v) {
if(!isset($v["compare"])) continue;
if (!isset($v["compare"])) continue;
foreach ($v["compare"] as $vs) {
if (!isset($v[$vs], $dat[$vs])) continue 2;
if ($v[$vs] != $dat[$vs]) {
@@ -64,7 +64,7 @@ class CoMessage
}
$last = $k;
}
if($last !== null) {
if ($last !== null) {
$all[$last]["result"] = $dat;
LightCacheInside::set("wait_api", "wait_api", $all);
SpinLock::unlock("wait_api");

View File

@@ -5,6 +5,7 @@ namespace ZM\Utils;
use ZM\Config\ZMConfig;
use ZM\Console\Console;
class DataProvider
{
@@ -15,7 +16,7 @@ class DataProvider
}
public static function getWorkingDir() {
if(LOAD_MODE == 0) return WORKING_DIR;
if (LOAD_MODE == 0) return WORKING_DIR;
elseif (LOAD_MODE == 1) return LOAD_MODE_COMPOSER_PATH;
elseif (LOAD_MODE == 2) return realpath('.');
return null;
@@ -28,4 +29,29 @@ class DataProvider
public static function getDataFolder() {
return ZM_DATA;
}
public static function saveToJson($filename, $file_array) {
$path = ZMConfig::get("global", "config_dir");
$r = explode("/", $filename);
if(count($r) == 2) {
$path = $path . $r[0]."/";
if(!is_dir($path)) mkdir($path);
$name = $r[1];
} elseif (count($r) != 1) {
Console::warning("存储失败,文件名只能有一级目录");
return false;
} else {
$name = $r[0];
}
return file_put_contents($path.$name.".json", json_encode($file_array, JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE));
}
public static function loadFromJson($filename) {
$path = ZMConfig::get("global", "config_dir");
if(file_exists($path.$filename.".json")) {
return json_decode(file_get_contents($path.$filename.".json"), true);
} else {
return null;
}
}
}

View File

@@ -89,10 +89,10 @@ function getAllClasses($dir, $indoor_name) {
//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") {
if(substr(file_get_contents($dir.$v), 6, 6) == "#plain") continue;
$composer = json_decode(file_get_contents(DataProvider::getWorkingDir()."/composer.json"), true);
foreach($composer["autoload"]["files"] as $fi) {
if(realpath(DataProvider::getWorkingDir()."/".$fi) == realpath($dir.$v)) {
if (substr(file_get_contents($dir . $v), 6, 6) == "#plain") continue;
$composer = json_decode(file_get_contents(DataProvider::getWorkingDir() . "/composer.json"), true);
foreach ($composer["autoload"]["files"] as $fi) {
if (realpath(DataProvider::getWorkingDir() . "/" . $fi) == realpath($dir . $v)) {
continue 2;
}
}
@@ -267,10 +267,14 @@ function zm_timer_after($ms, callable $callable) {
}
function zm_timer_tick($ms, callable $callable) {
go(function () use ($ms, $callable) {
Console::debug("Adding extra timer tick of " . $ms . " ms");
Swoole\Timer::tick($ms, $callable);
});
if (zm_cid() === -1) {
return go(function () use ($ms, $callable) {
Console::debug("Adding extra timer tick of " . $ms . " ms");
Swoole\Timer::tick($ms, $callable);
});
} else {
return Swoole\Timer::tick($ms, $callable);
}
}
function zm_data_hash($v) {