用 hyperf websocket 实现,类似 qq 单机登录功能
祝贺 hyperf 社区开通!!!
期盼已久的社区终于开通了! ≧◉◡◉≦
功能描述
如果你用过 qq,当你在A手机上登录后,然后在B手机登录。A会被强制下线。这种功能在很多系统中是非常有必要的,比如极客时间最近加入了 这种功能。
具体实现
实现的思路很多,我们选一种最简单的,websocket 实现。以下是流程,本来是画了图的,不知道咋上传!
- 登录请求----> 查询是否登录 -->未登录,缓存信息----> success
- 登录请求----> 查询是否登录 -->已登录,更新缓存强制缓存中的 fd 下线 -----> success
请求携带参数格式
可以直接参数拼接,在握手中间件中验证token,onopen事件中验证fd
- ws://127.0.0.1;9502?token=121212&&channel=pc
hyperf 具体实现 需要的知识
具体核心代码
<?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 Hyperf\Utils\ApplicationContext;
class BuildUserController implements OnMessageInterface, OnOpenInterface, OnCloseInterface
{
public function onMessage(Server $server, Frame $frame): void
{
$server->push($frame->fd, 'Recv: ' . $frame->data);
}
public function onClose(Server $server, int $fd, int $reactorId): void
{
}
public function onOpen(Server $server, Request $request): void
{
$fd = $request->fd;//当前用户fd
$token = $request->get['token'];//token
$channel = $request->get['channel'];//登陆渠道
echo '当前用户fd ===' . $fd;
//获取缓存数据
$container = ApplicationContext::getContainer();
$redis = $container->get(\Redis::class);
$userData = $redis->get($token);
if ($userData) {
$user = json_decode($userData, true);
if (isset($user['loginInfo']) && !empty($user['loginInfo'])) {
echo '当前账号已登录过' . PHP_EOL;
var_dump($user['loginInfo']) . PHP_EOL;
//让已经存在的连接强制下线
$loginInfo = $user['loginInfo'];
//强制下线提示
$close = json_encode([
'code' => 3000,
'message' => 'Your account is landing in another place',
]);
switch ($channel) {
case 'pc':
if (isset($loginInfo['pc']) && !empty($loginInfo['pc'])) {
echo 'PC强制下线' . PHP_EOL;
var_dump($loginInfo['pc']) . PHP_EOL;
if ($server->exist($loginInfo['pc'])) {
echo '下线fd ==' . $loginInfo['pc'] . PHP_EOL;
$server->push($loginInfo['pc'], $close);
$server->close($loginInfo['pc']);
//缓存用户信息
$user['loginInfo']['pc'] = $fd;
LoginController::setCacheUsers($token, $user);
}
}
break;
case 'phone':
if (isset($loginInfo['phone']) && !empty($loginInfo['phone'])) {
if ($server->exist($loginInfo['pc'])) {
$server->push($request->fd, $close);
$server->close($loginInfo['phone']);
$user['loginInfo']['phone'] = $fd;
LoginController::setCacheUsers($token, $user);
}
}
break;
}
} else {
echo '没有缓存' . PHP_EOL;
//没登陆过直接缓存
$user['loginInfo'][$channel] = $fd;
var_dump($user) . PHP_EOL;
LoginController::setCacheUsers($token, $user);
$server->push($request->fd, 'onopen success');
}
}
}
}
以上代码是 websocket 协程客户端代码。在 onopen事件中做。还需要一个登陆控制器。登陆成功后缓存用户信息。
<?php
declare(strict_types=1);
namespace App\Controller;
use Hyperf\HttpServer\Annotation\AutoController;//注解
use App\Model\UserInfo;
use think\facade\Validate;
use Hyperf\Utils\ApplicationContext;
/**
* @AutoController()
*/
class LoginController extends Controller
{
public function loginDoing()
{
$validate = Validate::rule([
'username' => 'require',
'password' => 'require',
]);
if (!$validate->check($this->request->all())) {
return $this->error($validate->getError());
}
$userData = UserInfo::query()
->where('username', '=', $this->request->input('username'))
->first();
if ($userData) {
if ($userData['password'] === $this->request->input('password')) {
$token = self::getToken($this->request->input('username'));
//缓存用户信息
$userData = [
'username' => $userData['username'],
'uid' => $userData['uid'],
'nickname' => $userData['nickname'],
];
self::setCacheUsers($token, $userData);
return $this->success(['token' => $token], '登陆成功');
}
}
return $this->error('账号或者密码错误');
}
/**
* 获取唯一的 token
* @param $username 用户名
* @return string
*/
protected static function getToken($username): string
{
return md5(uniqid() . $username);
}
/**
* 缓存用户信息
* @param string $token
* @param array $userData
*/
public static function setCacheUsers(string $token, array $userData)
{
$container = ApplicationContext::getContainer();
$redis = $container->get(\Redis::class);
$redis->set($token, json_encode($userData), 30000);
}
}
以上代码仅为本人凭空猜测,具体实现我也不知道
本作品采用《CC 协议》,转载必须注明作者和本文链接
本帖由系统于 5年前 自动加精
这手速 :scream:
第一个帖子送不锈钢脸盆(手动滑稽)
比我还快。。
看来是早有准备
有社区了.
hyperf威武
大佬大佬 :kissing_heart: