├── .phpstorm.meta.php
├── CREDITS
├── LICENSE
├── README.md
├── ascii.ui
├── changes.txt
├── composer.json
├── php-msf.png
└── src
├── Base
├── AOPFactory.php
├── Child.php
├── Core.php
├── Exception.php
├── Input.php
├── Output.php
└── Pool.php
├── Client
├── Exception.php
├── Http
│ └── Client.php
└── RpcClient.php
├── Console
├── Controller.php
└── Request.php
├── Controllers
├── Bench.php
├── Controller.php
├── Monitor.php
├── Rest.php
└── Rpc.php
├── Coroutine
├── Base.php
├── CNull.php
├── CTask.php
├── Dns.php
├── Exception.php
├── File.php
├── Http.php
├── IBase.php
├── MySql.php
├── Redis.php
├── Scheduler.php
├── Shell.php
├── Sleep.php
└── Task.php
├── Helpers
├── Common.php
└── Context.php
├── HttpServer.php
├── MSFCli.php
├── MSFServer.php
├── Macro.php
├── Models
└── Model.php
├── Pack
├── Exception.php
├── IPack.php
├── JsonPack.php
├── MsgPack.php
└── SerializePack.php
├── Pools
├── AsynPool.php
├── AsynPoolManager.php
├── CoroutineRedisProxy.php
├── Exception.php
├── IAsynPool.php
├── Miner.php
├── MysqlAsynPool.php
└── RedisAsynPool.php
├── Process
├── Config.php
├── Exception.php
├── Inotify.php
├── ProcessBase.php
└── Timer.php
├── Proxy
├── Exception.php
├── IProxy.php
├── MysqlProxyFactory.php
├── MysqlProxyMasterSlave.php
├── RedisProxyCluster.php
├── RedisProxyFactory.php
└── RedisProxyMasterSlave.php
├── Queue
├── Beanstalk.php
├── Exception.php
├── IQueue.php
├── Kafka.php
├── RabbitMQ.php
└── Redis.php
├── Rest
└── Controller.php
├── Route
├── Exception.php
├── IRoute.php
├── NormalRoute.php
└── RestRoute.php
├── Server.php
├── Session
├── Adapters
│ ├── File.php
│ └── Redis.php
├── Exception.php
├── ISession.php
└── Session.php
├── Tasks
├── AMQPTask.php
├── BeanstalkTask.php
├── Exception.php
├── KafkaTask.php
├── MongoDbTask.php
├── Task.php
└── TaskProxy.php
└── Views
└── index.html
/.phpstorm.meta.php:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 | # Micro Service Framework For PHP
14 |
15 | PHP微服务框架即“Micro Service Framework For PHP”,是[Camera360](https://www.camera360.com)社区服务器端团队基于[Swoole](http://www.swoole.com)自主研发现代化的PHP协程服务框架,简称msf或者php-msf,是[Swoole](http://www.swoole.com)的工程级企业应用框架,经受了Camera360亿级用户高并发大流量的考验。php-msf由[Camera360](https://www.camera360.com)服务器团队主导研发,会持续更新与维护,也希望有更多优秀的[Swoole](http://www.swoole.com)应用实践开发者加入。php-msf核心设计思想是采用协程、异步、并行的创新技术手段提高系统的单机吞吐能力,降低整体服务器成本。
16 |
17 | ## 主要特性
18 |
19 | * 精简版的MVC框架
20 | * IO密集性业务的单机处理能力提升5-10倍
21 | * 代码常驻内存
22 | * 支持对象池
23 | * 支持Redis连接池、MySQL连接池(异步与同步)
24 | * 内置Redis Proxy,支持分布式、master-slave集群(故障自动failover与recovery)
25 | * 内置MySQL Proxy,master-slave集群(读写分离、事务)
26 | * 支持异步、并行
27 | * 基于PHP Yield实现协程
28 | * 内建http/redis/mysql/mongodb/task等协程客户端
29 | * 纯异步的Http Server
30 | * RPC Server/Client
31 | * 支持命令行模式
32 | * 支持独立进程的定时器
33 | * 支持独立配置进程
34 | * 支持sendfile静态文件(需配置root目录)
35 |
36 | ## 环境要求
37 |
38 | - Linux,FreeBSD,MacOS(有兼容问题)
39 | - Linux内核版本2.3.32以上(支持epoll)
40 | - PHP-7.0及以上版本(生产环境建议使用PHP-7.1)
41 | - gcc-4.4以上版本
42 | - [swoole-1.9.15](https://github.com/swoole/swoole-src/archive/v1.9.15.tar.gz)及以上版本(暂不支持Swoole-2.0)
43 | - [hiredis-0.13.3](https://github.com/redis/hiredis/archive/v0.13.3.tar.gz)
44 | - [yac](https://github.com/laruence/yac/archive/yac-2.0.2.tar.gz)
45 | - [phpredis](http://pecl.php.net/get/redis-3.1.2.tgz)
46 | - composer
47 |
48 | ## 文档
49 |
50 | 框架手册(Gitbook): [PHP-MSF开发手册](https://pinguo.gitbooks.io/php-msf-docs/)
51 |
52 | API Document(Rawgit): [类文档](https://cdn.rawgit.com/pinguo/php-msf-docs/4e0ed09d/api-document/index.html)
53 |
54 | 示例DEMO项目: [PHP-MSF DEMO](https://github.com/pinguo/php-msf-demo)
55 |
56 | 帮助完善文档: [https://github.com/pinguo/php-msf-docs](https://github.com/pinguo/php-msf-docs),请提交PR。
57 |
58 | ## 交流与反馈
59 |
60 | PHP-MSF#1群(QQ): 614054288
61 |
62 | ## 快速起步
63 |
64 | ```bash
65 | $>php -r "copy('https://raw.githubusercontent.com/pinguo/php-msf-docker/master/installer.php', 'installer.php');include('installer.php');" && source ~/.bashrc
66 | ```
67 |
68 | `installer.php`会检查运行环境,根据你的自定义配置,自动创建项目模板,composer安装依赖,启动服务。如果`cdn.rawgit.com`无法访问,可以直接克隆或者下载[php-msf-docker](https://github.com/pinguo/php-msf-docker),提取`installer.php`,然后直接运行`php installer.php`
69 |
70 | 如果一切顺利,运行到最后你将看到如下的输出:
71 |
72 | ```bash
73 | [2017-09-06 16:08:34] Run composer install success
74 | [2017-09-06 16:08:34] Congratulations, all are installed successfully!
75 | [2017-09-06 16:08:34] You can, visit http://127.0.0.1:8990/Welcome for test
76 | _______ ____
77 | ________ / /_ ____ ____ ___ _____/ __/
78 | ___/ __ \/ __ \/ __ \______/ __ `__ \/ ___/ /_
79 | __/ /_/ / / / / /_/ /_____/ / / / / (__ ) __/
80 | _/ .___/_/ /_/ .___/ /_/ /_/ /_/____/_/
81 | /_/ /_/ Camera360 Open Source TM
82 | [2017-09-06 16:08:34] Swoole Version: 1.9.18
83 | [2017-09-06 16:08:34] PHP Version: 7.1.8
84 | [2017-09-06 16:08:34] Application ENV: docker
85 | [2017-09-06 16:08:34] Listen Addr: 0.0.0.0
86 | [2017-09-06 16:08:34] Listen Port: 8990
87 | ```
88 |
89 | 访问测试:
90 |
91 | ```bash
92 | $>curl http://127.0.0.1:8990/Welcome
93 | hello world!
94 | ```
95 |
96 | 注意端口,如果你不是8990,你需要修改,然后访问测试。
97 |
98 | ## 标准应用结构
99 |
100 | ```
101 | ├── app // PHP业务代码
102 | │ ├── AppServer.php // 应用server类,可根据需求自定义
103 | │ ├── Controllers // 控制器类目录
104 | │ ├── Lib // 特殊逻辑处理类目录
105 | │ ├── Models // Model类目录
106 | │ ├── Route // 特殊路由规则类目录
107 | │ ├── Tasks // Task类目录
108 | │ └── Views // 视图文件目录
109 | ├── build.sh // 构建脚本(拉取docker镜像,启动容器)
110 | ├── checkstyle.sh // 代码检查脚本
111 | ├── composer.json // composer包依赖配置文件
112 | ├── config // 配置目录
113 | ├── server.php // server启动脚本
114 | ├── console.php // 命令行脚本
115 | ├── test // 单元测试目录
116 | ```
117 |
118 | 上述为基于php-msf的标准应用结构,一键安装程序installer.php会自动生成目录,用户可以根据需求创建一些自定义目录,只要符合psr4标准即可自动加载。
119 |
120 | ## 服务启动
121 |
122 | 调试模式
123 |
124 | ```bash
125 | $>./server.php start
126 | ```
127 |
128 | Daemon模式
129 |
130 | ```bash
131 | $>./server.php start -d
132 | ```
133 |
134 |
135 | 停止服务
136 |
137 | ```bash
138 | $>./server.php stop
139 | ```
140 |
141 | 重启服务
142 |
143 | ```bash
144 | $>./server.php restart
145 | ```
146 |
147 | ## Docker
148 |
149 | 我们制作了Docker镜像,方便Docker用户快速的安装环境,运行[PHP-MSF DEMO](https://github.com/pinguo/php-msf-demo)工程。另外期望在开发环境修改代码实时预览效果,建议使用Docker for [Mac](https://download.docker.com/mac/stable/Docker.dmg)/[Windows](https://download.docker.com/win/stable/InstallDocker.msi)桌面版。
150 |
151 | 如果是升级Docker,它会自动迁移原有的镜像和容器,请耐心等待,千万不能中途kill掉Docker进程,否则再想迁移就难了。
152 |
153 | Docker Registry(阿里云):
154 |
155 | - 公网地址: `docker pull registry.cn-hangzhou.aliyuncs.com/pinguo-ops/php-msf-docker:latest`
156 | - 经典内网: `docker pull registry-internal.cn-hangzhou.aliyuncs.com/pinguo-ops/php-msf-docker:latest`
157 | - VPC网络: `docker pull registry-vpc.cn-hangzhou.aliyuncs.com/pinguo-ops/php-msf-docker:latest`
158 | - DockerHub(国外): `docker pull pinguoops/php-msf-docker`
159 |
160 |
161 |
162 | ## 框架定位
163 |
164 | 我们专注打造稳定高性能纯异步基于HTTP的微服务框架,作为nginx+php-fpm的替代技术栈实现架构的微服务化;而Tcp/WebSocket Server将作为插件的形势支持,或者作为其他独立的开源项目。
165 |
166 | 对于小型团队或者业务系统我们建议还是采用传统的nginx+php-fpm技术栈,对于成本和性能来说没有瓶颈,也就完全没有必要引入全新的技术栈。
167 |
168 | 对于大中型团队或者业务系统,处在服务治理或者服务化演进的重要阶段,php-msf是可选方案之一。
169 |
170 | 对于庞大的PHP应用集群,想要大幅节约服务器成本,提升服务性能,php-msf是可选方案之一。
171 |
172 | 对于聚合服务,比如大型的网站首页,想要通过服务器端聚合内容整合数据,php-msf是可选方案之一。
173 |
174 | ## 手工安装
175 |
176 | 推荐安装方式,通过编辑项目`composer.json`加入依赖`pinguo/php-msf`
177 |
178 | ```json
179 | {
180 | "require": {
181 | "pinguo/php-msf": ">=3.0.0"
182 | },
183 | "minimum-stability": "dev"
184 | }
185 | ```
186 |
187 | `"minimum-stability": "dev"`这个配置选项必须加上,因为日志组件依赖`"monolog/monolog": "2.0.x-dev"`,并且`monolog/monolog`无2.0的release包,不过我们在生产环境已经验证其稳定性。
188 |
189 | ## 项目原则
190 |
191 | ### 稳定
192 |
193 | php-msf经受了[Camera360](https://www.camera360.com)社区服务大流量、高并发的洗礼,稳定性得到充分验证。稳定性是我们花了大量时间、精力去解决的最重要问题,是三大原则的最重要原则。
194 |
195 | ### 高性能
196 |
197 | IO密集性业务的单机处理能力提升5-10倍,这是生产环境中得出的真实数据,如Camera360社区某聚合服务在流量高峰需要40台服务器抗住流量,而采用php-msf重构之后只需要4台相同配置的服务器就可以抗住所有流量。
198 |
199 | ### 简单
200 |
201 | 由于Swoole复杂的进程模型,并且有同步阻塞和异步非阻塞之分,所以在运行相同代码逻辑时,可能在调用方式、传递参数都不一致,从而直线拉高了学习成本,我们为了屏蔽低层的差异,做了大量的工作,实现和传统MVC框架的唯一区别在于添加“yield”关键字。我们参考了Yii2框架的部分代码实践,我们期望无缝的从Yii2开发切换过来。
202 |
203 | 上述三大原则,是我们在新增特性、功能实现时,投票或者合并代码的依据,任何影响这些原则的PR也将会被拒绝。
204 |
205 | ## 关于协程
206 |
207 | 目前社区有几个PHP开源项目支持协程,它们大多采用Generator+Yield来实现,但是实现的细微差别会导致性能相差甚远,我们应该认识到协程能够以同步的代码书写方式而运行异步逻辑,故协程调度器的性能一定要足够的高,php-msf的协程调度性能是原生异步回调方式的80%,也就是说某个API采用原生异步回调写法QPS为10000,通过php-msf协程调度器调度QPS为8000。
208 |
209 | ## 为什么是微服务框架?
210 |
211 | 目前php-msf还在起步阶段,我们花了大量的时间和精力解决稳定性、高性能、内存问题,因为我们认为“基石”是“万丈高楼”的最基本的保障,只有基础打得牢,才能将“大楼”建设得“更高”。3.0版本是我们开源的起始版本,是我们迈出的重要一步,接下来我们重点会是分布式微服务框架的打磨。
212 |
213 | 另外,由于基于PHP常驻进程,并直接解析HTTP或者TCP请求,这是服务化最重要的支撑,基于此我们可以做很多原来不敢去实现的想法,总之想像空间很大。
214 |
215 | ## 感谢
216 |
217 | php-msf最开始基于[SwooleDistributed-1.7.x](https://github.com/tmtbe/SwooleDistributed/)开发,而此次开源版本中,连接池主要采用了SD的实现。由于我们框架定位、解决的业务场景、稳定性的要求、代码风格等差异太大,因此我们决定自主研发微服务框架,每个框架都有自己的特色和优点,选择合适自己公司和业务场景的框架最重要,同时在此也感谢[白猫](https://github.com/tmtbe);另外,在研发php-msf框架及生产环境应用过程中,遇到很多底层问题,不过都一一解决,而这些问题能够解决最重要就是[Swoole](http://www.swoole.com)开源项目创始人[韩天峰-Rango](https://github.com/matyhtf)的大力支持,在此深表感谢。
218 |
219 | ## License
220 |
221 | GNU General Public License, version 2 see [https://www.gnu.org/licenses/gpl-2.0.html](https://www.gnu.org/licenses/gpl-2.0.html)
222 |
--------------------------------------------------------------------------------
/ascii.ui:
--------------------------------------------------------------------------------
1 | _______ ____
2 | ________ / /_ ____ ____ ___ _____/ __/
3 | ___/ __ \/ __ \/ __ \______/ __ `__ \/ ___/ /_
4 | __/ /_/ / / / / /_/ /_____/ / / / / (__ ) __/
5 | _/ .___/_/ /_/ .___/ /_/ /_/ /_/____/_/
6 | /_/ /_/ Camera360 Open Source TM
--------------------------------------------------------------------------------
/changes.txt:
--------------------------------------------------------------------------------
1 | 3.0.5 ==================================================================================================================
2 | bd01803 - xudianyang * 修复break()方法,协程调度返回值错乱的问题
3 | 9dd01ac - xudianyang * 修复协程异常捕获逻辑漏洞
4 | 40a03f0 - niulingyun * redis cluster random模式不支持eval的bug修复
5 | f8f0fd7 - zhanglu + add setnx in redis proxy
6 | 96fd14c - niulingyun + 异步协程shell_exec
7 | 73b210a - xudianyang * 修复yield $cor场景,当$cor抛出异常未能正常捕获的问题
8 | 321161e - xudianyang + 支持Not Found Controller To Default Controller/Action, Http/Client Support All HTTP METHOD
9 | 16c1735 - xudianyang + 关闭服务onShutdown事件支持
10 | bc20c6a - xudianyang * 修复Timer进程内存泄露的问题
11 | 643f011 - xudianyang * 修复事务sql出错时的bug
12 | 9064491 - xudianyang - 移出tidPidTable
13 | 252675c - niulingyun + session support,but only support file adapter now
14 | 22b7d76 - niulingyun + 添加Redis Session支持
15 | d174e4b - xudianyang * 修复默认路由的bug(Cannot declare class App\Controllers\Index, because the
16 | 622b0e0 - shellvon + 支持beanstalkd
17 | 1ca5344 - xudianyang * 日志时间精度提升
18 | a00a90e - niulingyun + timer支持多进程模式
19 | b750744 - niulingyun + task日志的写入比例可控
20 | bcde590 - zhanglu + 通过配置自定义Input类和Output类
21 | 061359f - niulingyun * 重构GET参数兼容逻辑
22 | 780681a - xudianyang * 修复goConcurrent Get请求的查询参数为string的报错
23 | ff9b8a8 - xudianyang * 优化连接池,提供max_conn,max_time,min_conn选项
24 | 8b4acc9 - xudianyang * fixed dump array value null
25 |
26 | 3.0.4 ==================================================================================================================
27 | 3cb4d61 - niulingyun + 增加队列支持,目前支持redis、rabbitMQ、kafka
28 | 32fc570 - niulingyun + http client 支持keep-alive缓存
29 | 7a37e9d - niulingyun + http client 支持gzip
30 | b6bcbfd - shellvon + 增加对MySQL字段和表名的转义
31 | 009477c - xudianyang * 路由路径问题修复
32 | 10024c7 - xudianyang * 修复打印数组的bug,同时去掉只打印100个元素的限制
33 | 2e02052 - niulingyun * 调整获取ip的顺序,修复负载器后获取ip的bug
34 | 095b746 - shellvon * 模版渲染引擎调整为使用官方
35 | 1b50d2f - niulingyun * 修复RPC重复unpack参数bug
36 | 1575443 - xudianyang * 优化协程异常捕获逻辑
37 | 5e9ac71 - niulingyun * 修复两处redis的bug
38 | bf743cc - niulingyun * 修复 task 构造函数传参问题和mysql删除操作的bug
39 | f5d3068 - zhangguangjia * checkRedisProxy新增redis密码授权
40 | d93e727 - xudianyang * 修复Post传递Query参数的问题
41 | 43dffd1 - niulingyun * 支持直接在url之后带参数形式的GET请求
42 | 7bc5bbb - zhanglu * 修改input:getAllPostGet返回get+post的参数
43 |
44 | 3.0.3 ==================================================================================================================
45 | 9a31436 - xudianyang * 调整processType标识
46 | 4b7df57 - niulingyun * 异步redis set过期时间增加EX标识
47 | 8e8eb58 - xudianyang + Timer进程支持多个定时器
48 | 1de66b5 - xudianyang + Timer进程支持协程调度
49 | e313c9a - xudianyang * 重构Request ID
50 |
51 | 3.0.2 ==================================================================================================================
52 | 9d6e003 - xudianyang * 修复请求链log_id的问题
53 | 5614caa - xudianyang + 异常规范、HTTP Access日志
54 | 8b3d8c6 - syyongx * Fix Restful bug
55 | 28ca9b2 - xudianyang * 规范Task异常时的日志
56 | e5793aa - 闫帅兵 * 优化inotify,完美支持文件或者目录的修改、删除、新增
57 | 430c4fa - xudianyang * 优化MySQL连接池,更简单,更容易使用
58 | 76cc105 - xudianyang + MySQL Proxy(主从结构、读写分离、支持事务)
59 | 0aa72ec - xudianyang + HTTP SEND FILE 静态文件(支持域名绑定)
60 | 445e2bb - xudianyang + 完善和优化MySQL同步模式,和异步模式接口一致
61 | 24524b2 - niulingyun + 新增异步协程毫秒级sleep,功能同php的sleep函数
62 |
63 | 3.0.1 ==================================================================================================================
64 | cfdb0b4 - niulingyun * 修复controller自动重置public属性
65 |
66 | 3.0.0 ==================================================================================================================
67 | 508380b - pinguo-xudianyang: * 调整controller::destroy策略
68 | 3b4c199 - pinguo-xudianyang: * 修复监控端口的问题
69 | 550757c - pinguo-xudianyang: * 修复MSFCli模式的报错
70 | c125044 - pinguo-xudianyang: + 添加Macro::PROCESS_WORKER,Macro::PROCESS_TASKER,Macro::PROCESS_USER进程标识宏
71 | 85eef20 - pinguo-xudianyang: * TaskProxy兼容用户自定义进程
72 | 2630446 - pinguo-xudianyang: + worker exit统计
73 | a23c91b - pinguo-xudianyang: + monitor writeln
74 | 02ae54b - pinguo-xudianyang: * 修复TaskProxy的类名问题
75 | 2874a3c - pinguo-xudianyang: * 控制台日志优化
76 | b9e571c - pinguo-xudianyang: * 调整RestRoute类位置
77 | 128acda - pinguo-xudianyang: * 控制器销毁逻辑策略调整
78 | 42cdeaa - pinguo-xudianyang: * 控制台日志标准优化及修复CLI模式进程无法退出的问题
79 | 13e5d4f - pinguo-xudianyang: * HttpServer dump输出到控制台
80 | f4289a1 - pinguo-xudianyang: * 调整协程调度器命名
81 | 5aeebf6 - pinguo-niulingyun: * uni console output
82 | 1b3ff26 - pinguo-xudianyang: * 修复TaskProxy的支持问题
83 | 53aa785 - pinguo-xudianyang: * 修复HTTP协程请求失败时日志记录失败的问题
84 | 4e15178 - pinguo-xudianyang: * 调整协程调度器命名
85 | 28dbbc3 - pinguo-xudianyang: * 修复Pool::setCurrentObjParent bug
86 | 4e136cd - pinguo-xudianyang: * 完善RPC
87 | fdfab09 - pinguo-xudianyang: * 重构RPC
88 | 73538b0 - pinguo-xudianyang: * 内置Pack打包器
89 | 926dea0 - pinguo-zengzhiqiang: + 文件头统一
90 | 9d973b2 - pinguo-lipengcheng: * add license md
91 | 9b32741 - pinguo-xudianyang: * 连接池前缀,删除独立进程连接池的支持
92 | 17ea068 - pinguo-lipengcheng: + add CREDITS
93 | 4b27ad6 - pinguo-xudianyang: * Server运行状态监控重构
94 | 89e40a0 - pinguo-xudianyang: * 对象池构造方法传参方式修改为数组
95 | 89e40a0 - pinguo-xudianyang: * 销毁对象支持自定义级别(DS_PUBLIC,DS_PROTECTED,DS_PRIVATE)
96 | 5eb1bed - pinguo-xudianyang: * 修复Http协程类型判断的问题
97 | aadbc77 - pinguo-xudianyang: * 完善连接池
98 | eefce11 - pinguo-xudianyang: * MySQL协程实现
99 | 3222e0d - pinguo-xudianyang: * 统一Tasker进程内的对象加载方式
100 | 3222e0d - pinguo-xudianyang: * 文件头统一
101 | 3222e0d - pinguo-xudianyang: * 所有类方法或者函数的参数注释
102 | 3222e0d - pinguo-xudianyang: - 去掉TCP(专注HTTP)
103 | 7049260 - pinguo-xudianyang: * 任何内建对象都可以采用通用对象池创建
104 | 7049260 - pinguo-xudianyang: * 所有协程方法使用go关键字作为前缀
105 | 7049260 - pinguo-xudianyang: * Controller使用通用对象池加载
106 | c09367f - pinguo-xudianyang: * 统一普通对象、Model、Task的加载
107 | c09367f - pinguo-xudianyang: * 对象池支持构造方法
108 | c09367f - pinguo-xudianyang: * 调用Task方法简化为两步
109 | c09367f - pinguo-xudianyang: * 所有框架内核依赖的成员变量及方法以__打头
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "pinguo/php-msf",
3 | "description": "Pinguo Micro Service Framework For PHP",
4 | "license": "GPL",
5 | "type": "library",
6 | "homepage": "https://github.com/pinguo/php-msf",
7 | "require": {
8 | "php": ">=7.0",
9 | "ext-swoole": ">=1.9.15",
10 | "pinguo/php-log": ">=1.0.2",
11 | "pinguo/php-context": ">=1.0.2",
12 | "pinguo/php-aop": "1.0.0",
13 | "league/plates": "3.3.0",
14 | "hassankhan/config": "~0.10",
15 | "flexihash/flexihash": "^2.0.0"
16 | },
17 | "suggest": {
18 | "alcaeus/mongo-php-adapter": "^1.0",
19 | "ext-amqp": ">=1.8.0",
20 | "pda/pheanstalk": "PHP client for beanstalkd queue",
21 | "kwn/php-rdkafka-stubs": "for kafka support"
22 | },
23 | "autoload": {
24 | "psr-4": {
25 | "PG\\MSF\\": "src/"
26 | },
27 | "files": [
28 | "src/Helpers/Common.php",
29 | "src/Macro.php"
30 | ]
31 | },
32 | "minimum-stability": "dev",
33 | "prefer-stable": true,
34 | "extra": {
35 | },
36 | "config": {
37 | "secure-http": false
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/php-msf.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/pinguo/php-msf/2a193c53da996c260e8aace9affa12fc2b9c08a6/php-msf.png
--------------------------------------------------------------------------------
/src/Base/Child.php:
--------------------------------------------------------------------------------
1 | context = $context;
37 | return $this;
38 | }
39 |
40 | /**
41 | * 销毁,解除引用
42 | */
43 | public function destroy()
44 | {
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/src/Base/Core.php:
--------------------------------------------------------------------------------
1 | server;
106 | }
107 |
108 | /**
109 | * 获取运行server实例配置对象
110 | *
111 | * @return Config
112 | */
113 | public function getConfig()
114 | {
115 | return getInstance()->config;
116 | }
117 |
118 | /**
119 | * 获取运行server实例打包对象
120 | *
121 | * @return IPack
122 | */
123 | public function getPack()
124 | {
125 | return getInstance()->pack;
126 | }
127 |
128 | /**
129 | * 获取Redis连接池
130 | *
131 | * @param string $poolName 配置的Redis连接池名称
132 | * @return bool|Wrapper|CoroutineRedisProxy|\Redis
133 | */
134 | public function getRedisPool(string $poolName)
135 | {
136 | $activePoolName = $poolName;
137 | $poolName = RedisAsynPool::ASYN_NAME . $poolName;
138 | if (isset($this->redisPools[$poolName])) {
139 | return $this->redisPools[$poolName];
140 | }
141 |
142 | $pool = getInstance()->getAsynPool($poolName);
143 | if (!$pool) {
144 | $pool = new RedisAsynPool($this->getConfig(), $activePoolName);
145 | getInstance()->addAsynPool($poolName, $pool, true);
146 | }
147 |
148 | $this->redisPools[$poolName] = AOPFactory::getRedisPoolCoroutine($pool->getCoroutine(), $this);
149 | return $this->redisPools[$poolName];
150 | }
151 |
152 | /**
153 | * 获取Redis代理
154 | *
155 | * @param string $proxyName 配置的Redis代理名称
156 | * @return bool|Wrapper|CoroutineRedisProxy|\Redis
157 | * @throws Exception
158 | */
159 | public function getRedisProxy(string $proxyName)
160 | {
161 | if (isset($this->redisProxies[$proxyName])) {
162 | return $this->redisProxies[$proxyName];
163 | }
164 |
165 | $proxy = getInstance()->getRedisProxy($proxyName);
166 | if (!$proxy) {
167 | $config = $this->getConfig()->get('redis_proxy.' . $proxyName, null);
168 | if (!$config) {
169 | throw new Exception("config redis_proxy.$proxyName not exits");
170 | }
171 | $proxy = RedisProxyFactory::makeProxy($proxyName, $config);
172 | if (!$proxy) {
173 | throw new Exception('make proxy failed, please check your proxy config.');
174 | }
175 | getInstance()->addRedisProxy($proxyName, $proxy);
176 | }
177 |
178 | $this->redisProxies[$proxyName] = AOPFactory::getRedisProxy($proxy, $this);
179 | return $this->redisProxies[$proxyName];
180 | }
181 |
182 | /**
183 | * 获取MySQL连接池
184 | *
185 | * @param string $poolName 配置的MySQL连接池名称
186 | * @return MysqlAsynPool|Miner|Wrapper
187 | */
188 | public function getMysqlPool(string $poolName)
189 | {
190 | $activePoolName = $poolName;
191 | $poolName = MysqlAsynPool::ASYN_NAME . $poolName;
192 | if (isset($this->mysqlPools[$poolName])) {
193 | return $this->mysqlPools[$poolName];
194 | }
195 |
196 | $pool = getInstance()->getAsynPool($poolName);
197 | if (!$pool) {
198 | $pool = new MysqlAsynPool($this->getConfig(), $activePoolName);
199 | getInstance()->addAsynPool($poolName, $pool, true);
200 | }
201 |
202 | $this->mysqlPools[$poolName] = AOPFactory::getMysqlPoolCoroutine($pool, $this);
203 | return $this->mysqlPools[$poolName];
204 | }
205 |
206 | /**
207 | * 获取Mysql代理
208 | *
209 | * @param string $proxyName 配置的MySQL代理名称
210 | * @return Wrapper|mysqlProxyMasterSlave|Miner|MysqlAsynPool
211 | * @throws Exception
212 | */
213 | public function getMysqlProxy(string $proxyName)
214 | {
215 | if (isset($this->mysqlProxies[$proxyName])) {
216 | return $this->mysqlProxies[$proxyName];
217 | }
218 |
219 | $proxy = getInstance()->getMysqlProxy($proxyName);
220 | if (!$proxy) {
221 | $config = $this->getConfig()->get('mysql_proxy.' . $proxyName, null);
222 | if (!$config) {
223 | throw new Exception("config mysql_proxy.$proxyName not exits");
224 | }
225 | $proxy = MysqlProxyFactory::makeProxy($proxyName, $config);
226 | if (!$proxy) {
227 | throw new Exception('make proxy failed, please check your proxy config.');
228 | }
229 | getInstance()->addMysqlProxy($proxyName, $proxy);
230 | }
231 |
232 | $this->mysqlProxies[$proxyName] = AOPFactory::getMysqlProxy($proxy, $this);
233 | return $this->mysqlProxies[$proxyName];
234 | }
235 |
236 | /**
237 | * 设置RedisPools
238 | *
239 | * @param array|null $redisPools 多个Redis连接池实例,通常用于销毁Redis连接池,赋值为NULL
240 | * @return $this
241 | */
242 | public function setRedisPools($redisPools)
243 | {
244 | if (!empty($this->redisPools)) {
245 | foreach ($this->redisPools as $k => &$pool) {
246 | $pool->destroy();
247 | $poll = null;
248 | }
249 | }
250 |
251 | $this->redisPools = $redisPools;
252 | return $this;
253 | }
254 |
255 | /**
256 | * 设置RedisPools
257 | *
258 | * @param array|null $redisProxies 多个Redis代理实例,通常用于销毁Redis代理,赋值为NULL
259 | * @return $this
260 | */
261 | public function setRedisProxies($redisProxies)
262 | {
263 | if (!empty($this->redisProxies)) {
264 | foreach ($this->redisProxies as $k => &$proxy) {
265 | $proxy->destroy();
266 | $proxy = null;
267 | }
268 | }
269 |
270 | $this->redisProxies = $redisProxies;
271 | return $this;
272 | }
273 |
274 | /**
275 | * 销毁,解除引用
276 | */
277 | public function destroy()
278 | {
279 | if (!$this->__isDestroy) {
280 | parent::destroy();
281 | $this->__isDestroy = true;
282 | }
283 | }
284 |
285 | /**
286 | * 对象已使用标识
287 | */
288 | public function isUse()
289 | {
290 | $this->__isDestroy = false;
291 | }
292 |
293 | /**
294 | * 是否已经执行destroy
295 | *
296 | * @return bool
297 | */
298 | public function getIsDestroy()
299 | {
300 | return $this->__isDestroy;
301 | }
302 | }
303 |
--------------------------------------------------------------------------------
/src/Base/Exception.php:
--------------------------------------------------------------------------------
1 | __serializeRequest = new \stdClass();
35 | $this->__serializeRequest->get = $this->request->get ?? [];
36 | $this->__serializeRequest->post = $this->request->post ?? [];
37 | $this->__serializeRequest->files = $this->request->files ?? [];
38 | $this->__serializeRequest->cookie = $this->request->cookie ?? [];
39 | $this->__serializeRequest->header = $this->request->header ?? [];
40 | $this->__serializeRequest->server = $this->request->server ?? [];
41 |
42 | return ['__serializeRequest'];
43 | }
44 |
45 | /**
46 | * 反序列化后的初始化操作
47 | */
48 | public function __wakeup()
49 | {
50 | $this->request = $this->__serializeRequest;
51 | }
52 |
53 | /**
54 | * 设置请求参数对象
55 | *
56 | * @param \swoole_http_request|\PG\MSF\Console\Request $request 请求参数对象
57 | * @return $this
58 | */
59 | public function set($request)
60 | {
61 | $this->request = $request;
62 | return $this;
63 | }
64 |
65 | /**
66 | * 重置请求参数对象
67 | *
68 | * @return $this
69 | */
70 | public function reset()
71 | {
72 | unset($this->request);
73 | return $this;
74 | }
75 |
76 | /**
77 | * 获取POST/GET参数,优先读取POST
78 | *
79 | * @param string $index 请求参数名
80 | * @return string
81 | */
82 | public function postGet($index)
83 | {
84 | return $this->request->post[$index] ?? $this->get($index);
85 | }
86 |
87 | /**
88 | * 获取POST参数
89 | *
90 | * @param string $index POST参数名
91 | * @return string
92 | */
93 | public function post($index)
94 | {
95 | return $this->request->post[$index] ?? '';
96 | }
97 |
98 | /**
99 | * 获取GET参数
100 | *
101 | * @param string $index GET参数名
102 | * @return string
103 | */
104 | public function get($index)
105 | {
106 | return $this->request->get[$index] ?? '';
107 | }
108 |
109 | /**
110 | * 获取POST/GET参数,优先读取GET
111 | *
112 | * @param string $index 请求参数名
113 | * @return string
114 | */
115 | public function getPost($index)
116 | {
117 | return $this->request->get[$index] ?? $this->post($index);
118 | }
119 |
120 | /**
121 | * 获取所有的POST参数和Get参数
122 | *
123 | * @return array
124 | */
125 | public function getAllPostGet()
126 | {
127 | if (isset($this->request->post, $this->request->get)) {
128 | return array_merge($this->request->get, $this->request->post);
129 | }
130 | return $this->request->post ?? $this->request->get ?? [];
131 | }
132 |
133 | /**
134 | * 获取所有的POST参数
135 | *
136 | * @return array
137 | */
138 | public function getAllPost()
139 | {
140 | return $this->request->post ?? [];
141 | }
142 |
143 | /**
144 | * 获取所有的GET参数
145 | *
146 | * @return array
147 | */
148 | public function getAllGet()
149 | {
150 | return $this->request->get ?? [];
151 | }
152 |
153 |
154 | /**
155 | * 获取请求的所有报头
156 | *
157 | * @return array
158 | */
159 | public function getAllHeader()
160 | {
161 | return $this->request->header ?? [];
162 | }
163 |
164 | /**
165 | * 获取处理请求的Server信息
166 | *
167 | * @return array
168 | */
169 | public function getAllServer()
170 | {
171 | return $this->request->server ?? [];
172 | }
173 |
174 | /**
175 | * 获取原始的POST包体
176 | *
177 | * @return string
178 | */
179 | public function getRawContent()
180 | {
181 | return $this->request->rawContent();
182 | }
183 |
184 | /**
185 | * 设置POST请求参数
186 | *
187 | * @param string $key 设置参数的key
188 | * @param mixed $value 设置参数的value
189 | * @return $this
190 | */
191 | public function setPost($key, $value)
192 | {
193 | $this->request->post[$key] = $value;
194 | return $this;
195 | }
196 |
197 | /**
198 | * 设置GET请求参数
199 | *
200 | * @param string $key 设置参数的key
201 | * @param mixed $value 设置参数的value
202 | * @return $this
203 | */
204 | public function setGet($key, $value)
205 | {
206 | $this->request->get[$key] = $value;
207 | return $this;
208 | }
209 |
210 | /**
211 | * 设置所有POST请求参数
212 | *
213 | * @param array $post 所有的待设置的POST请求参数
214 | * @return $this
215 | */
216 | public function setAllPost(array $post)
217 | {
218 | $this->request->post = $post;
219 | return $this;
220 | }
221 |
222 | /**
223 | * 设置所有GET请求参数
224 | *
225 | * @param array $get 所有的待设置的GET请求参数
226 | * @return $this
227 | */
228 | public function setAllGet(array $get)
229 | {
230 | $this->request->get = $get;
231 | return $this;
232 | }
233 |
234 | /**
235 | * 获取Cookie参数
236 | *
237 | * @param string $index Cookie参数名
238 | * @return string
239 | */
240 | public function getCookie($index)
241 | {
242 | return $this->request->cookie[$index] ?? '';
243 | }
244 |
245 | /**
246 | * 获取上传文件信息
247 | *
248 | * @param string $index File文件名
249 | * @return array
250 | */
251 | public function getFile($index)
252 | {
253 | return $this->request->files[$index] ?? '';
254 | }
255 |
256 | /**
257 | * 获取Server相关的数据
258 | *
259 | * @param string $index 获取的Server参数名
260 | * @return array|bool|string
261 | */
262 | public function getServer($index)
263 | {
264 | return $this->request->server[$index] ?? '';
265 | }
266 |
267 | /**
268 | * 获取请求的方法
269 | *
270 | * @return string
271 | */
272 | public function getRequestMethod()
273 | {
274 | if (isset($this->request->server['http_x_http_method_override'])) {
275 | return strtoupper($this->request->server['http_x_http_method_override']);
276 | }
277 | if (isset($this->request->server['request_method'])) {
278 | return strtoupper($this->request->server['request_method']);
279 | }
280 |
281 | return 'GET';
282 | }
283 |
284 | /**
285 | * 获取请求的URI
286 | *
287 | * @return string
288 | */
289 | public function getRequestUri()
290 | {
291 | return $this->request->server['request_uri'];
292 | }
293 |
294 | /**
295 | * 获取请求的PATH
296 | *
297 | * @return string
298 | */
299 | public function getPathInfo()
300 | {
301 | return $this->request->server['path_info'];
302 | }
303 |
304 | /**
305 | * 获取请求的用户ID
306 | *
307 | * @return string
308 | */
309 | public function getRemoteAddr()
310 | {
311 | return getInstance()::getRemoteAddr($this->request);
312 | }
313 |
314 | /**
315 | * 获取请求报头参数
316 | *
317 | * @param string $index 请求报头参数名
318 | * @return string
319 | */
320 | public function getHeader($index)
321 | {
322 | return $this->request->header[$index] ?? '';
323 | }
324 |
325 | /**
326 | * 销毁,解除引用
327 | */
328 | public function destroy()
329 | {
330 | parent::destroy();
331 | }
332 | }
333 |
--------------------------------------------------------------------------------
/src/Base/Pool.php:
--------------------------------------------------------------------------------
1 | map = [];
41 | }
42 |
43 | /**
44 | * 产生对象池单例
45 | *
46 | * @return Pool
47 | */
48 | public static function getInstance()
49 | {
50 | if (self::$instance == null) {
51 | self::$instance = new Pool();
52 | }
53 | return self::$instance;
54 | }
55 |
56 | /**
57 | * 获取一个
58 | *
59 | * @param string $class 完全命名空间类名
60 | * @param array $args 可变参数列表
61 | * @return mixed
62 | */
63 | public function get($class, ...$args)
64 | {
65 | $poolName = trim($class, '\\');
66 |
67 | if (!$poolName) {
68 | return null;
69 | }
70 |
71 | $pool = $this->map[$poolName] ?? null;
72 | if ($pool == null) {
73 | $pool = $this->applyNewPool($poolName);
74 | }
75 |
76 | if ($pool->count()) {
77 | $obj = $pool->shift();
78 | $obj->__isConstruct = false;
79 | return $obj;
80 | } else {
81 | $reflector = new \ReflectionClass($poolName);
82 | $obj = $reflector->newInstanceWithoutConstructor();
83 | $obj->__useCount = 0;
84 | $obj->__genTime = time();
85 | $obj->__isConstruct = false;
86 | $obj->__DSLevel = Macro::DS_PUBLIC;
87 | unset($reflector);
88 | return $obj;
89 | }
90 | }
91 |
92 | /**
93 | * 创建新的栈,用于储存相应的对象
94 | *
95 | * @param string $poolName 对象池名称
96 | * @return mixed
97 | * @throws Exception
98 | */
99 | private function applyNewPool($poolName)
100 | {
101 | if (array_key_exists($poolName, $this->map)) {
102 | throw new Exception('the name is exists in pool map');
103 | }
104 | $this->map[$poolName] = new \SplStack();
105 |
106 | return $this->map[$poolName];
107 | }
108 |
109 | /**
110 | * 返还一个对象
111 | *
112 | * @param mixed $classInstance 对象实例
113 | */
114 | public function push($classInstance)
115 | {
116 | $class = trim(get_class($classInstance), '\\');
117 | $pool = $this->map[$class] ?? null;
118 | if ($pool == null) {
119 | $pool = $this->applyNewPool($class);
120 | }
121 | $pool->push($classInstance);
122 | }
123 | }
124 |
--------------------------------------------------------------------------------
/src/Client/Exception.php:
--------------------------------------------------------------------------------
1 | getContext()) {
36 | $this->getContext()->getLog()->pushLog('params', $this->getContext()->getInput()->getAllPostGet());
37 | $this->getContext()->getLog()->pushLog('status', '200');
38 | }
39 | parent::destroy();
40 | clearTimes();
41 | exit();
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Console/Request.php:
--------------------------------------------------------------------------------
1 | server === null) {
45 | if (isset($_SERVER['argv'])) {
46 | $this->server = $_SERVER['argv'];
47 | array_shift($this->server);
48 | } else {
49 | $this->server = [];
50 | }
51 | }
52 |
53 | return $this->server;
54 | }
55 |
56 | /**
57 | * 设置服务器相关变量(兼容Web模式)
58 | *
59 | * @param array $params 参数列表
60 | * @return $this
61 | */
62 | public function setServer($params)
63 | {
64 | $this->server = $params;
65 | return $this;
66 | }
67 |
68 | /**
69 | * 解析命令行参数
70 | *
71 | * @return array
72 | */
73 | public function resolve()
74 | {
75 | $rawParams = $this->getServer();
76 | if (isset($rawParams[0])) {
77 | $route = $rawParams[0];
78 | array_shift($rawParams);
79 | } else {
80 | $route = '';
81 | }
82 |
83 | $params = [];
84 | foreach ($rawParams as $param) {
85 | if (preg_match('/^--(\w+)(?:=(.*))?$/', $param, $matches) || preg_match('/^-(\w+)(?:=(.*))?$/', $param, $matches)) {
86 | $name = $matches[1];
87 | $params[$name] = isset($matches[2]) ? $matches[2] : true;
88 | } else {
89 | $params[] = $param;
90 | }
91 | }
92 |
93 | $this->server['path_info'] = $route;
94 | $this->get = $params;
95 | $this->post = $params;
96 |
97 | return [$route, $params];
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/Controllers/Bench.php:
--------------------------------------------------------------------------------
1 | getRedisPool('bench')->set('bench_set', 'set')->break();
25 | $this->outputJson('ok');
26 | }
27 |
28 | public function actionRedisPoolGet()
29 | {
30 | $val = yield $this->getRedisPool('bench')->get('bench_set');
31 | $this->getContext()->getOutput()->end($val);
32 | }
33 |
34 | public function actionRedisGetCallBack()
35 | {
36 | if (empty($this->client)) {
37 | $this->client = new \swoole_redis();
38 | $this->client->connect('127.0.0.1', 6379, function (\swoole_redis $client, $result) {
39 | if ($result === false) {
40 | $this->getContext()->getOutput()->end("connect to redis server failed");
41 | return;
42 | } else {
43 | $this->getContext()->getOutput()->end("connect success");
44 | }
45 | });
46 | } else {
47 | $this->client->get('bench_set', function (\swoole_redis $client, $result) {
48 | $this->getContext()->getOutput()->end($result);
49 | });
50 | }
51 | }
52 |
53 | public function actionHttpCallBack()
54 | {
55 | $cli = new \swoole_http_client('127.0.0.1', 80);
56 | $cli->setHeaders([
57 | 'Host' => 'localhost',
58 | ]);
59 | $cli->get('/', function ($client) {
60 | $this->getContext()->getOutput()->end($client->body);
61 | });
62 | }
63 |
64 | public function actionHttpCoroutine()
65 | {
66 | $data = yield $this->getObject(Client::class)->goSingleGet('http://127.0.0.1/');
67 | $this->getContext()->getOutput()->end($data['body']);
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/src/Controllers/Controller.php:
--------------------------------------------------------------------------------
1 | requestStartTime = microtime(true);
53 | }
54 |
55 | /**
56 | * 获取对象池
57 | *
58 | * @return Wrapper|\PG\MSF\Base\Pool
59 | */
60 | public function getObjectPool()
61 | {
62 | return $this->objectPool;
63 | }
64 |
65 | /**
66 | * 设置对象池
67 | *
68 | * @param Wrapper|\PG\MSF\Base\Pool|NULL $objectPool Pool实例
69 | * @return $this
70 | */
71 | public function setObjectPool($objectPool)
72 | {
73 | $this->objectPool = $objectPool;
74 | return $this;
75 | }
76 |
77 | /**
78 | * 设置请求类型
79 | *
80 | * @param string $requestType TCP_REQUEST|HTTP_REQUEST
81 | * @return $this
82 | */
83 | public function setRequestType($requestType)
84 | {
85 | $this->requestType = $requestType;
86 | return $this;
87 | }
88 |
89 | /**
90 | * 返回请求类型
91 | *
92 | * @return string
93 | */
94 | public function getRequestType()
95 | {
96 | return $this->requestType;
97 | }
98 |
99 | /**
100 | * 异常的回调
101 | *
102 | * @param \Throwable $e 异常实例
103 | * @throws \Throwable
104 | */
105 | public function onExceptionHandle(\Throwable $e)
106 | {
107 | try {
108 | if ($e->getPrevious()) {
109 | $ce = $e->getPrevious();
110 | $errMsg = dump($ce, false, true);
111 | } else {
112 | $errMsg = dump($e, false, true);
113 | $ce = $e;
114 | }
115 | $this->getContext()->getLog()->error($errMsg);
116 | if ($this->getContext()->getOutput()) {
117 | $this->output('Internal Server Error', 500);
118 | }
119 | } catch (\Throwable $ne) {
120 | getInstance()->log->error('previous exception ' . dump($ce, false, true));
121 | getInstance()->log->error('handle exception ' . dump($ne, false, true));
122 | }
123 | }
124 |
125 | /**
126 | * 请求处理完成销毁相关资源
127 | */
128 | public function destroy()
129 | {
130 | if ($this->getContext()) {
131 | $this->getContext()->getLog()->appendNoticeLog();
132 | //销毁对象池
133 | foreach ($this->objectPoolBuckets as $k => $obj) {
134 | $this->objectPool->push($obj);
135 | $this->objectPoolBuckets[$k] = null;
136 | unset($this->objectPoolBuckets[$k]);
137 | }
138 | $this->resetProperties();
139 | $this->__isConstruct = false;
140 | getInstance()->objectPool->push($this);
141 | parent::destroy();
142 | }
143 | }
144 |
145 | /**
146 | * 响应原始数据
147 | *
148 | * @param mixed|null $data 响应数据
149 | * @param int $status 响应HTTP状态码
150 | * @return void
151 | */
152 | public function output($data = null, $status = 200)
153 | {
154 | $this->getContext()->getOutput()->output($data, $status);
155 | }
156 |
157 | /**
158 | * 响应json格式数据
159 | *
160 | * @param mixed|null $data 响应数据
161 | * @param int $status 响应HTTP状态码
162 | * @return void
163 | */
164 | public function outputJson($data = null, $status = 200)
165 | {
166 | $this->getContext()->getOutput()->outputJson($data, $status);
167 | }
168 |
169 | /**
170 | * 通过模板引擎响应输出HTML
171 | *
172 | * @param array $data 待渲染KV数据
173 | * @param string|null $view 文件名
174 | * @throws \Exception
175 | * @throws \Throwable
176 | * @throws Exception
177 | * @return void
178 | */
179 | public function outputView(array $data, $view = null)
180 | {
181 | $this->getContext()->getOutput()->outputView($data, $view);
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/src/Controllers/Monitor.php:
--------------------------------------------------------------------------------
1 | sysCache->get(Macro::SERVER_STATS);
25 |
26 | if ($data) {
27 | $concurrency = 0;
28 | foreach ($data['worker'] as $id => $worker) {
29 | if (!empty($worker['coroutine']['total'])) {
30 | $concurrency += $worker['coroutine']['total'];
31 | }
32 | }
33 | $data['running']['concurrency'] = $concurrency;
34 | $data['sys_cache'] = getInstance()->sysCache->info();
35 | $this->outputJson($data);
36 | } else {
37 | $data = [];
38 | $data['sys_cache'] = getInstance()->sysCache->info();
39 | $this->outputJson($data);
40 | }
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/Controllers/Rest.php:
--------------------------------------------------------------------------------
1 | 'rest/create',
11 | * 'GET rests/' => 'rest/view',
12 | * 'GET rests/' => 'rest/',
13 | * 'GET rests' => 'rest/index',
14 | * 'PUT,PATCH rests/' => 'rest/update',
15 | * 'DELETE rests/' => 'rest/delete',
16 | * 'OPTIONS rests' => 'rest/options',
17 | * 'OPTIONS rests/' => 'rest/options',
18 | * ];
19 | * 建议action:
20 | * [
21 | * update -> 更新资源,如:/users/
22 | * delete -> 删除资源,如:/users/
23 | * view -> 查看资源单条数据,如:/users/
24 | * index -> 查看资源列表数据(可分页),如:/users
25 | * options -> 查看资源所支持的HTTP动词,如:/users/ | /users
26 | * create -> 新建资源,如:/users
27 | * ]
28 | *
29 | * @author camera360_server@camera360.com
30 | * @copyright Chengdu pinguo Technology Co.,Ltd.
31 | */
32 |
33 | namespace PG\MSF\Controllers;
34 |
35 | use PG\MSF\Rest\Controller;
36 | use PG\MSF\Base\Output;
37 |
38 | /**
39 | *
40 | * Class Rest
41 | * @package PG\MSF\Controllers
42 | */
43 | class Rest extends Controller
44 | {
45 | /**
46 | * POST
47 | * /rests
48 | * x-www-form-urlencoded
49 | * [
50 | * 'p1' => 1,
51 | * 'p2' => 'Hello',
52 | * ]
53 | */
54 | public function actionCreate()
55 | {
56 | $data = [
57 | 'f1' => $this->getContext()->getInput()->post('p1'),
58 | 'f2' => $this->getContext()->getInput()->post('p2'),
59 | ];
60 |
61 | $this->outputJson($data);
62 | //$this->outputJson(null, 'shibaile', 403);
63 | }
64 |
65 | /**
66 | * GET
67 | * /rests?p1=1&p2=Hello
68 | */
69 | public function actionIndex()
70 | {
71 | $data = [
72 | [
73 | 'f1' => $this->getContext()->getInput()->get('p1'),
74 | 'f2' => $this->getContext()->getInput()->get('p2'),
75 | ],
76 | [
77 | 'f1' => $this->getContext()->getInput()->get('p1'),
78 | 'f2' => $this->getContext()->getInput()->get('p2'),
79 | ]
80 | ];
81 | $this->outputJson($data);
82 | //$this->outputJson(null, 'canshucuowuyo', 401);
83 | }
84 |
85 | /**
86 | * GET
87 | * /rests/1?p1=1&p2=Hello
88 | */
89 | public function actionView()
90 | {
91 | $data = [
92 | 'f1' => $this->getContext()->getInput()->get('p1'),
93 | 'f2' => $this->getContext()->getInput()->get('p2'),
94 | 'f3' => $this->getContext()->getInput()->get('id'),
95 | ];
96 | $this->outputJson($data);
97 | }
98 |
99 | /**
100 | * OPTIONS
101 | * /rests | /rests/1
102 | */
103 | public function actionOptions()
104 | {
105 | if ($this->getContext()->getInput()->get('id')) {
106 | $options = $this->resourceOptions;
107 | } else {
108 | $options = $this->collectionOptions;
109 | }
110 | $this->outputOptions($options);
111 | }
112 |
113 | /**
114 | * PUT|PATCH
115 | * /rests/1
116 | * x-www-form-urlencoded
117 | * [
118 | * 'p1' => 1,
119 | * 'p2' => 'Hello',
120 | * ]
121 | */
122 | public function actionUpdate()
123 | {
124 | $data = [
125 | 'f1' => $this->getContext()->getInput()->post('p1'),
126 | 'f2' => $this->getContext()->getInput()->post('p2'),
127 | 'id' => $this->getContext()->getInput()->get('id')
128 | ];
129 |
130 | $this->outputJson($data);
131 | }
132 |
133 | /**
134 | * DELETE
135 | * /rests/1
136 | */
137 | public function actionDelete()
138 | {
139 | $this->outputJson(null, Output::$codes[204], 204);
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/Controllers/Rpc.php:
--------------------------------------------------------------------------------
1 | getContext()->getInput()->getHeader('x-rpc') && isset($arguments[0])) {
73 | $this->parseHttpArgument($arguments);
74 | yield $this->runMethod();
75 | } else {
76 | $this->outputJson([], 400);
77 | }
78 | }
79 |
80 | /**
81 | * 解析RPC参数
82 | *
83 | * @param $arguments
84 | * @throws Exception
85 | */
86 | protected function parseHttpArgument($arguments)
87 | {
88 | if ($this->isRpc) {
89 | $unPackArgs = (array)getInstance()->pack->unPack($arguments[0]);
90 | if (!isset($unPackArgs['handler'])) {
91 | throw new Exception('Rpc argument of handler not set.');
92 | }
93 | if (!isset($unPackArgs['method'])) {
94 | throw new Exception('Rpc argument of method not set.');
95 | }
96 | if (!isset($unPackArgs['args'])) {
97 | throw new Exception('Rpc argument of args not set.');
98 | }
99 | $this->version = $unPackArgs['version'] ?? null;
100 | $this->handler = $unPackArgs['handler'];
101 | $this->method = $unPackArgs['method'];
102 | $this->reqParams = (array)$unPackArgs['args'];
103 | $this->construct = (array)$unPackArgs['construct'];
104 | $this->rpcTime = $unPackArgs['time'];
105 | }
106 | }
107 |
108 | /**
109 | * 执行
110 | *
111 | * @throws Exception
112 | */
113 | protected function runMethod()
114 | {
115 | $handlerClass = '\\App\\Models\\Handlers\\' . $this->handler;
116 | $handlerInstance = $this->getObject($handlerClass, $this->construct);
117 |
118 | if (!method_exists($handlerInstance, $this->method)) {
119 | throw new Exception('Rpc method not found.');
120 | }
121 |
122 | $response = yield $handlerInstance->{$this->method}(...$this->reqParams);
123 | $this->getContext()->getLog()->pushLog('Rpc', [$handlerClass => $this->method]);
124 | $this->outputJson($response);
125 | }
126 | }
127 |
--------------------------------------------------------------------------------
/src/Coroutine/Base.php:
--------------------------------------------------------------------------------
1 | config->get('coroutine.timeout', 30000);
81 | }
82 |
83 | if ($timeout > 0) {
84 | $this->timeout = $timeout;
85 | } else {
86 | $this->timeout = self::$maxTimeout;
87 | }
88 |
89 | $this->result = CNull::getInstance();
90 | $this->requestTime = microtime(true);
91 | }
92 |
93 | /**
94 | * 获取协程执行结果
95 | *
96 | * @return mixed|null
97 | */
98 | public function getResult()
99 | {
100 | if ($this->isTimeout() && !$this->ioBack) {
101 | return null;
102 | }
103 |
104 | return $this->result;
105 | }
106 |
107 | /**
108 | * 协程超时异常
109 | *
110 | * @throws Exception
111 | */
112 | public function throwTimeOutException()
113 | {
114 | throw new Exception("[coroutine]: Time Out, [class]: " . get_class($this) . ", [Request]: $this->request");
115 | }
116 |
117 | /**
118 | * 判断协程是否超时
119 | *
120 | * @return bool
121 | */
122 | public function isTimeout()
123 | {
124 | if (!$this->ioBack && (1000 * (microtime(true) - $this->requestTime) > $this->timeout)) {
125 | return true;
126 | }
127 |
128 | return false;
129 | }
130 |
131 | /**
132 | * 通知调度器进行下一次迭代
133 | *
134 | * @return bool
135 | */
136 | public function nextRun()
137 | {
138 | if (empty(getInstance()->scheduler->IOCallBack[$this->requestId])) {
139 | return true;
140 | }
141 |
142 | foreach (getInstance()->scheduler->IOCallBack[$this->requestId] as $k => $coroutine) {
143 | if ($coroutine->ioBack && !empty(getInstance()->scheduler->taskMap[$this->requestId])) {
144 | unset(getInstance()->scheduler->IOCallBack[$this->requestId][$k]);
145 | getInstance()->scheduler->schedule(getInstance()->scheduler->taskMap[$this->requestId]);
146 | } else {
147 | break;
148 | }
149 | }
150 |
151 | return true;
152 | }
153 |
154 | /**
155 | * 销毁
156 | */
157 | public function destroy()
158 | {
159 | $this->ioBack = false;
160 | $this->ioBackKey = null;
161 | $this->isBreak = false;
162 | $this->requestId = null;
163 | }
164 |
165 | /**
166 | * 属性不用于序列化
167 | *
168 | * @return array
169 | */
170 | public function __unsleep()
171 | {
172 | return ['context'];
173 | }
174 |
175 | /**
176 | * 发送异步请求后不需要执行回调
177 | *
178 | * @return bool
179 | */
180 | public function break()
181 | {
182 | if ($this->requestId && $this->ioBackKey !== null) {
183 | unset(getInstance()->scheduler->IOCallBack[$this->requestId][$this->ioBackKey]);
184 | $this->isBreak = true;
185 | }
186 |
187 | return true;
188 | }
189 |
190 | /**
191 | * 手工设置超时时间
192 | *
193 | * @param int $timeout 超时时间,单位毫秒
194 | * @return $this
195 | */
196 | public function setTimeout($timeout)
197 | {
198 | $this->timeout = $timeout;
199 | return $this;
200 | }
201 |
202 | /**
203 | * 发送异步请求
204 | *
205 | * @param $callback
206 | */
207 | abstract public function send($callback);
208 | }
209 |
--------------------------------------------------------------------------------
/src/Coroutine/CNull.php:
--------------------------------------------------------------------------------
1 | taskProxyData = $taskProxyData;
39 | $profileName = $taskProxyData['message']['task_name'] . '::' . $taskProxyData['message']['task_fuc_name'];
40 | $this->requestId = $this->getContext()->getRequestId();
41 | $requestId = $this->requestId;
42 |
43 | $this->getContext()->getLog()->profileStart($profileName);
44 | getInstance()->scheduler->IOCallBack[$this->requestId][] = $this;
45 | $keys = array_keys(getInstance()->scheduler->IOCallBack[$this->requestId]);
46 | $this->ioBackKey = array_pop($keys);
47 |
48 | $this->send(function ($serv, $taskId, $data) use ($profileName, $requestId) {
49 | if (empty($this->getContext()) || ($requestId != $this->getContext()->getRequestId())) {
50 | return;
51 | }
52 |
53 | if ($this->isBreak) {
54 | return;
55 | }
56 |
57 | if (empty(getInstance()->scheduler->taskMap[$this->requestId])) {
58 | return;
59 | }
60 |
61 | $this->getContext()->getLog()->profileEnd($profileName);
62 | $this->result = $data;
63 | $this->ioBack = true;
64 | $this->nextRun();
65 | });
66 | }
67 |
68 | /**
69 | * 投递异步任务给Tasker进程
70 | *
71 | * @param callable $callback 任务完成后的回调函数
72 | * @return $this
73 | * @throws Exception
74 | */
75 | public function send($callback)
76 | {
77 | $this->id = getInstance()->server->task($this->taskProxyData, -1, $callback);
78 | if ($this->id === false) {
79 | throw new Exception("worker->tasker send async task failed, data: " . dump($this->taskProxyData, false, true));
80 | }
81 |
82 | return $this;
83 | }
84 |
85 | /**
86 | * 销毁
87 | */
88 | public function destroy()
89 | {
90 | parent::destroy();
91 | }
92 | }
93 |
--------------------------------------------------------------------------------
/src/Coroutine/Dns.php:
--------------------------------------------------------------------------------
1 | client = $client;
41 | $this->headers = $headers;
42 | $profileName = mt_rand(1, 9) . mt_rand(1, 9) . mt_rand(1, 9) . '#dns-' . $this->client->urlData['host'];
43 | $this->requestId = $this->getContext()->getRequestId();
44 | $requestId = $this->requestId;
45 |
46 | getInstance()->scheduler->IOCallBack[$this->requestId][] = $this;
47 | $this->getContext()->getLog()->profileStart($profileName);
48 | $keys = array_keys(getInstance()->scheduler->IOCallBack[$this->requestId]);
49 | $this->ioBackKey = array_pop($keys);
50 |
51 | $this->send(function (Client $client) use ($profileName, $requestId) {
52 | if (empty($this->getContext()) || ($requestId != $this->getContext()->getRequestId())) {
53 | return;
54 | }
55 |
56 | if ($this->isBreak) {
57 | return;
58 | }
59 |
60 | if (empty(getInstance()->scheduler->taskMap[$this->requestId])) {
61 | return;
62 | }
63 |
64 | $this->result = $client;
65 | $this->responseTime = microtime(true);
66 | $this->getContext()->getLog()->profileEnd($profileName);
67 | $this->ioBack = true;
68 | $this->nextRun();
69 | });
70 | }
71 |
72 | /**
73 | * 发送DNS查询请求
74 | *
75 | * @param callable $callback DNS解析完成后的回调
76 | * @return $this
77 | */
78 | public function send($callback)
79 | {
80 | $this->client->asyncDNSLookup($callback, $this->headers);
81 | return $this;
82 | }
83 |
84 | /**
85 | * 销毁
86 | */
87 | public function destroy()
88 | {
89 | parent::destroy();
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/Coroutine/Exception.php:
--------------------------------------------------------------------------------
1 | getPrevious()->getMessage();
27 | }
28 |
29 | /**
30 | * Exception constructor.
31 | *
32 | * @param string $message 异常信息
33 | * @param int $code 异常码
34 | * @param \Exception|null $previous
35 | */
36 | public function __construct($message, $code = 0, \Exception $previous = null)
37 | {
38 | parent::__construct($message, $code, $previous);
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/src/Coroutine/File.php:
--------------------------------------------------------------------------------
1 | __filename = $filename;
31 | $this->__type = self::READ_FILE;
32 |
33 | $this->requestId = $this->getContext()->getRequestId();
34 | $requestId = $this->requestId;
35 |
36 | getInstance()->scheduler->IOCallBack[$this->requestId][] = $this;
37 | $keys = array_keys(getInstance()->scheduler->IOCallBack[$this->requestId]);
38 | $this->ioBackKey = array_pop($keys);
39 |
40 | $this->send(function ($filename, $content) use ($requestId) {
41 | if (empty($this->getContext()) || ($requestId != $this->getContext()->getRequestId())) {
42 | return;
43 | }
44 |
45 | if (empty(getInstance()->scheduler->taskMap[$this->requestId])) {
46 | return;
47 | }
48 |
49 | $this->result = $content;
50 | $this->ioBack = true;
51 | $this->nextRun();
52 | });
53 |
54 | return $this;
55 | }
56 |
57 | /**
58 | * 异步写文件
59 | * @param string $filename 文件名
60 | * @param string $fileContent 内容
61 | * @param int $flags 选项,FILE_APPEND表示追加
62 | * @return $this
63 | */
64 | public function goWriteFile(string $filename, string $fileContent, int $flags = 0)
65 | {
66 | $this->__filename = $filename;
67 | $this->__type = self::WRITE_FILE;
68 | $this->__content = $fileContent;
69 | $this->__flags = $flags;
70 |
71 | $this->requestId = $this->getContext()->getRequestId();
72 | $requestId = $this->requestId;
73 |
74 | getInstance()->scheduler->IOCallBack[$this->requestId][] = $this;
75 | $keys = array_keys(getInstance()->scheduler->IOCallBack[$this->requestId]);
76 | $this->ioBackKey = array_pop($keys);
77 |
78 | $this->send(function ($filename) use ($requestId) {
79 | if (empty($this->getContext()) || ($requestId != $this->getContext()->getRequestId())) {
80 | return;
81 | }
82 |
83 | if (empty(getInstance()->scheduler->taskMap[$this->requestId])) {
84 | return;
85 | }
86 |
87 | $this->result = true;
88 | $this->ioBack = true;
89 | $this->nextRun();
90 | });
91 |
92 | return $this;
93 | }
94 |
95 |
96 | /**
97 | * 异步文件IO,操作成功会执行回调,操作失败抛异常
98 | * @param $callback
99 | * @return $this
100 | * @throws Exception
101 | */
102 | public function send($callback)
103 | {
104 | $result = false;
105 | if ($this->__type === self::READ_FILE) {
106 | $result = \Swoole\Async::readFile($this->__filename, $callback);
107 | }
108 | if ($this->__type === self::WRITE_FILE) {
109 | $result = \Swoole\Async::writeFile($this->__filename, $this->__content, $callback, $this->__flags);
110 | }
111 |
112 | if ($result === false) {
113 | throw new Exception("{$this->__filename} 无法打开,请检查");
114 | }
115 |
116 | return $this;
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/Coroutine/Http.php:
--------------------------------------------------------------------------------
1 | client = $client;
52 | $this->path = $path;
53 | $this->method = $method;
54 | $this->data = $data;
55 | $profileName = mt_rand(1, 9) . mt_rand(1, 9) . mt_rand(1, 9) . '#api-http://' . $this->client->urlData['host'] . ':' . $this->client->urlData['port'] . $this->path;
56 | $this->requestId = $this->getContext()->getRequestId();
57 | $requestId = $this->requestId;
58 |
59 | $this->getContext()->getLog()->profileStart($profileName);
60 | getInstance()->scheduler->IOCallBack[$this->requestId][] = $this;
61 | $keys = array_keys(getInstance()->scheduler->IOCallBack[$this->requestId]);
62 | $this->ioBackKey = array_pop($keys);
63 |
64 | $this->send(function ($client) use ($profileName, $requestId) {
65 | if (empty($this->getContext()) || ($requestId != $this->getContext()->getRequestId())) {
66 | return;
67 | }
68 |
69 | if ($this->isBreak) {
70 | return;
71 | }
72 |
73 | if (empty(getInstance()->scheduler->taskMap[$this->requestId])) {
74 | return;
75 | }
76 |
77 | // 发现拒绝建立连接,删除DNS缓存
78 | if (is_object($client) && ($client->errCode == 111 || $client->statusCode == 404)) {
79 | Client::clearDnsCache($this->client->urlData['host']);
80 | $client->isClose = true;
81 | }
82 |
83 | if (is_object($client) && $client->errCode != 0) {
84 | $this->getContext()->getLog()->warning(dump($client, false, true));
85 | $client->isClose = true;
86 | }
87 |
88 | $client->ioBack = true;
89 | $this->result = (array)$client;
90 | $this->responseTime = microtime(true);
91 | $this->getContext()->getLog()->profileEnd($profileName);
92 | $this->ioBack = true;
93 | $this->nextRun();
94 | });
95 | }
96 |
97 | /**
98 | * 发送异步的HTTP请求
99 | *
100 | * @param callable $callback 请求响应的回调函数
101 | */
102 | public function send($callback)
103 | {
104 | switch ($this->method) {
105 | case 'POST':
106 | $this->client->post($this->path, $this->data, $callback);
107 | break;
108 | case 'GET':
109 | $this->client->get($this->path, $this->data, $callback);
110 | break;
111 | case 'EXECUTE':
112 | if (!empty($this->data)) {
113 | $this->client->setData($this->data);
114 | }
115 | $this->client->execute($this->path, $callback);
116 | break;
117 | }
118 | }
119 |
120 | /**
121 | * 销毁
122 | */
123 | public function destroy()
124 | {
125 | parent::destroy();
126 | }
127 | }
128 |
--------------------------------------------------------------------------------
/src/Coroutine/IBase.php:
--------------------------------------------------------------------------------
1 | mysqlAsynPool = $_mysqlAsynPool;
45 | $this->bindId = $_bind_id;
46 | $this->sql = $_sql;
47 | $this->request = $this->mysqlAsynPool->getAsynName() . '(' . str_replace("\n", " ", $_sql) . ')';
48 | $this->requestId = $this->getContext()->getRequestId();
49 | $requestId = $this->requestId;
50 |
51 | $this->getContext()->getLog()->profileStart($this->request);
52 | getInstance()->scheduler->IOCallBack[$this->requestId][] = $this;
53 | $keys = array_keys(getInstance()->scheduler->IOCallBack[$this->requestId]);
54 | $this->ioBackKey = array_pop($keys);
55 |
56 | $this->send(function ($result) use ($requestId) {
57 | if (empty($this->getContext()) || ($requestId != $this->getContext()->getRequestId())) {
58 | return;
59 | }
60 |
61 | if ($this->isBreak) {
62 | return;
63 | }
64 |
65 | if (empty(getInstance()->scheduler->taskMap[$this->requestId])) {
66 | return;
67 | }
68 |
69 | $this->getContext()->getLog()->profileEnd($this->request);
70 | $this->result = $result;
71 | $this->ioBack = true;
72 | $this->nextRun();
73 | });
74 | }
75 |
76 | /**
77 | * 发送异步的MySQL请求
78 | *
79 | * @param callable $callback 执行SQL后的回调函数
80 | */
81 | public function send($callback)
82 | {
83 | $this->mysqlAsynPool->query($callback, $this->bindId, $this->sql, $this->getContext());
84 | }
85 |
86 | /**
87 | * 获取执行结果
88 | *
89 | * @return mixed|null
90 | * @throws Exception
91 | */
92 | public function getResult()
93 | {
94 | $result = parent::getResult();
95 | if (is_array($result) && isset($result['error'])) {
96 | throw new Exception($result['error']);
97 | }
98 | return $result;
99 | }
100 |
101 | /**
102 | * 属性不用于序列化
103 | *
104 | * @return array
105 | */
106 | public function __unsleep()
107 | {
108 | return ['mysqlAsynPool'];
109 | }
110 |
111 | /**
112 | * 销毁
113 | */
114 | public function destroy()
115 | {
116 | parent::destroy();
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/Coroutine/Redis.php:
--------------------------------------------------------------------------------
1 | redisAsynPool = $redisAsynPool;
67 | $this->hashKey = $redisAsynPool->hashKey;
68 | $this->phpSerialize = $redisAsynPool->phpSerialize;
69 | $this->keyPrefix = $redisAsynPool->keyPrefix;
70 | $this->redisSerialize = $redisAsynPool->redisSerialize;
71 | $this->name = $name;
72 | $this->arguments = $arguments;
73 | $this->request = mt_rand(1, 9) . mt_rand(1, 9) . mt_rand(1, 9) . '#' . $this->redisAsynPool->getAsynName() . '.' . $name;
74 | $this->requestId = $this->getContext()->getRequestId();
75 | $requestId = $this->requestId;
76 |
77 | $this->getContext()->getLog()->profileStart($this->request);
78 | getInstance()->scheduler->IOCallBack[$this->requestId][] = $this;
79 | $keys = array_keys(getInstance()->scheduler->IOCallBack[$this->requestId]);
80 | $this->ioBackKey = array_pop($keys);
81 |
82 | $this->send(function ($result) use ($name, $requestId) {
83 | if (empty($this->getContext()) || ($requestId != $this->getContext()->getRequestId())) {
84 | return;
85 | }
86 | if ($this->isBreak) {
87 | return;
88 | }
89 |
90 | if (empty(getInstance()->scheduler->taskMap[$this->requestId])) {
91 | return;
92 | }
93 |
94 | $this->getContext()->getLog()->profileEnd($this->request);
95 |
96 | switch ($name) {
97 | case 'mget':
98 | $keys = $this->arguments[0];
99 | $len = strlen($this->keyPrefix);
100 | $result = $this->unSerializeHandler($result, $keys, $len);
101 | break;
102 | case 'eval':
103 | //如果redis中的数据本身没有进行序列化,同时返回值是json,那么解析成array
104 | $decodeVal = @json_decode($result, true);
105 | if (is_array($decodeVal)) {
106 | $result = $decodeVal;
107 | }
108 | $result = $this->unSerializeHandler($result);
109 | break;
110 | default:
111 | $result = $this->unSerializeHandler($result);
112 | }
113 |
114 | $this->result = $result;
115 | $this->ioBack = true;
116 | $this->nextRun();
117 | });
118 |
119 | return $this;
120 | }
121 |
122 | /**
123 | * 发送异步的Redis请求
124 | *
125 | * @param callable $callback Redis指令执行后的回调函数
126 | */
127 | public function send($callback)
128 | {
129 | $this->arguments[] = $callback;
130 | $this->redisAsynPool->__call($this->name, $this->arguments);
131 | }
132 |
133 | /**
134 | * 反序列化
135 | *
136 | * @param mixed $data Redis响应数据
137 | * @param array $keys Redis操作的Key列表
138 | * @param int $len 操作的数据量
139 | * @return array|bool|mixed
140 | */
141 | protected function unSerializeHandler($data, $keys = [], $len = 0)
142 | {
143 | // 如果值是null,直接返回false
144 | if (null === $data) {
145 | return false;
146 | }
147 |
148 | if ('OK' === $data) {
149 | return $data;
150 | }
151 |
152 | try {
153 | //mget
154 | if (!empty($keys) && is_array($data)) {
155 | $ret = [];
156 | array_walk($data, function ($val, $k) use ($keys, $len, &$ret) {
157 | if (!is_null($val)) {
158 | $key = substr($keys[$k], $len);
159 | $val = $this->realUnserialize($val);
160 | $ret[$key] = $val;
161 | }
162 | });
163 |
164 | $data = $ret;
165 | } elseif (is_array($data) && empty($keys)) {
166 | //eval sRandMember...
167 | $ret = [];
168 | array_walk($data, function ($val, $k) use (&$ret) {
169 | if (is_array($val)) {
170 | foreach ($val as $i => $v) {
171 | $val[$i] = $this->realUnserialize($v);
172 | }
173 | } else {
174 | $val = $this->realUnserialize($val);
175 | }
176 |
177 | $ret[$k] = $val;
178 | });
179 |
180 | $data = $ret;
181 | } else {
182 | //get
183 | $data = $this->realUnserialize($data);
184 | }
185 | } catch (\Exception $exception) {
186 | // do noting
187 | }
188 |
189 | return $data;
190 | }
191 |
192 | /**
193 | * 是否可以反序列化
194 | *
195 | * @param string $string 待反序列化的原始数据
196 | * @return bool
197 | */
198 | private function canUnserialize(string $string)
199 | {
200 | return in_array(substr($string, 0, 2), ['s:', 'i:', 'b:', 'N', 'a:', 'O:', 'd:']);
201 | }
202 |
203 | /**
204 | * 真正反序列化
205 | *
206 | * @param string $data 待反序列化的原始数据
207 | * @return mixed|string
208 | */
209 | private function realUnserialize($data)
210 | {
211 | //get
212 | if (is_string($data) && $this->redisSerialize) {
213 | switch ($this->redisSerialize) {
214 | case Macro::SERIALIZE_PHP:
215 | if ($this->canUnserialize($data)) {
216 | $data = unserialize($data);
217 | }
218 | break;
219 | case Macro::SERIALIZE_IGBINARY:
220 | $data = @igbinary_unserialize($data);
221 | break;
222 | }
223 | }
224 |
225 | if (is_string($data) && $this->phpSerialize) {
226 | switch ($this->phpSerialize) {
227 | case Macro::SERIALIZE_PHP:
228 | if ($this->canUnserialize($data)) {
229 | $data = unserialize($data);
230 | }
231 | break;
232 | case Macro::SERIALIZE_IGBINARY:
233 | $data = @igbinary_unserialize($data);
234 | break;
235 | }
236 |
237 | //兼容Yii逻辑
238 | if (is_array($data) && count($data) === 2 && array_key_exists(1, $data) && $data[1] === null) {
239 | $data = $data[0];
240 | }
241 | }
242 | return $data;
243 | }
244 |
245 | /**
246 | * 属性不用于序列化
247 | *
248 | * @return array
249 | */
250 | public function __unsleep()
251 | {
252 | return ['redisAsynPool'];
253 | }
254 |
255 | /**
256 | * 销毁
257 | */
258 | public function destroy()
259 | {
260 | parent::destroy();
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/src/Coroutine/Scheduler.php:
--------------------------------------------------------------------------------
1 | sysTimers[] = swoole_timer_tick(1000, function ($timerId) {
40 | if (empty($this->IOCallBack)) {
41 | return true;
42 | }
43 |
44 | foreach ($this->IOCallBack as $requestId => $callBacks) {
45 | foreach ($callBacks as $key => $callBack) {
46 | if ($callBack->ioBack) {
47 | continue;
48 | }
49 |
50 | if ($callBack->isTimeout()) {
51 | if (!empty($this->taskMap[$requestId])) {
52 | $this->schedule($this->taskMap[$requestId]);
53 | }
54 | }
55 | }
56 | }
57 | });
58 |
59 | /**
60 | * 每隔3600s清理对象池中的对象
61 | */
62 | swoole_timer_tick(3600000, function ($timerId) {
63 | if (!empty(getInstance()->objectPool->map)) {
64 | foreach (getInstance()->objectPool->map as $class => &$objectsMap) {
65 | while ($objectsMap->count()) {
66 | $obj = $objectsMap->shift();
67 | if ($obj instanceof Core) {
68 | $obj->setRedisPools(null);
69 | $obj->setRedisProxies(null);
70 | }
71 |
72 | if ($obj instanceof Controller) {
73 | $obj->getObjectPool()->destroy();
74 | $obj->setObjectPool(null);
75 | }
76 | $obj = null;
77 | unset($obj);
78 | }
79 | }
80 | }
81 | });
82 | }
83 |
84 | /**
85 | * 调度协程任务(请求)
86 | *
87 | * @param Task $task 协程实例
88 | * @param callable|null $callback 特殊场景回调,如清理资源
89 | * @return $this
90 | */
91 | public function schedule(Task $task, callable $callback = null)
92 | {
93 | // 特殊场景回调,如清理资源
94 | if ($callback !== null) {
95 | $task->resetCallBack($callback);
96 | }
97 |
98 | /* @var $task Task */
99 | $task->run();
100 |
101 | try {
102 | do {
103 | // 迭代器检查
104 | if (!$task->getRoutine() instanceof \Generator) {
105 | break;
106 | }
107 |
108 | // 协程异步IO
109 | if ($task->getRoutine()->valid() && ($task->getRoutine()->current() instanceof IBase)) {
110 | break;
111 | }
112 |
113 | // 继续调度
114 | if (!$task->isFinished()) {
115 | $this->schedule($task);
116 | break;
117 | }
118 |
119 | // 请求调度结束(考虑是否回调)
120 | $task->resetRoutine();
121 | if (is_callable($task->getCallBack())) {
122 | $func = $task->getCallBack();
123 | $task->resetCallBack();
124 | $func();
125 | break;
126 | }
127 | } while (0);
128 | } catch (\Throwable $e) {
129 | $task->handleTaskException($e, null);
130 | $this->schedule($task);
131 | }
132 |
133 | return $this;
134 | }
135 |
136 |
137 | /**
138 | * 开始执行调度请求
139 | *
140 | * @param \Generator $routine 待调度的迭代器实例
141 | * @param Controller $controller 当前请求控制器名称
142 | * @param callable|null $callBack 迭代器执行完成后回调函数
143 | */
144 | public function start(\Generator $routine, Controller $controller, callable $callBack = null)
145 | {
146 | $task = $controller->getObject(Task::class, [$routine, $controller, $callBack]);
147 | $requestId = $controller->getContext()->getRequestId();
148 | $this->IOCallBack[$requestId] = [];
149 | $this->taskMap[$requestId] = $task;
150 | $this->schedule($task);
151 | }
152 | }
153 |
--------------------------------------------------------------------------------
/src/Coroutine/Shell.php:
--------------------------------------------------------------------------------
1 | __command = $command;
29 | $this->requestId = $this->getContext()->getRequestId();
30 | $requestId = $this->requestId;
31 |
32 | getInstance()->scheduler->IOCallBack[$this->requestId][] = $this;
33 | $keys = array_keys(getInstance()->scheduler->IOCallBack[$this->requestId]);
34 | $this->ioBackKey = array_pop($keys);
35 |
36 | $this->send(function ($result, $status) use ($requestId) {
37 | if (empty($this->getContext()) || ($requestId != $this->getContext()->getRequestId())) {
38 | return;
39 | }
40 |
41 | if (empty(getInstance()->scheduler->taskMap[$this->requestId])) {
42 | return;
43 | }
44 |
45 | $this->result = $status === false ? false : $result;
46 | $this->ioBack = true;
47 | $this->nextRun();
48 | });
49 |
50 | return $this;
51 | }
52 |
53 | /**
54 | * 异步执行shell,并执行回调
55 | * @param callable $callback 回调函数
56 | * @return $this
57 | * @throws Exception
58 | */
59 | public function send($callback)
60 | {
61 | if (version_compare(SWOOLE_VERSION, '1.9.22', '<')) {
62 | throw new Exception('Swoole version must >= 1.9.22');
63 | }
64 | \Swoole\Async::exec($this->__command, $callback);
65 | return $this;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/src/Coroutine/Sleep.php:
--------------------------------------------------------------------------------
1 | __sleepTime = $mSec;
26 | $this->requestId = $this->getContext()->getRequestId();
27 | $requestId = $this->requestId;
28 | $this->setTimeout($mSec + 1000); //协程超时时间要比睡眠时间更长
29 |
30 | getInstance()->scheduler->IOCallBack[$this->requestId][] = $this;
31 | $keys = array_keys(getInstance()->scheduler->IOCallBack[$this->requestId]);
32 | $this->ioBackKey = array_pop($keys);
33 |
34 | $this->send(function () use ($requestId) {
35 | if (empty($this->getContext()) || ($requestId != $this->getContext()->getRequestId())) {
36 | return;
37 | }
38 |
39 | if (empty(getInstance()->scheduler->taskMap[$this->requestId])) {
40 | return;
41 | }
42 |
43 | $this->result = true;
44 | $this->ioBack = true;
45 | $this->nextRun();
46 | });
47 |
48 | return $this;
49 | }
50 |
51 | /**
52 | * 通过定时器来模拟异步IO
53 | * @param callable $callback 定时器回调函数
54 | * @return $this
55 | */
56 | public function send($callback)
57 | {
58 | swoole_timer_after($this->__sleepTime, $callback);
59 | return $this;
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/src/Coroutine/Task.php:
--------------------------------------------------------------------------------
1 | routine = $routine;
64 | $this->controller = $controller;
65 | $this->stack = new \SplStack();
66 | $this->id = $this->getContext()->getRequestId();
67 | $this->callBack = $callBack;
68 | }
69 |
70 | /**
71 | * 重置迭代器
72 | *
73 | * @param \Generator $routine 迭代器实例
74 | * @return $this
75 | */
76 | public function resetRoutine(\Generator $routine = null)
77 | {
78 | $this->routine = null;
79 | $this->routine = $routine;
80 | return $this;
81 | }
82 |
83 | /**
84 | * 获取callback
85 | *
86 | * @return mixed
87 | */
88 | public function getCallBack()
89 | {
90 | return $this->callBack;
91 | }
92 |
93 | /**
94 | * 重置callback
95 | *
96 | * @param callable|null $callBack 迭代器执行完成后回调函数
97 | * @return $this
98 | */
99 | public function resetCallBack(callable $callBack = null)
100 | {
101 | $this->callBack = null;
102 | $this->callBack = $callBack;
103 | return $this;
104 | }
105 |
106 | /**
107 | * 获取controller
108 | *
109 | * @return Controller
110 | */
111 | public function getController()
112 | {
113 | return $this->controller;
114 | }
115 |
116 | /**
117 | * 请求的协程调度
118 | */
119 | public function run()
120 | {
121 | try {
122 | if (!$this->routine) {
123 | return;
124 | }
125 |
126 | $value = $this->routine->current();
127 | //嵌套的协程
128 | if ($value instanceof \Generator) {
129 | $this->stack->push($this->routine);
130 | $this->routine = $value;
131 | $value = null;
132 | return;
133 | }
134 |
135 | if ($value != null && $value instanceof IBase) {
136 | if ($value->isTimeout()) {
137 | try {
138 | $value->throwTimeOutException();
139 | } catch (\Exception $e) {
140 | $this->handleTaskTimeout($e, $value);
141 | }
142 | unset($value);
143 | $this->routine->send(false);
144 | } else {
145 | $result = $value->getResult();
146 | if ($result !== CNull::getInstance()) {
147 | $this->routine->send($result);
148 | }
149 |
150 | while (!$this->routine->valid() && !empty($this->stack) && !$this->stack->isEmpty()) {
151 | try {
152 | $result = $this->routine->getReturn();
153 | } catch (\Throwable $e) {
154 | // not todo
155 | $result = false;
156 | }
157 | $this->routine = null;
158 | $this->routine = $this->stack->pop();
159 | $this->routine->send($result);
160 | }
161 | }
162 | } else {
163 | if ($this->routine instanceof \Generator && $this->routine->valid()) {
164 | $this->routine->send($value);
165 | } else {
166 | try {
167 | $result = $this->routine->getReturn();
168 | } catch (\Throwable $e) {
169 | // not todo
170 | $result = false;
171 | }
172 |
173 | if (!empty($this->stack) && !$this->stack->isEmpty()) {
174 | $this->routine = null;
175 | $this->routine = $this->stack->pop();
176 | }
177 | $this->routine->send($result);
178 | }
179 | }
180 | } catch (\Throwable $e) {
181 | if (empty($value)) {
182 | $value = '';
183 | }
184 |
185 | $this->handleTaskException($e, $value);
186 | unset($value);
187 | }
188 | }
189 |
190 | /**
191 | * 处理协程任务的超时
192 | *
193 | * @param \Throwable $e 异常实例
194 | * @param mixed $value 当前迭代的值
195 | * @return \Throwable
196 | */
197 | public function handleTaskTimeout(\Throwable $e, $value)
198 | {
199 | if ($value != '') {
200 | $logValue = '';
201 | dumpInternal($logValue, $value, 0, false);
202 | $message = 'Yield ' . $logValue . ' message: ' . $e->getMessage();
203 | } else {
204 | $message = 'message: ' . $e->getMessage();
205 | }
206 |
207 | $this->getContext()->getLog()->warning($message);
208 |
209 | return $e;
210 | }
211 |
212 | /**
213 | * 处理协程任务的异常
214 | *
215 | * @param \Throwable $e 异常实例
216 | * @param mixed $value 当前迭代的值
217 | * @return bool|Exception|\Throwable
218 | * @throws \Throwable
219 | */
220 | public function handleTaskException(\Throwable $userException, $value)
221 | {
222 | try {
223 | if (!empty($this->routine) && $this->routine instanceof \Generator) {
224 | $this->routine->throw($userException);
225 | }
226 | } catch (\Throwable $noCatchUserException) {
227 | if ($this->stack->isEmpty()) {
228 | try {
229 | throw $noCatchUserException;
230 | } catch (\Throwable $noCatch) {
231 | if ($this->controller) {
232 | $this->controller->onExceptionHandle($noCatch);
233 | } else {
234 | throw $noCatch;
235 | }
236 | }
237 |
238 | return true;
239 | }
240 |
241 | $noCatchException = null;
242 | while (!$this->stack->isEmpty()) {
243 | $this->routine = $this->stack->pop();
244 | try {
245 | $this->routine->throw($noCatchUserException);
246 | break;
247 | } catch (\Throwable $e) {
248 | if ($this->stack->isEmpty()) {
249 | $noCatchException = $noCatchUserException;
250 | }
251 | }
252 | }
253 |
254 | if ($this->stack->isEmpty() && $noCatchException !== null && $this->controller) {
255 | $this->controller->onExceptionHandle($noCatchException);
256 | }
257 | }
258 |
259 | return true;
260 | }
261 |
262 | /**
263 | * [isFinished 判断该task是否完成]
264 | * @return boolean [description]
265 | */
266 | public function isFinished()
267 | {
268 | return (empty($this->stack) || $this->stack->isEmpty()) && !$this->routine->valid();
269 | }
270 |
271 | /**
272 | * 获取协程任务当前正在运行的迭代器
273 | *
274 | * @return \Generator
275 | */
276 | public function getRoutine()
277 | {
278 | return $this->routine;
279 | }
280 |
281 | /**
282 | * 销毁
283 | */
284 | public function destroy()
285 | {
286 | if (!empty($this->id)) {
287 | getInstance()->scheduler->taskMap[$this->id] = null;
288 | unset(getInstance()->scheduler->taskMap[$this->id]);
289 | getInstance()->scheduler->IOCallBack[$this->id] = null;
290 | unset(getInstance()->scheduler->IOCallBack[$this->id]);
291 | $this->stack = null;
292 | $this->controller = null;
293 | $this->id = null;
294 | $this->callBack = null;
295 | }
296 | }
297 | }
298 |
--------------------------------------------------------------------------------
/src/Helpers/Common.php:
--------------------------------------------------------------------------------
1 | config->get('server.set.log_file', '');
21 | if ($logFile) {
22 | file_put_contents($logFile, $msgStr, FILE_APPEND);
23 | } else {
24 | echo $msgStr;
25 | }
26 | }
27 |
28 | /**
29 | * 获取实例
30 | * @return \PG\MSF\MSFServer|\PG\MSF\MSFCli
31 | */
32 | function &getInstance()
33 | {
34 | return \PG\MSF\Server::getInstance();
35 | }
36 |
37 | /**
38 | * 清理所有的定时器(请谨慎使用)
39 | */
40 | function clearTimes()
41 | {
42 | $timers = getInstance()->sysTimers;
43 | if (!empty($timers)) {
44 | foreach ($timers as $timerId) {
45 | swoole_timer_clear($timerId);
46 | }
47 | }
48 | swoole_event_exit();
49 | }
50 |
51 | /**
52 | * 内部打印变量
53 | *
54 | * @param string $output 打印的结果
55 | * @param mixed $var 待打印的变量
56 | * @param int $level 打印的层级,最大4层
57 | * @param bool $format 是否格式化返回,默认为true
58 | * @param bool $truncated 大字符串是否截断,默认为true
59 | */
60 | function dumpInternal(&$output, $var, $level, $format = true, $truncated = true)
61 | {
62 | switch (gettype($var)) {
63 | case 'boolean':
64 | $output .= $var ? 'true' : 'false';
65 | break;
66 | case 'integer':
67 | $output .= "$var";
68 | break;
69 | case 'double':
70 | $output .= "$var";
71 | break;
72 | case 'string':
73 | if ($truncated && defined('DUMP_TRUNCATED') && strlen($var) > 512) {
74 | $output .= "'**'";
75 | } else {
76 | $output .= "'" . addslashes($var) . "'";
77 | }
78 | break;
79 | case 'resource':
80 | $output .= '{resource}';
81 | break;
82 | case 'NULL':
83 | $output .= 'null';
84 | break;
85 | case 'unknown type':
86 | $output .= '{unknown}';
87 | break;
88 | case 'array':
89 | if (4 <= $level) {
90 | $output .= '[...]';
91 | } elseif (empty($var)) {
92 | $output .= '[]';
93 | } else {
94 | if ($format) {
95 | $spaces = str_repeat(' ', $level * 4);
96 | } else {
97 | $spaces = '';
98 | }
99 |
100 | $output .= '[';
101 | foreach ($var as $key => $val) {
102 | if ($format) {
103 | $output .= "\n" . $spaces . ' ';
104 | }
105 | dumpInternal($output, $key, 0, $format);
106 | $output .= ' => ';
107 | dumpInternal($output, $var[$key], $level + 1, $format);
108 | if (!$format) {
109 | $output .= ', ';
110 | }
111 | }
112 |
113 | if ($format) {
114 | $output .= "\n" . $spaces . ']';
115 | } else {
116 | $output .= "], ";
117 | }
118 | }
119 | break;
120 | case 'object':
121 | if ($var instanceof \swoole_http_response || $var instanceof \swoole_http_request) {
122 | $output .= get_class($var) . '(...)';
123 | break;
124 | }
125 |
126 | if ($var instanceof \Throwable) {
127 | $truncated = false;
128 | $dumpValues = [
129 | 'message' => $var->getMessage(),
130 | 'code' => $var->getCode(),
131 | 'line' => $var->getLine(),
132 | 'file' => $var->getFile(),
133 | 'trace' => $var->getTraceAsString(),
134 | ];
135 | } else {
136 | $dumpValues = (array)$var;
137 | $truncated = true;
138 | }
139 | if (method_exists($var, '__sleep')) {
140 | $sleepProperties = $var->__sleep();
141 | if (empty($sleepProperties)) {
142 | $sleepProperties = array_keys($dumpValues);
143 | }
144 | } else {
145 | $sleepProperties = array_keys($dumpValues);
146 | }
147 |
148 | if (method_exists($var, '__unsleep')) {
149 | $unsleepProperties = $var->__unsleep();
150 | } else {
151 | $unsleepProperties = [];
152 | }
153 | $sleepProperties = array_diff($sleepProperties, $unsleepProperties);
154 |
155 | if (4 <= $level) {
156 | $output .= get_class($var) . '(...)';
157 | } else {
158 | $spaces = str_repeat(' ', $level * 4);
159 | $className = get_class($var);
160 | if ($format) {
161 | $output .= "$className\n" . $spaces . '(';
162 | } else {
163 | $output .= "$className(";
164 | }
165 |
166 | $i = 0;
167 | foreach ($dumpValues as $key => $value) {
168 | if (!in_array(trim($key, "* \0"), $sleepProperties)) {
169 | continue;
170 | }
171 |
172 | if ($i >= 100) {
173 | if ($format) {
174 | $output .= "\n" . $spaces . " [...] => ...";
175 | } else {
176 | $output .= "... => ...";
177 | }
178 | break;
179 | }
180 | $i++;
181 | $key = str_replace('*', '', $key);
182 | $key = strtr(trim($key), "\0", ':');
183 | $keyDisplay = strtr(trim($key), "\0", ':');
184 | if ($format) {
185 | $output .= "\n" . $spaces . " [$keyDisplay] => ";
186 | } else {
187 | $output .= "$keyDisplay => ";
188 | }
189 | dumpInternal($output, $value, $level + 1, $format, $truncated);
190 | if (!$format) {
191 | $output .= ', ';
192 | }
193 | }
194 |
195 | if ($format) {
196 | $output .= "\n" . $spaces . ')';
197 | } else {
198 | $output .= '), ';
199 | }
200 | }
201 | break;
202 | }
203 |
204 | if (!$format) {
205 | $output = str_replace([', ,', ', ', ', )', ', ]'], [', ', ', ', ')', ']'], $output);
206 | }
207 | }
208 |
209 | /**
210 | * 打印变量
211 | *
212 | * @param mixed $var 打印的变量
213 | * @param bool $format 是否格式化
214 | * @param bool $return 是否直接返回字符串,而直接打印
215 | * @return mixed
216 | */
217 | function dump($var, $format = true, $return = false)
218 | {
219 | global $____GLOBAL_DUMP;
220 | dumpInternal($____GLOBAL_DUMP, $var, 0, $format);
221 | if (!$return) {
222 | echo $____GLOBAL_DUMP, "\n";
223 | $____GLOBAL_DUMP = '';
224 | } else {
225 | $dump = $____GLOBAL_DUMP;
226 | $____GLOBAL_DUMP = '';
227 | return $dump;
228 | }
229 | }
230 |
--------------------------------------------------------------------------------
/src/Helpers/Context.php:
--------------------------------------------------------------------------------
1 | requestId = $requestId;
68 | }
69 |
70 | /**
71 | * 返回当前请求ID
72 | *
73 | * @return int
74 | */
75 | public function getRequestId()
76 | {
77 | return $this->requestId;
78 | }
79 |
80 | /**
81 | * 获取请求输入对象
82 | *
83 | * @return Input
84 | */
85 | public function getInput()
86 | {
87 | return $this->input;
88 | }
89 |
90 | /**
91 | * 设置请求输入对象
92 | *
93 | * @param Input $input 请求对象
94 | * @return $this
95 | */
96 | public function setInput($input)
97 | {
98 | $this->input = $input;
99 | return $this;
100 | }
101 |
102 | /**
103 | * 获取请求输出对象
104 | *
105 | * @return Output
106 | */
107 | public function getOutput()
108 | {
109 | return $this->output;
110 | }
111 |
112 | /**
113 | * 设置请求输出对象
114 | *
115 | * @param Output $output 请求输出对象
116 | * @return $this
117 | */
118 | public function setOutput($output)
119 | {
120 | $this->output = $output;
121 | return $this;
122 | }
123 |
124 | /**
125 | * 获取对象池对象
126 | *
127 | * @return Pool
128 | */
129 | public function getObjectPool()
130 | {
131 | return $this->objectPool;
132 | }
133 |
134 | /**
135 | * 设置对象池对象
136 | *
137 | * @param Pool $objectPool 对象池实例
138 | * @return $this
139 | */
140 | public function setObjectPool($objectPool)
141 | {
142 | $this->objectPool = $objectPool;
143 | return $this;
144 | }
145 |
146 | /**
147 | * 设置控制器名称
148 | *
149 | * @param string $controllerName 控制器名称
150 | * @return $this
151 | */
152 | public function setControllerName($controllerName)
153 | {
154 | $this->controllerName = $controllerName;
155 | return $this;
156 | }
157 |
158 | /**
159 | * 返回控制器名称
160 | *
161 | * @return string
162 | */
163 | public function getControllerName()
164 | {
165 | return $this->controllerName;
166 | }
167 |
168 | /**
169 | * 设置方法名称
170 | *
171 | * @param string $actionName 控制器方法名
172 | * @return $this
173 | */
174 | public function setActionName($actionName)
175 | {
176 | $this->actionName = $actionName;
177 | return $this;
178 | }
179 |
180 | /**
181 | * 返回方法名称
182 | *
183 | * @return string
184 | */
185 | public function getActionName()
186 | {
187 | return $this->actionName;
188 | }
189 |
190 | /**
191 | * 获取所有用户自定义的全局上下文对象
192 | *
193 | * @return array
194 | */
195 | public function getAllUserDefined()
196 | {
197 | return $this->userDefined;
198 | }
199 |
200 | /**
201 | * 获取key所对应的用户自定义的全局上下文数据
202 | *
203 | * @param string $key 用户自定义的上下文数据Key
204 | * @return mixed|null
205 | */
206 | public function getUserDefined($key)
207 | {
208 | return $this->userDefined[$key] ?? null;
209 | }
210 |
211 | /**
212 | * 设置key所对应的用户自定义的全局上下文的value
213 | *
214 | * @param string $key 用户自定义的上下文数据Key
215 | * @param mixed $val 用户自定义的上下文数据Value
216 | * @return $this
217 | */
218 | public function setUserDefined($key, $val)
219 | {
220 | $this->userDefined[(string)$key] = $val;
221 | return $this;
222 | }
223 |
224 | /**
225 | * 属性不用于序列化
226 | *
227 | * @return array
228 | */
229 | public function __sleep()
230 | {
231 | return ['logId', 'requestId', 'input', 'controllerName', 'actionName', 'userDefined'];
232 | }
233 |
234 | /**
235 | * 销毁
236 | */
237 | public function destroy()
238 | {
239 | $this->PGLog = null;
240 | $this->input = null;
241 | $this->output = null;
242 | $this->objectPool = null;
243 | $this->controllerName = null;
244 | $this->actionName = null;
245 | $this->userDefined = [];
246 | $this->requestId = null;
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/src/MSFCli.php:
--------------------------------------------------------------------------------
1 | scheduler = new Scheduler();
38 | }
39 |
40 | /**
41 | * 命令行请求回调
42 | */
43 | public function onConsoleRequest()
44 | {
45 | $this->requestId++;
46 | parent::run();
47 | $request = new Request();
48 | $request->resolve();
49 | $this->route->handleHttpRequest($request);
50 |
51 | $PGLog = clone getInstance()->log;
52 | $PGLog->accessRecord['beginTime'] = microtime(true);
53 | $PGLog->accessRecord['uri'] = $this->route->getPath();
54 | $PGLog->logId = $this->genLogId($request);
55 | defined('SYSTEM_NAME') && $PGLog->channel = SYSTEM_NAME;
56 | $PGLog->init();
57 | $PGLog->pushLog('verb', 'cli');
58 |
59 | do {
60 | $controllerName = $this->route->getControllerName();
61 | $controllerClassName = $this->route->getControllerClassName();
62 | $methodPrefix = $this->config->get('http.method_prefix', 'action');
63 | $methodName = $methodPrefix . $this->route->getMethodName();
64 | if ($controllerClassName == '') {
65 | clearTimes();
66 | writeln("not found controller {$controllerName}");
67 | break;
68 | }
69 |
70 | /**
71 | * @var \PG\MSF\Controllers\Controller $instance
72 | */
73 | $instance = $this->objectPool->get($controllerClassName, [$controllerName, $methodName]);
74 | $instance->__useCount++;
75 | if (empty($instance->getObjectPool())) {
76 | $instance->setObjectPool(AOPFactory::getObjectPool(getInstance()->objectPool, $instance));
77 | }
78 | // 初始化控制器
79 | $instance->requestStartTime = microtime(true);
80 | if (!method_exists($instance, $methodName)) {
81 | writeln("not found method $controllerName" . "->" . "$methodName");
82 | $instance->destroy();
83 | break;
84 | }
85 |
86 | $instance->context = $instance->getObjectPool()->get(Context::class, [$this->requestId]);
87 |
88 | // 构造请求上下文成员
89 | $instance->context->setLogId($PGLog->logId);
90 | $instance->context->setLog($PGLog);
91 | $instance->context->setObjectPool($instance->getObjectPool());
92 |
93 | /**
94 | * @var $input Input
95 | */
96 | $input = $instance->getObjectPool()->get(Input::class);
97 | $input->set($request);
98 | $instance->context->setInput($input);
99 | $instance->context->setControllerName($controllerName);
100 | $instance->context->setActionName($methodName);
101 | $init = $instance->__construct($controllerName, $methodName);
102 | if ($init instanceof \Generator) {
103 | $this->scheduler->start(
104 | $init,
105 | $instance,
106 | function () use ($instance, $methodName) {
107 | $params = array_values($this->route->getParams());
108 | if (empty($this->route->getParams())) {
109 | $params = [];
110 | }
111 |
112 | $generator = $instance->$methodName(...$params);
113 | if ($generator instanceof \Generator) {
114 | $this->scheduler->taskMap[$instance->context->getRequestId()]->resetRoutine($generator);
115 | $this->scheduler->schedule(
116 | $this->scheduler->taskMap[$instance->context->getRequestId()],
117 | function () use ($instance) {
118 | $instance->destroy();
119 | }
120 | );
121 | } else {
122 | $instance->destroy();
123 | }
124 | }
125 | );
126 | } else {
127 | $params = array_values($this->route->getParams());
128 | if (empty($this->route->getParams())) {
129 | $params = [];
130 | }
131 |
132 | $generator = $instance->$methodName(...$params);
133 | if ($generator instanceof \Generator) {
134 | $this->scheduler->start(
135 | $generator,
136 | $instance,
137 | function () use ($instance) {
138 | $instance->destroy();
139 | }
140 | );
141 | } else {
142 | $instance->destroy();
143 | }
144 | }
145 | break;
146 | } while (0);
147 | }
148 |
149 | /**
150 | * 服务启动前的初始化
151 | */
152 | public function beforeSwooleStart()
153 | {
154 | // 初始化Yac共享内存
155 | $this->sysCache = new \Yac('sys_cache_');
156 |
157 | //初始化对象池
158 | $this->objectPool = Pool::getInstance();
159 | }
160 |
161 |
162 | /**
163 | * 添加异步redis,添加redisProxy
164 | *
165 | * @param \swoole_server|null $serv server实例
166 | * @param int $workerId worker id
167 | * @throws Exception
168 | */
169 | public function onWorkerStart($serv, $workerId)
170 | {
171 | $this->initAsynPools();
172 | $this->initRedisProxies();
173 | //注册
174 | $this->asynPoolManager = new AsynPoolManager(null, $this);
175 | $this->asynPoolManager->noEventAdd();
176 | foreach ($this->asynPools as $pool) {
177 | if ($pool) {
178 | $pool->workerInit($workerId);
179 | $this->asynPoolManager->registerAsyn($pool);
180 | }
181 | }
182 |
183 | //redis proxy监测
184 | getInstance()->sysTimers[] = swoole_timer_tick(5000, function () {
185 | if (!empty($this->redisProxyManager)) {
186 | foreach ($this->redisProxyManager as $proxy) {
187 | $proxy->check();
188 | }
189 | }
190 | });
191 | }
192 |
193 | /**
194 | * 设置服务器配置参数
195 | * @return mixed
196 | */
197 | public function setServerSet()
198 | {
199 | // TODO: Implement setServerSet() method.
200 | }
201 |
202 | /**
203 | * Display staring UI.
204 | *
205 | * @return void
206 | */
207 | protected static function displayUI()
208 | {
209 | // TODO
210 | }
211 |
212 | /**
213 | * Parse command.
214 | *
215 | * @return void
216 | */
217 | protected static function parseCommand()
218 | {
219 | // TODO
220 | }
221 |
222 | /**
223 | * Init.
224 | *
225 | * @return void
226 | */
227 | protected static function init()
228 | {
229 | self::setProcessTitle(self::$_worker->config->get('server.process_title') . '-console');
230 | }
231 |
232 | /**
233 | * Init All worker instances.
234 | *
235 | * @return void
236 | */
237 | protected static function initWorkers()
238 | {
239 | // TODO
240 | }
241 | }
242 |
--------------------------------------------------------------------------------
/src/Macro.php:
--------------------------------------------------------------------------------
1 | 'Master',
136 | self::PROCESS_MANAGER => 'Manager',
137 | self::PROCESS_WORKER => 'Worker',
138 | self::PROCESS_TASKER => 'Tasker',
139 | self::PROCESS_RELOAD => 'Reload',
140 | self::PROCESS_CONFIG => 'Config',
141 | self::PROCESS_TIMER => 'Timer',
142 | self::PROCESS_USER => 'User',
143 | ];
144 |
145 | /**
146 | * 发送静态文件404
147 | */
148 | const SEND_FILE_404 = 404;
149 |
150 | /**
151 | * 发送静态文件200
152 | */
153 | const SEND_FILE_200 = 200;
154 |
155 | /**
156 | * 发送静态文件304
157 | */
158 | const SEND_FILE_304 = 304;
159 |
160 | /**
161 | * 发送静态文件403
162 | */
163 | const SEND_FILE_403 = 403;
164 |
165 | /**
166 | * Timer TICK
167 | */
168 | const SWOOLE_TIMER_TICK = 'swoole_timer_tick';
169 |
170 | /**
171 | * Timer AFTER
172 | */
173 | const SWOOLE_TIMER_AFTER = 'swoole_timer_after';
174 |
175 | /**
176 | * 文件Session
177 | */
178 | const SESSION_FILE = 1;
179 | /**
180 | * redis Session
181 | */
182 | const SESSION_REDIS = 2;
183 | /**
184 | * redis proxy Session
185 | */
186 | const SESSION_REDIS_PROXY = 3;
187 | }
188 |
--------------------------------------------------------------------------------
/src/Models/Model.php:
--------------------------------------------------------------------------------
1 | callBacks = [];
100 | $this->commands = new \SplQueue();
101 | $this->pool = new \SplQueue();
102 | $this->config = $config;
103 | $this->active = $active;
104 | }
105 |
106 | /**
107 | * 注册回调
108 | *
109 | * @param callable $callback 回调函数
110 | * @return int
111 | */
112 | public function addTokenCallback($callback)
113 | {
114 | $token = $this->token;
115 | $this->callBacks[$token] = $callback;
116 | $this->token++;
117 | if ($this->token >= self::MAX_TOKEN) {
118 | $this->token = 0;
119 | }
120 |
121 | return $token;
122 | }
123 |
124 | /**
125 | * 分发消息
126 | *
127 | * @param array $data 待分发数据
128 | * @return $this
129 | */
130 | public function distribute($data)
131 | {
132 | $callback = $this->callBacks[$data['token']];
133 | unset($this->callBacks[$data['token']]);
134 | if ($callback != null) {
135 | $callback($data['result']);
136 | }
137 |
138 | return $this;
139 | }
140 |
141 | /**
142 | * 初始化
143 | *
144 | * @param MSFServer $swooleServer Server实例
145 | * @param AsynPoolManager $asynManager 异步连接池管理器
146 | * @return $this
147 | */
148 | public function serverInit($swooleServer, $asynManager)
149 | {
150 | $this->swooleServer = $swooleServer;
151 | $this->server = $swooleServer->server;
152 | $this->asynManager = $asynManager;
153 |
154 | return $this;
155 | }
156 |
157 | /**
158 | * 初始化workerId
159 | *
160 | * @param int $workerId worker进程ID
161 | * @return $this
162 | */
163 | public function workerInit($workerId)
164 | {
165 | $this->workerId = $workerId;
166 |
167 | return $this;
168 | }
169 |
170 | /**
171 | * 归还连接
172 | *
173 | * @param mixed $client 连接对象
174 | * @return $this
175 | */
176 | public function pushToPool($client)
177 | {
178 | $maxTime = $this->config[static::ASYN_NAME][$this->active]['max_time'] ?? 3600;
179 | $minConn = $this->config[static::ASYN_NAME][$this->active]['min_conn'] ?? 0;
180 |
181 | //回归连接
182 | if (((time() - $client->genTime) < $maxTime)
183 | || (($this->establishedConn + $this->waitConnectNum) <= $minConn)
184 | ) {
185 | $this->pool->push($client);
186 | if (count($this->commands) > 0) {//有残留的任务
187 | $command = $this->commands->shift();
188 | $this->execute($command);
189 | }
190 | } else {
191 | $client->close();
192 | }
193 |
194 | return $this;
195 | }
196 |
197 | /**
198 | * 创建一个连接
199 | */
200 | public function prepareOne()
201 | {
202 | $maxConn = $this->config[static::ASYN_NAME][$this->active]['max_conn'] ?? null;
203 | if ($maxConn) {
204 | if ($maxConn > ($this->waitConnectNum + $this->establishedConn)) {
205 | $this->reconnect();
206 | }
207 | } else {
208 | $this->reconnect();
209 | }
210 | }
211 |
212 | /**
213 | * 返回唯一的连接池名称
214 | *
215 | * @return string
216 | */
217 | public function getAsynName()
218 | {
219 | return self::ASYN_NAME . '.' . $this->active;
220 | }
221 |
222 | /**
223 | * 建立连接
224 | *
225 | * @param null $client
226 | * @return mixed
227 | */
228 | abstract public function reconnect($client = null);
229 |
230 | /**
231 | * 获取同步
232 | *
233 | * @return mixed
234 | */
235 | abstract public function getSync();
236 | }
237 |
--------------------------------------------------------------------------------
/src/Pools/AsynPoolManager.php:
--------------------------------------------------------------------------------
1 | process = $process;
50 | $this->swooleServer = $swooleServer;
51 | }
52 |
53 | /**
54 | * 采用额外进程的方式
55 | *
56 | * @return $this
57 | */
58 | public function eventAdd()
59 | {
60 | $this->notEventAdd = false;
61 | swoole_event_add($this->process->pipe, [$this, 'getPipeMessage']);
62 |
63 | return $this;
64 | }
65 |
66 | /**
67 | * 不采用进程通讯,每个进程都启用进程池
68 | *
69 | * @return $this
70 | */
71 | public function noEventAdd()
72 | {
73 | $this->notEventAdd = true;
74 |
75 | return $this;
76 | }
77 |
78 | /**
79 | * 算是管道消息
80 | *
81 | * @param int $pipe 管道文件描述符号
82 | * @return $this
83 | */
84 | public function getPipeMessage($pipe)
85 | {
86 | $read = $this->process->read();
87 | $data = unserialize($read);
88 | $asyn = $this->registerDir[$data['asyn_name']];
89 | $asyn->execute($data);
90 |
91 | return $this;
92 | }
93 |
94 | /**
95 | * 分发消息
96 | *
97 | * @param array $data 待分发数据
98 | * @return $this
99 | */
100 | public function distribute($data)
101 | {
102 | $asyn = $this->registerDir[$data['asyn_name']];
103 | $asyn->distribute($data);
104 |
105 | return $this;
106 | }
107 |
108 | /**
109 | * 注册异步连接池
110 | *
111 | * @param IAsynPool $asyn 连接池对象
112 | * @return $this
113 | */
114 | public function registerAsyn(IAsynPool $asyn)
115 | {
116 | $this->registerDir[$asyn->getAsynName()] = $asyn;
117 | $asyn->serverInit($this->swooleServer, $this);
118 |
119 | return $this;
120 | }
121 |
122 | /**
123 | * 写入管道
124 | *
125 | * @param IAsynPool $asyn 连接池对象
126 | * @param mixed $data 写入数据
127 | * @param int $workerId worker进程ID
128 | * @return $this
129 | */
130 | public function writePipe(IAsynPool $asyn, $data, $workerId)
131 | {
132 | if ($this->notEventAdd) {
133 | $asyn->execute($data);
134 | } else {
135 | $data['asyn_name'] = $asyn->getAsynName();
136 | $data['worker_id'] = $workerId;
137 | //写入管道
138 | $this->process->write(serialize($data));
139 | }
140 |
141 | return $this;
142 | }
143 |
144 | /**
145 | * 分发消息给worker
146 | *
147 | * @param IAsynPool $asyn 连接池对象
148 | * @param mixed $data 待分发数据
149 | * @return $this
150 | */
151 | public function sendMessageToWorker(IAsynPool $asyn, $data)
152 | {
153 | if ($this->notEventAdd) {
154 | $asyn->distribute($data);
155 | } else {
156 | $workerID = $data['worker_id'];
157 | $message = $this->swooleServer->packSerevrMessageBody(Macro::MSG_TYPR_ASYN, $data);
158 | $this->swooleServer->server->sendMessage($message, $workerID);
159 | }
160 |
161 | return $this;
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/Pools/Exception.php:
--------------------------------------------------------------------------------
1 | MSFServer->processType = Macro::PROCESS_RELOAD;
41 | $notice = 'Inotify Reload: ';
42 | $this->monitorDir = realpath(ROOT_PATH . '/');
43 | if (!extension_loaded('inotify')) {
44 | $notice .= "Failed(未安装inotify扩展)";
45 | } else {
46 | $this->inotify();
47 | $notice .= "Enabled";
48 | }
49 |
50 | writeln($notice);
51 | }
52 |
53 | /**
54 | * 监控目录
55 | */
56 | public function inotify()
57 | {
58 | $this->inotifyFd = inotify_init();
59 |
60 | stream_set_blocking($this->inotifyFd, 0);
61 | $dirIterator = new \RecursiveDirectoryIterator($this->monitorDir);
62 | $iterator = new \RecursiveIteratorIterator($dirIterator);
63 | $monitorFiles = [];
64 | $tempFiles = [];
65 |
66 | foreach ($iterator as $file) {
67 | $fileInfo = pathinfo($file);
68 |
69 | if (!isset($fileInfo['extension']) || $fileInfo['extension'] != 'php') {
70 | continue;
71 | }
72 |
73 | //改为监听目录
74 | $dirPath = $fileInfo['dirname'];
75 | if (!isset($tempFiles[$dirPath])) {
76 | $wd = inotify_add_watch($this->inotifyFd, $fileInfo['dirname'], IN_MODIFY | IN_CREATE | IN_IGNORED | IN_DELETE);
77 | $tempFiles[$dirPath] = $wd;
78 | $monitorFiles[$wd] = $dirPath;
79 | }
80 | }
81 |
82 | $tempFiles = null;
83 |
84 | swoole_event_add($this->inotifyFd, function ($inotifyFd) use (&$monitorFiles) {
85 | $events = inotify_read($inotifyFd);
86 | $flag = true;
87 | foreach ($events as $ev) {
88 | if (pathinfo($ev['name'], PATHINFO_EXTENSION) != 'php') {
89 | //创建目录添加监听
90 | if ($ev['mask'] == 1073742080) {
91 | $path = $monitorFiles[$ev['wd']] .'/'. $ev['name'];
92 |
93 | $wd = inotify_add_watch($inotifyFd, $path, IN_MODIFY | IN_CREATE | IN_IGNORED | IN_DELETE);
94 | $monitorFiles[$wd] = $path;
95 | }
96 | $flag = false;
97 | continue;
98 | }
99 | writeln('RELOAD ' . $monitorFiles[$ev['wd']] .'/'. $ev['name'] . ' update');
100 | }
101 | if ($flag == true) {
102 | $this->MSFServer->server->reload();
103 | }
104 | }, null, SWOOLE_EVENT_READ);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/src/Process/ProcessBase.php:
--------------------------------------------------------------------------------
1 | config = $config;
40 | $this->MSFServer = $MSFServer;
41 | $this->MSFServer->processType = Macro::PROCESS_USER;
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/Process/Timer.php:
--------------------------------------------------------------------------------
1 | MSFServer->processType = Macro::PROCESS_TIMER;
34 | writeln('User Timer: Enabled');
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/Proxy/Exception.php:
--------------------------------------------------------------------------------
1 | name = $name;
63 |
64 | try {
65 | if (empty($config['pools'])) {
66 | throw new Exception('pools is empty');
67 | }
68 |
69 | if (empty($config['pools']['master'])) {
70 | throw new Exception('No master mysql server in master-slave config!');
71 | }
72 | $this->master = $config['pools']['master'];
73 |
74 | if (empty($config['pools']['slaves'])) {
75 | throw new Exception('No slave mysql server in master-slave config!');
76 | }
77 | $this->slaves = $config['pools']['slaves'];
78 |
79 | $this->startCheck();
80 | } catch (Exception $e) {
81 | writeln('Mysql Proxy ' . $e->getMessage());
82 | }
83 | }
84 |
85 | /**
86 | * 用户定时检测
87 | *
88 | * @return bool
89 | */
90 | public function check()
91 | {
92 | // 暂时未实现主从自动切换
93 | return true;
94 | }
95 |
96 | /**
97 | * 获取DB Query Builder
98 | *
99 | * @param Context $context 请求上下文对象
100 | * @return Miner
101 | */
102 | public function getDBQueryBuilder(Context $context = null)
103 | {
104 | if ($this->dbQueryBuilder == null) {
105 | $this->dbQueryBuilder = new Miner();
106 | }
107 | $this->dbQueryBuilder->context = $context;
108 |
109 | return $this->dbQueryBuilder;
110 | }
111 |
112 | /**
113 | * 发送异步请求
114 | *
115 | * @param string $method 指令
116 | * @param array $arguments 指令参数
117 | * @return $this
118 | * @throws Exception
119 | */
120 | public function handle(string $method, array $arguments)
121 | {
122 | $upMethod = strtoupper($method);
123 | $context = array_shift($arguments);
124 |
125 | if (in_array($upMethod, self::$asynCommand)) {
126 | $sql = '';
127 | if ($upMethod == 'GO') {
128 | if (!empty($arguments[1])) {
129 | $sql = trim($arguments[1]);
130 | } else {
131 | $sql = $this->getDBQueryBuilder($context)->getStatement(false);
132 | }
133 | }
134 |
135 | if (!empty($sql) && empty($arguments[0]) && !empty($this->slaves)) {
136 | if (stripos($sql, 'SELECT') === 0 || stripos($sql, 'SHOW') === 0) {
137 | $rand = array_rand($this->slaves);
138 | $poolName = $this->slaves[$rand];
139 | } else {
140 | $poolName = $this->master;
141 | }
142 | } else {
143 | $poolName = $this->master;
144 | }
145 |
146 | $pool = getInstance()->getAsynPool($poolName);
147 | if ($pool == null) {
148 | throw new Exception("mysql pool $poolName not register!");
149 | }
150 | $this->getDBQueryBuilder($context)->mysqlPool = $pool;
151 | if (method_exists($this->getDBQueryBuilder(), $method)) {
152 | return $this->getDBQueryBuilder($context)->{$method}(...$arguments);
153 | } else {
154 | array_push($arguments, $context);
155 | return $this->getDBQueryBuilder($context)->mysqlPool->{$method}(...$arguments);
156 | }
157 | }
158 |
159 | $this->getDBQueryBuilder($context)->{$method}(...$arguments);
160 |
161 | return $this->__wrapper;
162 | }
163 |
164 | /**
165 | * 组装连接池
166 | *
167 | * @return $this
168 | */
169 | public function startCheck()
170 | {
171 | $masterPool = getInstance()->getAsynPool($this->master);
172 | if (!$masterPool) {
173 | $masterPool = new MysqlAsynPool(getInstance()->config, $this->master);
174 | getInstance()->addAsynPool($this->master, $masterPool, true);
175 | }
176 |
177 | foreach ($this->slaves as $slavePoolName) {
178 | $slavePool = getInstance()->getAsynPool($slavePoolName);
179 | if (!$slavePool) {
180 | $slavePool = new MysqlAsynPool(getInstance()->config, $slavePoolName);
181 | getInstance()->addAsynPool($slavePoolName, $slavePool, true);
182 | }
183 | }
184 |
185 | return $this;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/src/Proxy/RedisProxyFactory.php:
--------------------------------------------------------------------------------
1 | name = $name;
72 | $this->pools = $config['pools'];
73 | try {
74 | $this->startCheck();
75 | if (!$this->master) {
76 | throw new Exception('No master redis server in master-slave config!');
77 | }
78 |
79 | if (empty($this->slaves)) {
80 | throw new Exception('No slave redis server in master-slave config!');
81 | }
82 | } catch (Exception $e) {
83 | writeln('Redis Proxy ' . $e->getMessage());
84 | }
85 | }
86 |
87 | /**
88 | * 启动时检测Redis集群状态
89 | *
90 | * @return bool
91 | */
92 | public function startCheck()
93 | {
94 | //探测主节点
95 | foreach ($this->pools as $pool) {
96 | try {
97 | $poolInstance = getInstance()->getAsynPool($pool);
98 | if (!$poolInstance) {
99 | $poolInstance = new RedisAsynPool(getInstance()->config, $pool);
100 | getInstance()->addAsynPool($pool, $poolInstance, true);
101 | }
102 |
103 | if ($poolInstance->getSync()
104 | ->set('msf_active_master_slave_check_' . gethostname(), 1, 5)
105 | ) {
106 | $this->master = $pool;
107 | break;
108 | }
109 | } catch (\RedisException $e) {
110 | // do nothing
111 | }
112 | }
113 |
114 | //探测从节点
115 | if (count($this->pools) === 1) {
116 | $this->slaves[] = $this->master;
117 | } else {
118 | foreach ($this->pools as $pool) {
119 | $poolInstance = getInstance()->getAsynPool($pool);
120 | if (!$poolInstance) {
121 | $poolInstance = new RedisAsynPool(getInstance()->config, $pool);
122 | getInstance()->addAsynPool($pool, $poolInstance, true);
123 | }
124 |
125 | if ($pool != $this->master) {
126 | try {
127 | if ($poolInstance->getSync()
128 | ->get('msf_active_master_slave_check_' . gethostname()) == 1
129 | ) {
130 | $this->slaves[] = $pool;
131 | }
132 | } catch (\RedisException $e) {
133 | // do nothing
134 | }
135 | }
136 | }
137 | }
138 |
139 | if (empty($this->slaves)) {
140 | return false;
141 | }
142 |
143 | return true;
144 | }
145 |
146 | /**
147 | * 发送异步Redis请求
148 | *
149 | * @param string $method Redis指令
150 | * @param array $arguments Redis指令参数
151 | * @return mixed
152 | */
153 | public function handle(string $method, array $arguments)
154 | {
155 | $upMethod = strtoupper($method);
156 | //读
157 | if (in_array($upMethod, self::$readOperation) && !empty($this->slaves)) {
158 | $rand = array_rand($this->slaves);
159 | $redisPoolName = $this->slaves[$rand];
160 | } else {
161 | //写
162 | $redisPoolName = $this->master;
163 | }
164 |
165 | // EVALMOCK在指定了脚本仅读操作时,可以在从节点上执行
166 | if ($upMethod == 'EVALMOCK' && isset($arguments[4])) {
167 | if ($arguments[4]) {
168 | $rand = array_rand($this->slaves);
169 | $redisPoolName = $this->slaves[$rand];
170 | }
171 |
172 | array_pop($arguments);
173 | }
174 |
175 | if (!isset(RedisProxyFactory::$redisCoroutines[$redisPoolName])) {
176 | if (getInstance()->getAsynPool($redisPoolName) == null) {
177 | return false;
178 | }
179 | RedisProxyFactory::$redisCoroutines[$redisPoolName] = getInstance()->getAsynPool($redisPoolName)->getCoroutine();
180 | }
181 | $redisPoolCoroutine = RedisProxyFactory::$redisCoroutines[$redisPoolName];
182 |
183 | if ($method === 'cache' || $method === 'evalMock') {
184 | return $redisPoolCoroutine->$method(...$arguments);
185 | } else {
186 | return $redisPoolCoroutine->__call($method, $arguments);
187 | }
188 | }
189 |
190 | /**
191 | * 定时检测
192 | *
193 | * @return bool
194 | */
195 | public function check()
196 | {
197 | try {
198 | $this->goodPools = getInstance()->sysCache->get($this->name) ?? [];
199 |
200 | if (empty($this->goodPools)) {
201 | return false;
202 | }
203 |
204 | $newMaster = $this->goodPools['master'];
205 | $newSlaves = $this->goodPools['slaves'];
206 |
207 | if (empty($newMaster)) {
208 | writeln('Redis Proxy No master redis server in master-slave config!');
209 | throw new Exception('No master redis server in master-slave config!');
210 | }
211 |
212 | if ($this->master !== $newMaster) {
213 | $this->master = $newMaster;
214 | writeln('Redis Proxy master node change to ' . $newMaster);
215 | }
216 |
217 | if (empty($newSlaves)) {
218 | writeln('Redis Proxy No slave redis server in master-slave config!');
219 | throw new Exception('No slave redis server in master-slave config!');
220 | }
221 |
222 | $loses = array_diff($this->slaves, $newSlaves);
223 | if ($loses) {
224 | $this->slaves = $newSlaves;
225 | writeln('Redis Proxy slave nodes change to ( ' . implode(
226 | ',',
227 | $newSlaves
228 | ) . ' ), lost ( ' . implode(',', $loses) . ' )');
229 | }
230 |
231 | $adds = array_diff($newSlaves, $this->slaves);
232 | if ($adds) {
233 | $this->slaves = $newSlaves;
234 | writeln('Redis Proxy slave nodes change to ( ' . implode(
235 | ',',
236 | $newSlaves
237 | ) . ' ), add ( ' . implode(',', $adds) . ' )');
238 | }
239 |
240 | return true;
241 | } catch (Exception $e) {
242 | writeln('Redis Proxy ' . $e->getMessage());
243 | return false;
244 | }
245 | }
246 | }
247 |
--------------------------------------------------------------------------------
/src/Queue/Beanstalk.php:
--------------------------------------------------------------------------------
1 | beanstalkTask = $this->getObject(BeanstalkTask::class, [$configKey]);
41 | }
42 |
43 | /**
44 | * 入队.
45 | *
46 | * @param string $data 需要放入队列的数据.
47 | * @param string $queue Tube名字,具体请参见beanstalk文档.
48 | * @param int $delay 延迟ready的秒数,在这段时间job为delayed状态.默认不延迟
49 | *
50 | * @return CTask
51 | */
52 | public function set(string $data, string $queue = 'default', $delay = 0)
53 | {
54 | return $this->beanstalkTask->putInTube($queue, $data, null, $delay);
55 | }
56 |
57 | /**
58 | * 从队列中获取一个job,需要注意的是获取Job之后不会从队列中删除Job,需
59 | * 明确指定`$isAck=true`才会自动删除.
60 | *
61 | * @param string $queue Tube名字(默认会watch此tube).
62 | * @param boolean $isAck 是否从队列中删除,默认是.
63 | * @param int|null $timeout 取Job的超时时间,即`reserve-with-timeout`.
64 | *
65 | * @return false|Job
66 | */
67 | public function get(string $queue = 'default', $isAck = true, $timeout = null)
68 | {
69 | yield $this->beanstalkTask->watch($queue);
70 | $job = yield $this->beanstalkTask->reserve($timeout);
71 | if ($job && $isAck) {
72 | yield $this->beanstalkTask->delete($job);
73 | }
74 |
75 | return $job;
76 | }
77 |
78 | }
79 |
--------------------------------------------------------------------------------
/src/Queue/Exception.php:
--------------------------------------------------------------------------------
1 | kafka = $this->getObject(KafkaTask::class, [$configKey]);
21 | }
22 |
23 | /**
24 | * 入队
25 | * @param string $data
26 | * @param string $queue
27 | * @return \Generator
28 | */
29 | public function set(string $data, string $queue = 'default')
30 | {
31 | return yield $this->kafka->produce($data);
32 | }
33 |
34 | /**
35 | * 出队
36 | * @param string $queue
37 | * @return bool|null
38 | * @throws \RdKafka\Exception
39 | */
40 | public function get(string $queue = 'default')
41 | {
42 | $message = yield $this->kafka->consume();
43 | if (!is_object($message)) {
44 | return false;
45 | }
46 |
47 | if ($message->err === RD_KAFKA_RESP_ERR_NO_ERROR) {//正常
48 | return $message->payload;
49 | } elseif ($message->err === RD_KAFKA_RESP_ERR__PARTITION_EOF) {//暂时没有消息
50 | return null;
51 | } elseif ($message->err === RD_KAFKA_RESP_ERR__TIMED_OUT) {//超时
52 | return false;
53 | } else {
54 | throw new \RdKafka\Exception('RD_KAFKA_RESP_ERR', $message->err);//其他异常
55 | }
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Queue/RabbitMQ.php:
--------------------------------------------------------------------------------
1 | rabbit = $this->getObject(AMQPTask::class, [$configKey, $routing_key]);
26 | }
27 |
28 | /**
29 | * 入队
30 | * @param string $data
31 | * @param string $queue
32 | * @return bool
33 | */
34 | public function set(string $data, string $queue = 'default')
35 | {
36 | return $this->rabbit->publish($data, $queue);
37 | }
38 |
39 | /**
40 | * 出队
41 | * @param string $queue
42 | * @param bool $isAck
43 | * @return \AMQPEnvelope|string
44 | */
45 | public function get(string $queue = 'default', $isAck = true)
46 | {
47 | /** @var \AMQPEnvelope $AMQPEnvelope */
48 | $AMQPEnvelope = yield $this->rabbit->get($isAck);
49 | if ($AMQPEnvelope !== false) {
50 | if ($isAck == false) {
51 | $this->deliveryTag = $AMQPEnvelope->getDeliveryTag();
52 | }
53 | return $AMQPEnvelope->getBody();
54 | }
55 | return false;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Queue/Redis.php:
--------------------------------------------------------------------------------
1 | redis = $isProxy ? $this->getRedisProxy($redisName) : $this->getRedisPool($redisName);
25 | }
26 |
27 | /**
28 | * 入队
29 | * @param string $queue 队列名称
30 | * @param string $data
31 | * @return int
32 | */
33 | public function set(string $data, string $queue = 'default')
34 | {
35 | return $this->redis->rPush($queue, $data);
36 | }
37 |
38 | /**
39 | * 出队
40 | * @param string $queue 队列名称
41 | * @return string
42 | */
43 | public function get(string $queue = 'default')
44 | {
45 | return $this->redis->lPop($queue);
46 | }
47 |
48 | /**
49 | * 队列当前长度
50 | * @param string $queue
51 | * @return int
52 | */
53 | public function len(string $queue = 'default')
54 | {
55 | return $this->redis->lLen($queue);
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/Rest/Controller.php:
--------------------------------------------------------------------------------
1 | verb = $this->getContext()->getInput()->getRequestMethod();
40 | }
41 |
42 | /**
43 | * 返回当前请求是否为GET
44 | *
45 | * @return bool
46 | */
47 | public function getIsGet()
48 | {
49 | return $this->verb === 'GET';
50 | }
51 |
52 | /**
53 | * 返回当前请求是否为OPTIONS
54 | *
55 | * @return bool
56 | */
57 | public function getIsOptions()
58 | {
59 | return $this->verb === 'OPTIONS';
60 | }
61 |
62 | /**
63 | * 返回当前请求是否为HEAD
64 | *
65 | * @return bool
66 | */
67 | public function getIsHead()
68 | {
69 | return $this->verb === 'HEAD';
70 | }
71 |
72 | /**
73 | * 返回当前请求是否为POST
74 | *
75 | * @return bool
76 | */
77 | public function getIsPost()
78 | {
79 | return $this->verb === 'POST';
80 | }
81 |
82 | /**
83 | * 返回当前请求是否为DELETE
84 | *
85 | * @return bool
86 | */
87 | public function getIsDelete()
88 | {
89 | return $this->verb === 'DELETE';
90 | }
91 |
92 | /**
93 | * 返回当前请求是否为PUT
94 | *
95 | * @return bool
96 | */
97 | public function getIsPut()
98 | {
99 | return $this->verb === 'PUT';
100 | }
101 |
102 | /**
103 | * 返回当前请求是否为PATCH
104 | *
105 | * @return bool
106 | */
107 | public function getIsPatch()
108 | {
109 | return $this->verb === 'PATCH';
110 | }
111 |
112 | /**
113 | * 响应json格式数据
114 | *
115 | * @param mixed|null $data 响应数据
116 | * @param string $message 响应提示
117 | * @param int $status 响应HTTP状态码
118 | * @throws \Exception
119 | */
120 | public function outputJson($data = null, $message = '', $status = 200)
121 | {
122 | // 错误信息返回格式可参考:[https://developer.github.com/v3/]
123 | if ($status != 200 && $message !== '') {
124 | $data = [
125 | 'message' => $message
126 | ];
127 | }
128 | parent::outputJson($data, $status);
129 | }
130 |
131 | /**
132 | * 响应options请求
133 | *
134 | * @param array $options OPTIONS
135 | */
136 | public function outputOptions(array $options)
137 | {
138 | /* @var $output \PG\MSF\Base\Output */
139 | $output = $this->getContext()->getOutput();
140 | $status = 200;
141 | if ($this->verb !== 'OPTIONS') {
142 | $status = 405;
143 | }
144 | $output->setHeader('Allow', implode(', ', $options));
145 | if (!empty($output->response)) {
146 | $output->setContentType('application/json; charset=UTF-8');
147 | $output->end('', $status);
148 | }
149 | }
150 |
151 | /**
152 | * 异常的回调
153 | *
154 | * @param \Throwable $e 异常
155 | * @throws \Throwable
156 | */
157 | public function onExceptionHandle(\Throwable $e)
158 | {
159 | try {
160 | if ($e->getPrevious()) {
161 | $ce = $e->getPrevious();
162 | $errMsg = dump($ce, false, true);
163 | } else {
164 | $errMsg = dump($e, false, true);
165 | $ce = $e;
166 | }
167 |
168 | $this->getContext()->getLog()->error($errMsg);
169 | $this->outputJson(parent::$stdClass, 'Internal Server Error', 500);
170 | } catch (\Throwable $ne) {
171 | getInstance()->log->error('previous exception ' . dump($ce, false, true));
172 | getInstance()->log->error('handle exception ' . dump($ne, false, true));
173 | }
174 | }
175 | }
176 |
--------------------------------------------------------------------------------
/src/Route/Exception.php:
--------------------------------------------------------------------------------
1 | 'update', // 更新资源,如:/users/
50 | // 'DELETE {id}' => 'delete', // 删除资源,如:/users/
51 | // 'GET,HEAD {id}' => 'view', // 查看资源单条数据,如:/users/
52 | // 'POST' => 'create', // 新建资源,如:/users
53 | // 'GET,HEAD' => 'index', // 查看资源列表数据(可分页),如:/users
54 | // '{id}' => 'options', // 查看资源所支持的HTTP动词,如:/users/ | /users
55 | // '' => 'options',
56 | //];
57 |
58 | /**
59 | * Route constructor.
60 | */
61 | public function __construct()
62 | {
63 | parent::__construct();
64 | $this->initRules();
65 | }
66 |
67 | /**
68 | * HTTP请求解析
69 | *
70 | * @param \swoole_http_request $request 请求对象
71 | */
72 | public function handleHttpRequest($request)
73 | {
74 | $this->parseRequestBase($request);
75 |
76 | $data = $this->parseRule();
77 | $path = $data[0] ?? $this->routeParams->path;
78 | if ($path) {
79 | $this->parsePath($path);
80 | }
81 | if (!empty($data[1])) {
82 | foreach ($data[1] as $name => $value) {
83 | $request->get[$name] = $value;
84 | }
85 | }
86 | }
87 |
88 | /**
89 | * 解析路由规则
90 | *
91 | * @return array
92 | */
93 | public function parseRule()
94 | {
95 | if (empty($this->restRules)) {
96 | return [];
97 | }
98 | $pathInfo = $this->trimSlashes($this->routeParams->path);
99 | foreach ($this->restRules as $rule) {
100 | if (!in_array($this->routeParams->verb, $rule[0])) {
101 | continue;
102 | }
103 | if (!preg_match($rule[1][0], $pathInfo, $matches)) {
104 | continue;
105 | }
106 |
107 | $patternParams = $rule[1][1];
108 | $pathParams = $rule[1][2];
109 | $placeholders = $rule[1][3];
110 |
111 | foreach ($placeholders as $placeholder => $name) {
112 | if (isset($matches[$placeholder])) {
113 | $matches[$name] = $matches[$placeholder];
114 | unset($matches[$placeholder]);
115 | }
116 | }
117 |
118 | $params = [];
119 | $tr = [];
120 | foreach ($matches as $key => $value) {
121 | if (isset($pathParams[$key])) {
122 | $tr[$pathParams[$key]] = $value;
123 | //unset($params[$key]);
124 | $params[$key] = $value;
125 | } elseif (isset($patternParams[$key])) {
126 | $params[$key] = $value;
127 | }
128 | }
129 | $rule[2] = '/' . strtr($rule[2], $tr);
130 |
131 | return [$rule[2], $params];
132 | }
133 |
134 | return [];
135 | }
136 |
137 | /**
138 | * 初始化Rules
139 | *
140 | * @return array|mixed
141 | */
142 | protected function initRules()
143 | {
144 | $rules = getInstance()->config->get('rest.route.rules', []);
145 | if (empty($rules)) {
146 | return;
147 | }
148 |
149 | $verbs = 'GET|HEAD|POST|PUT|PATCH|DELETE|OPTIONS';
150 | foreach ($rules as $pattern => $path) {
151 | // 分离 verb 和 pattern
152 | if (preg_match("/^((?:($verbs),)*($verbs))(?:\\s+(.*))?$/", $pattern, $matches1)) {
153 | $matchVerbs = explode(',', $matches1[1]);
154 | $pattern = isset($matches1[4]) ? $matches1[4] : '';
155 | } else {
156 | $matchVerbs = [];
157 | }
158 |
159 | $patternParams = []; // pattern 里含有<>
160 | $pathParams = []; // path 里含有<>
161 | $placeholders = [];
162 |
163 | // pattern 预处理
164 | $pattern = $this->trimSlashes($pattern);
165 | if ($pattern === '') {
166 | $pattern = '#^$#u';
167 | $this->restRules[] = [
168 | $matchVerbs,
169 | [$pattern, [], [], []],
170 | $path
171 | ];
172 | continue;
173 | } else {
174 | $pattern = '/' . $pattern . '/';
175 | }
176 |
177 | // 解析如果path里面含有<>
178 | if (strpos($path, '<') !== false && preg_match_all('/<([\w._-]+)>/', $path, $matches2)) {
179 | foreach ($matches2[1] as $name) {
180 | $pathParams[$name] = "<$name>";
181 | }
182 | }
183 |
184 | // 解析如果pattern里面含有<>
185 | $tr = [
186 | '.' => '\\.',
187 | '*' => '\\*',
188 | '$' => '\\$',
189 | '[' => '\\[',
190 | ']' => '\\]',
191 | '(' => '\\(',
192 | ')' => '\\)',
193 | ];
194 | $tr2 = [];
195 | if (preg_match_all('/<([\w._-]+):?([^>]+)?>/', $pattern, $matches3, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
196 | foreach ($matches3 as $match) {
197 | $name = $match[1][0];
198 | $tmpPattern = isset($match[2][0]) ? $match[2][0] : '[^\/]+';
199 | $placeholder = 'a' . hash('crc32b', $name);
200 | $placeholders[$placeholder] = $name;
201 | $tr["<$name>"] = "(?P<$placeholder>$tmpPattern)";
202 | if (isset($pathParams[$name])) {
203 | $tr2["<$name>"] = "(?P<$placeholder>$tmpPattern)";
204 | } else {
205 | $patternParams[$name] = $tmpPattern === '[^\/]+' ? '' : "#^$tmpPattern$#u";
206 | }
207 | }
208 | }
209 | $pattern = preg_replace('/<([\w._-]+):?([^>]+)?>/', '<$1>', $pattern);
210 | $pattern = '#^' . trim(strtr($pattern, $tr), '/') . '$#u';
211 |
212 | // 组装数据
213 | $this->restRules[] = [
214 | $matchVerbs,
215 | [
216 | $pattern, // 0
217 | $patternParams, // 1
218 | $pathParams, // 2
219 | $placeholders // 3
220 | ],
221 | $path
222 | ];
223 | }
224 | }
225 |
226 | /**
227 | * 去掉下划线
228 | *
229 | * @param string $string
230 | * @return string
231 | */
232 | protected function trimSlashes($string)
233 | {
234 | if (strpos($string, '//') === 0) {
235 | return '//' . trim($string, '/');
236 | }
237 | return trim($string, '/');
238 | }
239 | }
240 |
--------------------------------------------------------------------------------
/src/Session/Adapters/File.php:
--------------------------------------------------------------------------------
1 | savePath}/{$this->sessionName}_{$sessionId}";
46 | if (file_exists($file)) {
47 | unlink($file);
48 | }
49 |
50 | return true;
51 | }
52 |
53 | /**
54 | * gc
55 | * 可全量gc或某个会话
56 | * @param int $maxLifeTime
57 | * @param string $sessionId
58 | * @return mixed
59 | */
60 | public function gc(int $maxLifeTime, string $sessionId = '')
61 | {
62 | if ($sessionId) {
63 | $file = "{$this->savePath}/{$this->sessionName}_{$sessionId}";
64 | if (file_exists($file) && (filemtime($file) + $maxLifeTime < time())) {
65 | unlink($file);
66 | }
67 | return true;
68 | }
69 |
70 | foreach (glob("{$this->savePath}/{$this->sessionName}_*") as $file) {
71 | if (file_exists($file) && (filemtime($file) + $maxLifeTime < time())) {
72 | unlink($file);
73 | }
74 | }
75 |
76 | return true;
77 | }
78 |
79 | /**
80 | * 初始化适配器
81 | * @param string $savePath session存储路径
82 | * @param string $name session前缀
83 | * @return mixed
84 | */
85 | public function open(string $savePath, string $name)
86 | {
87 | if (!is_dir($savePath)) {
88 | mkdir($savePath, 0777);
89 | }
90 | $this->savePath = $savePath;
91 | $this->sessionName = $name;
92 | return true;
93 | }
94 |
95 | /**
96 | * 读取session
97 | * @param string $sessionId 会话id
98 | * @return mixed
99 | */
100 | public function read(string $sessionId)
101 | {
102 | $file = "{$this->savePath}/{$this->sessionName}_{$sessionId}";
103 | if (!is_file($file)) {
104 | return false;
105 | }
106 |
107 | return $this->getObject(\PG\MSF\Coroutine\File::class)->goReadFile($file);
108 | }
109 |
110 | /**
111 | * 写入session
112 | * @param string $sessionId 会话id
113 | * @param string $sessionData 会话内容
114 | * @return mixed
115 | */
116 | public function write(string $sessionId, string $sessionData)
117 | {
118 | return $this->getObject(\PG\MSF\Coroutine\File::class)->goWriteFile(
119 | "{$this->savePath}/{$this->sessionName}_{$sessionId}",
120 | $sessionData
121 | );
122 | }
123 |
124 | /**
125 | * 设定session的访问和修改时间
126 | * @param string $sessionId
127 | * @return bool
128 | */
129 | public function touch(string $sessionId)
130 | {
131 | $file = "{$this->savePath}/{$this->sessionName}_{$sessionId}";
132 | if (file_exists($file)) {
133 | return touch($file, time());
134 | } else {
135 | return $this->getObject(\PG\MSF\Coroutine\File::class)->goWriteFile(
136 | "{$this->savePath}/{$this->sessionName}_{$sessionId}",
137 | '{}'
138 | );
139 | }
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/src/Session/Adapters/Redis.php:
--------------------------------------------------------------------------------
1 | isProxy = $isProxy;
44 | }
45 |
46 | /**
47 | * 关闭适配器
48 | * @return mixed
49 | */
50 | public function close()
51 | {
52 | return true;
53 | }
54 |
55 | /**
56 | * 删除当前会话
57 | * @param string $sessionId 会话id
58 | * @return mixed
59 | */
60 | public function unset(string $sessionId)
61 | {
62 | $key = "{$this->sessionName}_{$sessionId}";
63 | return $this->redis->del($key);
64 | }
65 |
66 | /**
67 | * gc
68 | * 可全量gc或某个会话
69 | * 由redis的策略控制
70 | * @param int $maxLifeTime
71 | * @param string $sessionId
72 | * @return mixed
73 | */
74 | public function gc(int $maxLifeTime, string $sessionId = '')
75 | {
76 | return true;
77 | }
78 |
79 | /**
80 | * 初始化适配器
81 | * @param string $savePath redis配置key
82 | * @param string $name session前缀
83 | * @return mixed
84 | */
85 | public function open(string $savePath, string $name)
86 | {
87 | if ($this->isProxy) {
88 | $this->redis = $this->getRedisProxy($savePath);
89 | } else {
90 | $this->redis = $this->getRedisPool($savePath);
91 | }
92 | $this->savePath = $savePath;
93 | $this->sessionName = $name;
94 | return true;
95 | }
96 |
97 | /**
98 | * 读取session
99 | * @param string $sessionId 会话id
100 | * @return mixed
101 | */
102 | public function read(string $sessionId)
103 | {
104 | $key = "{$this->sessionName}_{$sessionId}";
105 |
106 | return $this->redis->get($key);
107 | }
108 |
109 | /**
110 | * 写入session
111 | * @param string $sessionId 会话id
112 | * @param string $sessionData 会话内容
113 | * @return mixed
114 | */
115 | public function write(string $sessionId, string $sessionData)
116 | {
117 | $key = "{$this->sessionName}_{$sessionId}";
118 |
119 | return $this->redis->set($key, $sessionData, $this->getConfig()->get('session.maxLifeTime', 1440));
120 | }
121 |
122 | /**
123 | * 设定session的访问和修改时间
124 | * @param string $sessionId
125 | * @return bool
126 | */
127 | public function touch(string $sessionId)
128 | {
129 | $key = "{$this->sessionName}_{$sessionId}";
130 | if (yield $this->redis->exists($key)) {
131 | return $this->redis->expire($key, $this->getConfig()->get('session.maxLifeTime', 1440));
132 | } else {
133 | return $this->redis->set($key, '{}',
134 | $this->getConfig()->get('session.maxLifeTime', 1440));
135 | }
136 | }
137 | }
138 |
--------------------------------------------------------------------------------
/src/Session/Exception.php:
--------------------------------------------------------------------------------
1 | getConfig()->get('session.handler', Macro::SESSION_FILE);
59 | $this->savePath = $this->getConfig()->get('session.savePath', '/tmp/msf');
60 | $this->sessionName = $this->getConfig()->get('session.sessionName', 'msf_session');
61 | $this->maxLifeTime = $this->getConfig()->get('session.maxLifeTime', 1440);
62 |
63 | if ($this->sessionHandler == null) {
64 | if ($handler == Macro::SESSION_FILE) {
65 | $this->sessionHandler = $this->getObject(File::class);
66 | } elseif ($handler == Macro::SESSION_REDIS) {
67 | $this->sessionHandler = $this->getObject(Redis::class);
68 | } elseif ($handler == Macro::SESSION_REDIS_PROXY) {
69 | $this->sessionHandler = $this->getObject(Redis::class, [1]);
70 | } else {
71 | throw new Exception('不支持的Session存储类型');
72 | }
73 | }
74 |
75 | if ($this->isOpen == false) {
76 | $this->sessionHandler->open($this->savePath, $this->sessionName);
77 | }
78 | }
79 |
80 | /**
81 | * 初始化当前会话 类似session_start()
82 | * @return bool
83 | */
84 | protected function start()
85 | {
86 | //获取上下文内的sessionId
87 | $this->sessionId = $this->getContext()->getUserDefined('sessionId');
88 | if ($this->sessionId !== null) {
89 | return true;
90 | }
91 |
92 | //获取cookie里的sessionId
93 | $this->sessionId = $this->getContext()->getInput()->getCookie($this->sessionName);
94 | if (!$this->sessionId) {
95 | //新建sessionId
96 | $this->getContext()->getOutput()->setCookie($this->sessionName, $this->getContext()->getLogId());
97 | $this->sessionId = $this->getContext()->getLogId();
98 | } else {
99 | $this->sessionHandler->gc($this->maxLifeTime, $this->sessionId);
100 | }
101 |
102 | //设定session的访问和修改时间
103 | yield $this->sessionHandler->touch($this->sessionId);
104 |
105 | //设置上下文内的sessionId
106 | $this->getContext()->setUserDefined('sessionId', $this->sessionId);
107 |
108 | return true;
109 | }
110 |
111 | /**
112 | * 设置session, 支持批量
113 | * @param string | array $key 键
114 | * @param mixed $value 值
115 | * @return bool
116 | */
117 | public function set($key, $value)
118 | {
119 | //初始化会话
120 | yield $this->start();
121 |
122 | $data = yield $this->sessionHandler->read($this->sessionId);
123 | if (!$data) {
124 | $data = [];
125 | } else {
126 | $data = json_decode($data, true);
127 | }
128 |
129 | if (is_array($key)) {
130 | $data = array_merge($data, $key);
131 | } else {
132 | $data[$key] = $value;
133 | }
134 |
135 | return yield $this->sessionHandler->write($this->sessionId, json_encode($data));
136 | }
137 |
138 | /**
139 | * 获取session
140 | * @param string $key 键
141 | * @param null $default 默认值
142 | * @return mixed
143 | */
144 | public function get(string $key, $default = null)
145 | {
146 | //初始化会话
147 | yield $this->start();
148 |
149 | $data = yield $this->sessionHandler->read($this->sessionId);
150 | if (!$data) {
151 | return $default;
152 | }
153 |
154 | $data = json_decode($data, true);
155 | if (!isset($data[$key])) {
156 | return $default;
157 | }
158 |
159 | return $data[$key];
160 | }
161 |
162 | /**
163 | * 查询某一个键是否存在
164 | * @param string $key 键
165 | * @return bool
166 | */
167 | public function has(string $key)
168 | {
169 | //初始化会话
170 | yield $this->start();
171 |
172 | $data = yield $this->sessionHandler->read($this->sessionId);
173 | if (!$data) {
174 | return false;
175 | }
176 |
177 | $data = json_decode($data, true);
178 | if (!isset($data[$key])) {
179 | return false;
180 | }
181 |
182 | return true;
183 | }
184 |
185 | /**
186 | * 删除某一个键
187 | * @param string $key 键
188 | * @return bool
189 | */
190 | public function delete(string $key)
191 | {
192 | //初始化会话
193 | yield $this->start();
194 |
195 | $data = yield $this->sessionHandler->read($this->sessionId);
196 | if (!$data) {
197 | return true;
198 | }
199 |
200 | $data = json_decode($data, true);
201 | if (!isset($data[$key])) {
202 | return true;
203 | }
204 |
205 | unset($data[$key]);
206 | return yield $this->sessionHandler->write($this->sessionId, json_encode($data));
207 | }
208 |
209 | /**
210 | * 删除当前会话
211 | * @return bool
212 | */
213 | public function clear()
214 | {
215 | //初始化会话
216 | yield $this->start();
217 |
218 | return yield $this->sessionHandler->unset($this->sessionId);
219 | }
220 | }
221 |
--------------------------------------------------------------------------------
/src/Tasks/AMQPTask.php:
--------------------------------------------------------------------------------
1 | amqpConf = $amqpConf;
43 | } elseif (empty($this->amqpConf)) {
44 | throw new Exception('No $amqpConf in this class or no server config in $amqpConf');
45 | }
46 |
47 | if (!is_object($this->amqpConnection) || !$this->amqpConnection->isConnected()) {
48 | $this->prepare($this->amqpConf[0], $this->amqpConf[1] ?? 'default');
49 | }
50 |
51 | parent::__construct();
52 | }
53 |
54 | /**
55 | * 长连接
56 | * @param string $confKey
57 | * @param string $routing_key
58 | * @throws \AMQPException
59 | */
60 | public function prepare(string $confKey, string $routing_key)
61 | {
62 | $this->config = getInstance()->config['amqp'] ?? [];
63 | if (!isset($this->config[$confKey])) {
64 | throw new \AMQPException('No such a amqpMQ config ' . $confKey);
65 | }
66 | $conf = $this->config[$confKey];
67 | $this->amqpConnection = new \AMQPConnection([
68 | 'host' => $conf['host'] ?? 'localhost',
69 | 'port' => $conf['port'] ?? '5672',
70 | 'vhost' => $conf['vhost'] ?? '/',
71 | 'login' => $conf['login'] ?? 'guest',
72 | 'password' => $conf['password'] ?? 'guest',
73 | 'read_timeout' => $conf['read_timeout'] ?? 0,
74 | 'write_timeout' => $conf['write_timeout'] ?? 0,
75 | 'connect_timeout' => $conf['connect_timeout'] ?? 0,
76 | 'channel_max' => $conf['channel_max'] ?? 256,
77 | 'frame_max' => $conf['frame_max'] ?? 131072,
78 | 'heartbeat' => $conf['heartbeat'] ?? 0,
79 | 'cacert' => $conf['cacert'] ?? null,
80 | 'key' => $conf['key'] ?? null,
81 | 'cert' => $conf['cert'] ?? null,
82 | 'verify' => $conf['verify'] ?? 1
83 | ]);
84 |
85 | //Establish connection AMQP
86 | $this->amqpConnection->pconnect();
87 |
88 | //Create and declare channel
89 | $this->amqpChannel = new \AMQPChannel($this->amqpConnection);
90 |
91 | //AMQP Exchange is the publishing mechanism
92 | $this->amqpExchange = new \AMQPExchange($this->amqpChannel);
93 |
94 | //Declare Queue
95 | $this->amqpQueue = new \AMQPQueue($this->amqpChannel);
96 | $this->amqpQueue->setName($routing_key);
97 | $this->amqpQueue->setFlags(AMQP_NOPARAM);
98 | $this->amqpQueue->declareQueue();
99 | }
100 |
101 | /**
102 | * 发布消息
103 | * @param string $message
104 | * @param string $routing_key
105 | * @return bool
106 | */
107 | public function publish(string $message, string $routing_key = 'default')
108 | {
109 | return $this->amqpExchange->publish($message, $routing_key);
110 | }
111 |
112 | /**
113 | * 读取消息
114 | * @param int $autoAck 是否在MQ中清除
115 | * @return \AMQPEnvelope|bool
116 | */
117 | public function get($autoAck = 1)
118 | {
119 | if ($autoAck) {
120 | return $this->amqpQueue->get(AMQP_AUTOACK);
121 | } else {
122 | return $this->amqpQueue->get();
123 | }
124 | }
125 |
126 | /**
127 | * 消费消息
128 | * @param callable $callback 回调函数
129 | * @param int $autoAck 是否在MQ中清除
130 | */
131 | public function consume(callable $callback, $autoAck = 1)
132 | {
133 | if ($autoAck) {
134 | $this->amqpQueue->consume($callback, AMQP_AUTOACK);
135 | } else {
136 | $this->amqpQueue->consume($callback);
137 | }
138 | }
139 |
140 | /**
141 | * 告知MQ如何处理消息
142 | * 在消费未开启 autoAck 时,需要调用此方法。
143 | * @param $delivery_tag
144 | * @param int $type 1表示消费成功可以删除消息 2表示消费失败,重新放回队列 3表示消费失败,放弃处理
145 | * @return bool
146 | * @throws \AMQPQueueException
147 | */
148 | public function acknowledge($delivery_tag, $type = 1)
149 | {
150 | if ($type == 1) {
151 | //确认消息
152 | return $this->amqpQueue->ack($delivery_tag);
153 | } elseif ($type == 2) {
154 | //恢复消息
155 | return $this->amqpQueue->nack($delivery_tag);
156 | } elseif ($type == 3) {
157 | //确认取消
158 | return $this->amqpQueue->reject($delivery_tag);
159 | } else {
160 | throw new \AMQPQueueException('Undefined Acknowledge type');
161 | }
162 | }
163 | }
164 |
--------------------------------------------------------------------------------
/src/Tasks/Exception.php:
--------------------------------------------------------------------------------
1 | kafkaConfKey = $kafkaConfKey;
43 | } elseif (empty($this->kafkaConfKey)) {
44 | throw new Exception('No $kafkaConfKey in this class or no server config in $kafkaConf');
45 | }
46 |
47 | if (!is_object($this->kafkaConf)) {
48 | $this->prepare($this->kafkaConfKey);
49 | }
50 |
51 | parent::__construct();
52 | }
53 |
54 | /**
55 | * 实例化配置
56 | * @param string $confKey
57 | * @throws Exception
58 | */
59 | public function prepare(string $confKey)
60 | {
61 | $this->config = getInstance()->config['kafka'] ?? [];
62 | if (!isset($this->config[$confKey])) {
63 | throw new Exception('No such a kafka config ' . $confKey);
64 | }
65 | $this->setting = $this->config[$confKey];
66 | $this->kafkaConf = new Conf();
67 | foreach ($this->setting as $k => $value) {
68 | $this->kafkaConf->set($k, $value);
69 | }
70 | }
71 |
72 | /**
73 | * 发布消息
74 | * @param string $message
75 | * @param string $topic
76 | * @return bool
77 | */
78 | public function produce(string $message, string $topic = 'default')
79 | {
80 | if (!is_object($this->kafkaProducer)) {
81 | $this->kafkaProducer = new Producer($this->kafkaConf);
82 | $this->kafkaProducer->addBrokers($this->setting['bootstrap.servers']);
83 | }
84 | $topic = $this->kafkaProducer->newTopic($topic);
85 | $topic->produce(RD_KAFKA_PARTITION_UA, 0, $message);
86 | return true;
87 | }
88 |
89 | /**
90 | * 消费消息
91 | * @param string $topic
92 | * @return \RdKafka\Message
93 | */
94 | public function consume(string $topic = 'default')
95 | {
96 | if (!is_object($this->kafkaConsumer)) {
97 | $topicConf = new TopicConf();
98 | $topicConf->set('auto.offset.reset', 'smallest');
99 | $this->kafkaConf->setDefaultTopicConf($topicConf);
100 | $this->kafkaConsumer = new KafkaConsumer($this->kafkaConf);
101 | }
102 | $this->kafkaConsumer->subscribe([$topic]);
103 | return $this->kafkaConsumer->consume(120 * 1000);
104 | }
105 | }
106 |
--------------------------------------------------------------------------------
/src/Tasks/Task.php:
--------------------------------------------------------------------------------
1 | taskId = $taskId;
43 |
44 | if ($context) {
45 | // 构造请求上下文成员
46 | $context->setObjectPool($objectPool);
47 | $this->setContext($context);
48 | }
49 | }
50 |
51 | /**
52 | * 销毁
53 | */
54 | public function destroy()
55 | {
56 | parent::destroy();
57 | $this->taskId = 0;
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Tasks/TaskProxy.php:
--------------------------------------------------------------------------------
1 | taskConstruct = $args;
54 | parent::__construct();
55 | }
56 |
57 | /**
58 | * __call魔术方法
59 | *
60 | * @param string $name 任务方法名
61 | * @param array $arguments 执行参数
62 | * @return CTask
63 | */
64 | public function __call($name, $arguments)
65 | {
66 | $this->taskProxyData = [
67 | 'type' => Macro::SERVER_TYPE_TASK,
68 | 'message' => [
69 | 'task_name' => $this->taskName,
70 | 'task_fuc_name' => $name,
71 | 'task_fuc_data' => $arguments,
72 | 'task_context' => $this->getContext(),
73 | 'task_construct' => $this->taskConstruct,
74 | ]
75 | ];
76 |
77 | return $this->getObject(CTask::class, [$this->taskProxyData, $this->timeout]);
78 | }
79 |
80 | /**
81 | * 设置任务执行超时时间
82 | *
83 | * @param int $timeout 超时时间,单位毫秒
84 | * @return $this
85 | */
86 | public function setTimeout($timeout)
87 | {
88 | $this->timeout = $timeout;
89 | return $this;
90 | }
91 |
92 | /**
93 | * 获取任务执行超时时间
94 | *
95 | * @return int
96 | */
97 | public function getTimeout()
98 | {
99 | return $this->timeout;
100 | }
101 |
102 | /**
103 | * 异步任务
104 | *
105 | * @param callable|null $callback 任务执行完成的回调
106 | */
107 | public function startTask($callback = null)
108 | {
109 | getInstance()->server->task($this->taskProxyData, -1, $callback);
110 | }
111 |
112 | /**
113 | * 销毁
114 | */
115 | public function destroy()
116 | {
117 | $this->taskProxyData = null;
118 | parent::destroy();
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/Views/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PHP-MSF
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
--------------------------------------------------------------------------------