为了记录用户全局行为日志,我需要额外编写一个中间件
- 文件路径
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.php中onMessageBase方法的部分代码
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"}}}