感觉Hyperf虽然网上资料很少,但是文档写得比较详细,而且还有对应的视频教程,比较好上手
像ws转发,这种看下文档基本也能自己实现,主要要用到的组件有WebSocket 服务,WebSocket 协程客户端,自定义进程
先创建一个普通的WebSocket 应用,详细步骤可参照官方文档
这里要主要要提到的是把等下需要转发的信息先缓存到REDIS里面,附上代码:
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\Contract\OnCloseInterface;
use Hyperf\Contract\OnMessageInterface;
use Hyperf\Contract\OnOpenInterface;
use Swoole\Http\Request;
use Swoole\Server;
use Swoole\Websocket\Frame;
use Swoole\WebSocket\Server as WebSocketServer;
use Hyperf\Utils\ApplicationContext;
class WebSocketController implements OnMessageInterface, OnOpenInterface, OnCloseInterface
{
public function onMessage(WebSocketServer $server, Frame $frame): void
{
$container = ApplicationContext::getContainer();
$redis = $container->get(\Redis::class);
$getData = $frame->data;
$getData = json_decode($getData,true);
if(isset($getData['type'])){
$type = $getData['type'];
// echo $type;
$roomId = "All";
switch ($type) {
case 'sub':
$redis->sAdd('roomInfo_'.$roomId, $frame->fd);
$redis->expire('roomInfo_'.$roomId, 7200);
break;
case 'unsub':
$redis->sRem('roomInfo_'.$roomId, $frame->fd);
break;
case 'login':
if(isset($getData['user_id']) && $getData['user_id']){
$uid = $getData['user_id'];
$server->bind($frame->fd,$uid);
$result = $this->rpcService->setinfo($uid,['is_online'=>1]);
}
break;
case 'logout':
if(isset($getData['user_id']) && $getData['user_id']){
$uid = $getData['user_id'];
// $redis->sRem('online_list',$uid);
$result = $this->rpcService->setinfo($uid,['is_online'=>0]);
}
break;
default:
# code...
break;
}
}
// $server->push($frame->fd, 'Recv: ' . $frame->data);
}
public function onClose(Server $server, int $fd, int $reactorId): void
{
$container = ApplicationContext::getContainer();
$redis = $container->get(\Redis::class);
$result = $server->getClientInfo($fd);
if(isset($result['uid']) && $result['uid']){
$uid = $result['uid'];
$result = $this->rpcService->setinfo($uid,['is_online'=>0]);
}
// echo $fd.' Closed';
}
public function onOpen(WebSocketServer $server, Request $request): void
{
// $server->push($request->fd, $request->fd.' Connected');
}
}
然后创建一个自定义进程ScoketProcess并创建客户端对象,如何添加自定义进程和ws客户端均参照官方文档
以下附上代码(注:代码因为去掉了一些业务逻辑,可能会出现一点问题,但是按这个逻辑经测是可行的):
<?php
declare(strict_types=1);
namespace App\Process;
use App\Model\Model;
use Hyperf\Process\AbstractProcess;
use Hyperf\Process\Annotation\Process;
use Hyperf\WebSocketClient\ClientFactory;
use Hyperf\WebSocketClient\Frame;
use Hyperf\Server\Server;
use Hyperf\Server\ServerFactory;
use Hyperf\Utils\ApplicationContext;
/**
* @Process(name="scoket_process")
*/
class ScoketProcess extends AbstractProcess
{
public function handle(): void
{
$api = config("shadow.api");
$host = $api['url'];
//获取授权TOKEN
$url = $host.'/login?account='.$api['username'].'&&passwd='.$api['password'];
$response = GetData($url,'',"GET");
$json = json_decode((string)$response,true);
if(!$json) return;
if($json['code'] != '200') return;
if($json['code'] == '200'){
$Model = new Model();
$container = ApplicationContext::getContainer();
$redis = $container->get(\Redis::class);
$token = $json['token'];
$parmas = "token=".$token;
//获取WS地址和端口
$url = $host.'/getsign?'.$parmas;
$response = GetData($url,'',"GET");
$json = json_decode($response,true);
if(!$json) return;
if($json['code'] != '200') return;
$ClientFactory = new ClientFactory;
$data = $json["data"];
// 对端服务的地址,如没有提供 ws:// 或 wss:// 前缀,则默认补充 ws://
$host = $data['ip'].':'.$data['port']."?sign=".$data['sign'];
// echo $host.PHP_EOL;
// 通过 ClientFactory 创建 Client 对象,创建出来的对象为短生命周期对象
$client = $ClientFactory->create($host);
// 向 WebSocket 服务端发送消息
// 获取服务端响应的消息,服务端需要通过 push 向本客户端的 fd 投递消息,才能获取;以下设置超时时间 2s,接收到的数据类型为 Frame 对象。
while (true) {
/** @var Frame $msg */
$msg = $client->recv(2);
if($msg){
// var_dump($msg->data);
if(isset($msg->data)){
$data = json_decode($msg->data,true);
if(isset($data['type'])){
switch ($data['type']) {
case 'do':
foreach ($data['data'] as $key => $value) {
$update = [
'status' => $value['status'],
'utime' => time()
];
$result = $Model->where('mid',$value['rid'])->update($update);
$update['mid'] = $value['rid'];
}
//此处重点注释下,获取服务
$server = $this->container->get(ServerFactory::class)->getServer()->getServer();
$fdList = $redis->sMembers('roomInfo_'.$value['rid']);
foreach($fdList as $v){
if(!empty($v)){
$result = $server->push((int)$v, json_encode($update));
if(!$result){
$redis->sRem('roomInfo_'.$value['rid'], $v);
}
}
}
break;
default:
break;
}
}
}
}
}
}
}
// public function isEnable(): bool
// {
// // 不跟随服务启动一同启动
// return false;
// }
}