├── .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 | --------------------------------------------------------------------------------