祝賀 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 協議》,轉載必須註明作者和本文連結