首先,新建一个注册类,在这个类中,写用户注册的具体实现。
<?php
namespace app\services;
use pool\pool;
class LoginService extends CommonService
{
public function __construct($server) {
$this->server = $server;
parent::__construct();
}
public function register($frame, $params) {
try {
//注册 一级和二级topic不能为空。
if (!$params['first_topic'] || !$params['second_topic']) {
throw new \Exception("当前用户的分组不存在,请重试!");
}
if (!$params['user_id']) {
throw new \Exception("用户ID不存在,请重试!");
}
$result=$this->saveToRedis($frame,$params);
//TODO 插入redis出错处理
if(!$result){
throw new \Exception('插入redis出错,请重试!');
}
$this->notify();
} catch (\Exception $e) {
$this->result['status'] = 1;
$this->result['msg'] = $e->getMessage();
echo json_encode($this->result,JSON_UNESCAPED_UNICODE);
}
return $this->result;
}
/**
* 用户断开后的操作
* @param $fd int ws的fd标识
*/
public static function close($fd) {
}
private function saveToRedis($frame,$params){
//存入组-用户中
$rk = str_replace(['first_topic', 'second_topic'], [$params['first_topic'], $params['second_topic']], self::redis_key_group_user);
$user_list = [];
if (pool::redis()->exists($rk)) {
$user_list = explode(",", pool::redis()->get($rk));
}
$user_list[] = $params['user_id'];
pool::redis()->set($rk, implode(",", array_unique(array_filter($user_list))));
//存入 用户-组
$rk = str_replace('fd', $frame->fd, self::redis_key_user_group);
pool::redis()->set($rk, $params['first_topic'] . "_" . $params['second_topic']);
//用户对应的fd
pool::redis()->hSet(self::user_bind_redis_key, $frame->fd, $params['user_id']);
//fd对应的user
pool::redis()->hSet(self::fd_bind_user_redis_key, $params['user_id'], $frame->fd);
return true;
}
}
在这个类中,并没有写通知的具体内容,只是首先需要继承一下CommonService,在CommonService中继承了PushEventGenerator这个类,所以在LoginService中可以直接调用PushEventGenerator的notify方法。
这样做的好处是,只要有了通知,在调用notify方法之后,就会自动一条条去调用通知,而无需在业务代码中写通知的具体实现。
<?php
namespace app\services;
use app\services\push\PushEventGenerator;
use pool\pool;
use services\LogService;
/**
* Class CommonService
* @package app\services
*/
class CommonService extends PushEventGenerator
{
const redis_key_group_user = 'ws_topic_first_topic_second_topic';//分组下对应的用户
const redis_key_user_group = 'ws_user_fd';//用户对应的分组
const user_bind_redis_key = "ws_user_bind_fd_redis_key";//fd绑定用户的redis_key fd=》user
const fd_bind_user_redis_key = "ws_fd_bind_user_redis_key";//用户绑定fd的redis_key user=>fd
const redis_expire_time=86400;
public $result = ['status' => 0, 'msg' => 'success', 'data' => []];
public $server;
public function __construct() {
}
}
最后,我们回到MessageController中,在getMessage中,判断用户发送信息的类型。
public function getMessage(\swoole_websocket_frame $frame)
{
$data=json_decode($frame->data,true);
switch($data['type']){
case 'login':
$loginService = new LoginService($this->server);
//注入推送给自己注册成功的方法;
$pushtoselfstatus = new PushToSelfRegisterInfo($this->server, '注册成功', [$frame->fd]);
$loginService->addPushObServer($pushtoselfstatus);
//注入推送给所有人 “xxx进入房间”的消息。
//拼装消息体
$msgService=new MessageService();
$data=$msgService->getMessageContent($data,0);
$userService = new UserService();
$fds = $userService->getFdByGroup($data['first_topic'], $data['second_topic']);
$pushToAll = new PushToAllMessage($this->server,$data,$fds);
$loginService->addPushObServer($pushToAll);
$result = $loginService->register($frame, $data);
if($result['status']!=0){
echo json_encode($result,JSON_UNESCAPED_UNICODE) ."\n";
}
break;
}
}
如果type是‘login’,那么就可以直接调用LoginService中的方法。
在调用之前,注入PushToSelfRegisterInfo和PushToAllMessage的类,这样在登录完成之后,就可以直接完成推送通知的需求了。
我们打开两个测试工具,分别输入10086和10087这两个用户,可以看到,如下的结果
结果如图所示:
OK,下一步我们就可以进行收发消息的开发了。