workerman拆分events文件,实现常规编程习惯
wokerman的gateway很好用,自动解决了扩展性,分布式等较底层的东西。
所有的逻辑代码只需要专注于events.php文件就行了,但是作者对于events编程的介绍比较少,如果从业多年的老手自然知道怎么去搭建自己喜欢的框架。
新入门朋友还需要摸索一下。
下面我们来拆分一下events.php,实现mvc框架编程的丝滑体验。。
首先看看目录结构
这里我们引入了一个thinkphp的orm
composer require "topthink/think-orm"
有轮子当然用啊,没有我们再造,还有一个东西,redis缓存就需要我们自己造了
我把redis放在App\Rdb.php下面
<?php /** * Author:陈杰 * Blog:http://blog.95shouyou.com * Email:823380606@qq.com * Git:https://gitee.com/chen95 * Date:2020/12/22 0022 * Time:10:07 */ namespace App\Library; class Rdb { private $redis; private static $_instance = null; //定义单例模式的变量 public static function getInstance() { if (empty(self::$_instance)) { self::$_instance = new self(); } return self::$_instance; } private function __construct() { $this->redis = new \Redis(); $redis = $this->redis->connect('127.0.0.1', 8379); if ($redis === false) { throw new \Exception('redis connect error'); } $this->redis->auth("********"); $this->redis->select(2); } public function get($key, $default = false) { $data = $this->redis->get($key); return $data ?? $default; } public function set($key, $data, $ttl) { $this->redis-> $this->redis->set($key, $data, $ttl); } public function del($key) { return $this->redis->del($key); } public function query($command, $parameters) { return $this->redis->$command(...$parameters); } public static function __callStatic($method, $parameters) { return (new static())->{$method}(...$parameters); } /** * 防止clone多个实例 */ private function __clone() { } /** * 防止反序列化 */ private function __wakeup() { } }
把redis做成单例,因为这个workerman是常驻内存的,所以用一个单例来维护redis连接。
当然这里我们用得比较低级,如果出现连接断开的问题怎么解决还没实现,感兴趣的可以自行百度连接池。
这样我们的作为curd男孩的两大必备的工具就用了,orm,redis。
接下来就是拆分了
看看events.php
<?php /** * 用于检测业务代码死循环或者长时间阻塞等问题 * 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload * 然后观察一段时间workerman.log看是否有process_timeout异常 */ //declare(ticks=1); use App\Service\MemberService; use \GatewayWorker\Lib\Gateway; use think\facade\Db; class Events { use \App\Library\Tools; public static function onWorkerStart() { Db::setConfig([ // 默认数据连接标识 'default' => 'mysql', // 数据库连接信息 'connections' => [ 'mysql' => [ // 数据库类型 'type' => 'mysql', // 主机地址 'hostname' => '127.0.0.1', // 用户名 'username' => 'root', //密码 'password' => '*********', // 数据库名 'database' => 'table', // 数据库编码默认采用utf8 'charset' => 'utf8', // 数据库调试模式 'debug' => false, ], ], ]); } public static function onConnect($client_id) { } /** * type值 * 1 ping 心跳 返回1 * 2 bind 绑定 返回2 * 101 interview_member_join_room 集合室-用户进入集合室 * 102 interview_member_down 集合室-用户坐下座位 * 103 interview_member_up 集合室-用户离开座位 * 400 错误 返回400 */ public static function onMessage($client_id, $message) { try { $message = json_decode($message, true); if (!is_array($message)) return Gateway::sendToCurrentClient(self::error(400, '参数类型错误')); if ($message['type'] > 2 and !isset($_SESSION['member_id'])) { return Gateway::closeClient(self::error(400, '未登录')); } switch ($message['type']) { case 101: // doSomeThing break; case 2: MemberService::bind($client_id, $message); break; default : return Gateway::sendToCurrentClient(self::success(1)); break; } } catch (\Exception $exception) { return Gateway::sendToCurrentClient(self::error(400, '参数类型错误')); } } public static function onClose($client_id) { } }
现在我们的events.php就类似于我们的laravel thinkphp中的index.php了,就是一个入口文件,并且是带路由的。
当然这里有更好的实现,比如你可以添加一些前置中间件这些都行。
我这里就做了一个简单的实现,就是把所有的操作全部定义为type,对照我们的文档自行就行路由方法。
接下来看看我们的MemberService吧
<?php /** * Author:陈杰 * Blog:http://blog.95shouyou.com * Email:823380606@qq.com * Git:https://gitee.com/chen95 * Date:2020/12/21 0021 * Time:16:17 */ namespace App\Service; use GatewayWorker\Lib\Gateway; use think\facade\Db; class MemberService extends BaseService { public static function bind($client_id, $message) { //绑定用户 $token = $message['data']['token']; $authorizationInfo = explode(":", openssl_decrypt(base64_decode($token), 'DES-EDE3' , self::$key, OPENSSL_RAW_DATA)); if (!is_array($authorizationInfo) || count($authorizationInfo) < 2 || $authorizationInfo[0] < 1) return Gateway::sendToCurrentClient(self::error(2, '登录参数错误')); $user = Db::table('member_token')->where('member_id', $authorizationInfo[0]) ->field('member_id,token')->find(); $member = Db::table('member')->find($user['member_id']); if (!isset($user)) return Gateway::sendToCurrentClient(self::error(2, '没有找到该用户')); if ($user['token'] !== $authorizationInfo[1]) return Gateway::sendToCurrentClient(self::error(2, '登录验证错误')); Gateway::bindUid($client_id, $authorizationInfo[0]); $_SESSION['member'] = $member; $_SESSION['token'] = $user; $_SESSION['member_id'] = $member['id']; return Gateway::sendToCurrentClient(self::success(2)); } }
我们还在Library中用到了一个Tools的trait
看看代码
<?php /** * Author:陈杰 * Blog:http://blog.95shouyou.com * Email:823380606@qq.com * Git:https://gitee.com/chen95 * Date:2020/12/21 0021 * Time:16:49 */ namespace App\Library; trait Tools { public static $key = "*********************"; public static function success(int $type = 1, $data = [], $msg = '成功', $code = 200) { $error = [ 'type' => $type, 'msg' => $msg, 'code' => $code, 'data' => $data ]; return json_encode($error, 256); } //类型错误 public static function error(int $type = 400, $msg = '错误', $code = 400, $data = []) { $error = [ 'type' => $type, 'msg' => $msg, 'code' => $code, 'data' => $data ]; return json_encode($error, 256); } }
注意一下命名空间,这样实际上已经实现了我们的Events.php的拆分了,变成了我们比较常见的MVC编程的习惯。
把Events变成入口文件,不同的路由分别路由到不同的文件中进行业务处理。
当然还可以做得更完善,比如连接池,比如异常处理,比如中间件,比如验证器,等等等等。
最后还有最重要的一步要做,利用composer的自动加载把我们的写的代码加载进去
{ "name": "workerman/gateway-worker-demo", "keywords": [ "distributed", "communication" ], "homepage": "http://www.workerman.net", "license": "MIT", "require": { "workerman/gateway-worker": ">=3.0.0", "topthink/think-orm": "^2.0" }, "autoload": { "psr-4": { "App\\": "App/" }, "classmap": [ "App/Library", "App/Service" ] } }
主要是autoload下面的这几行,加载我们刚刚写的App目录下的文件,不加载的话会提示找不到类哦。
