如何使用WebSocket开发一套系统(三. 编写全局行为日志、抽象类、调用方案)

515 阅读1分钟

为了记录用户全局行为日志,我需要额外编写一个中间件

  • 文件路径 app/Middleware/OperateLog.php
<?php  
  
namespace App\Middleware;  
  
use App\Constants\Websocket;  
use Carbon\Carbon;  
use Hyperf\DbConnection\Db;  
use Hyperf\WebSocketServer\Context;  
  
class OperateLog  
{  
    // 写入日志
    public function autoCatch(string $controller, string $action, string $req): int  
    {  
        $class = new $controller;  
        $class->setComment();  
        $class->setMethodsComment();  
        $comment = $class->comment ?? '未知模块';  
        $actionComment = $class->methodsComment[$action] ?? '未知操作';  
        return Db::table('global_operate_log')->insertGetId([  
            'module' => $controller,  
            'module_name' => $comment,  
            'action' => $action,  
            'action_name' => $actionComment,  
            'operator' => Context::get(Websocket::MANAGER_UID) ?? 0,  
            'request' => $req,  
            'response' => '',  
            'created_at' => Carbon::now()->toDateTimeString()  
        ]);  
    }  
    // 追加响应值
    public function autoUpdate(int $id, string $response): void  
    {  
        Db::table('hsmf_operate_log')->where('id', $id)->update([  
            'response' => $response,  
            'updated_at' => Carbon::now()->toDateTimeString()  
        ]);  
    }  
}
  • 将中间件应用到ws中,app/Controller/WebSocketController.phponMessageBase方法的部分代码
if (method_exists($class, $method)) {  
    $operateId = $this->operate->autoCatch($class, $method, Json::encode($params));  

    $respond = (new $class)->{$method}($server, $fd, $params) ?? [];  
    $this->send($server, $fd, $this->outputWithOrigin($payload, $respond));  

    $this->operate->autoUpdate($operateId, Json::encode($respond));  
}else{  
    $this->send($server, $fd, $this->outputWithOrigin($payload, $this->fail(ErrorCode::FUNCTION_ERROR)));  
}
  • 为了记录用户的行为,需要为类与方法增加注释,于是我新增了一个抽象类app/ControllerAbstractController.php,这个类需要每个控制器继承,它同时注入了验证器与响应器
<?php  
  
namespace App\Controller;  
  
use App\Traits\SocketResponse;  
use Hyperf\Di\Annotation\Inject;  
use Hyperf\Validation\Contract\ValidatorFactoryInterface;  
  
abstract class AbstractController  
{  
    /**  
    * 注入 Websocket 的响应方法  
    */  
    use SocketResponse;  
    /**  
    * @Inject()  
    * @var ValidatorFactoryInterface  
    */  
    protected ValidatorFactoryInterface $validatorFactory;  
    /**  
    * 对子类的注释,便于记录全局日志  
    * @var string  
    */  
    public string $comment = '';  
    /**  
    * 对子类方法的描述,便于记录全局日志  
    * @var array  
    */  
    public array $methodsComment = [];  

    /**  
    * 类注释  
    * @return void  
    */  
    abstract public function setComment():void;  

    /**  
    * 类中的方法注释  
    * @return void  
    */  
    abstract public function setMethodsComment():void;  
}
  • 使用示例app/Controller/TestController.php
<?php  
declare(strict_types=1);  
namespace App\Controller;  
  
use App\Constants\ErrorCode;  
  
class TestController extends AbstractController  
{  
    public function setComment(): void  
    {  
        $this->comment = '测试类';  
    }  

    public function setMethodsComment(): void  
    {  
        $this->methodsComment = [  
        'list' => '获取测试类数据的全部列表',  
        'create' => '创建一条测试类数据',  
        ];  
    }  

    public function list($server, $fd, $params): array  
    {  
        $validator = $this->validatorFactory->make(  
            $params,  
            [  
            'foo' => 'required',  
            'bar' => 'required',  
            ]  
        );  

        if ($validator->fails()){  
            $errorMessage = $validator->errors()->first();  
            return $this->fail(ErrorCode::PARAMS_INVALID, $errorMessage); 
        }  
        return $this->success();  
    }  

    public function create($server, $fd, $params): array  
    {  
        return $this->success();  
    }  
}
  • 如此一来,这套websocket方案基本上已经完成了,我们编写路由与调用方法做一次尝试

  • 路由文件 config/routes.php

Router::addServer('ws', function () {  
    Router::get('/ws/', 'App\Controller\WebSocketController');  
});
  • 调用示例(注:由于我是mac,我可以在phpstorm中新建一个http文件用来测试ws连接,其它系统可以找一个在线工具或者自己编写一个js的socket客户端)
###  
WEBSOCKET ws://0.0.0.0:9502/ws/  
Sec-WebSocket-Protocol: eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpYXQiOjE2ODMxNjk5MjMsIm5iZiI6MTY4MzE2OTkyMywiZXhwIjoxNjgzMjU2MzIzLCJkYXRhIjp7InVpZCI6MTExfX0.B-nvMMMrUusv8rhYGaliVksDVlpMGCcAitQaoyOkpJc  
  
{  
    "class": "test",  
    "function": "list",  
    "params": {  
        "foo": "bar"  
    }  
}
  • 得到如下响应
{"err_no":10002,"err_msg":"bar 字段是必须的","result":[],"origin":{"class":"test","function":"list","params":{"foo":"bar"}}}