up. gateway 更多的代码和优化内容

This commit is contained in:
扶桑花间 2025-08-29 22:00:07 +08:00
parent 0f163c519d
commit 67bbaecac6
13 changed files with 507 additions and 128 deletions

View File

@ -2,7 +2,7 @@
namespace app\command\admin;
use app\event\AdminGatewayEvents;
use app\event\SysGatewayEvent;
use GatewayWorker\Register;
use think\console\Command;
use think\console\Input;
@ -52,7 +52,7 @@ class SysGateway extends Command
$worker->name = 'AdminBusinessWorker';
$worker->count = 4;
$worker->registerAddress = '127.0.0.1:1236';
$worker->eventHandler = AdminGatewayEvents::class;
$worker->eventHandler = SysGatewayEvent::class;
// 运行所有服务
Worker::runAll();

View File

@ -0,0 +1,45 @@
<?php
namespace app\entity\gateway\el;
class ElNotification
{
public string $title = '';
public string $message = '';
public bool $dangerouslyUseHTMLString = false;
public string $type;
public ?string $icon;
public int $duration;
public string $position;
public function __construct(
array $_attr = [],
string $title = '',
string $message = '',
bool $dangerouslyUseHTMLString = false,
string $type = '',
string $icon = null,
int $duration = 4500,
string $position = 'top-right',
)
{
$this->title = $title;
$this->message = $message;
$this->dangerouslyUseHTMLString = $dangerouslyUseHTMLString;
$this->type = $type;
$this->icon = $icon;
$this->duration = $duration;
$this->position = $position;
if (!empty($_attr)) {
foreach ($_attr as $key => $value) {
$this->$key = $value;
}
}
}
public function toArray(): array
{
return get_object_vars($this);
}
}

View File

@ -0,0 +1,35 @@
<?php
namespace app\entity\gateway\ws;
use app\entity\gateway\el\ElNotification;
use app\enum\gateway\WsEventEnum;
use app\enum\gateway\WsTypeEnum;
class OutMessage
{
private string $type;
private string $event;
private array $data;
public function __construct(WsTypeEnum $type, WsEventEnum $event, mixed $data = [])
{
$this->type = $type->value;
$this->event = $event->value;
if($data instanceof ElNotification) {
$this->data = $data->toArray();
}else{
$this->data = (array)$data;
}
}
public function toJson(): string
{
return json_encode([
'type' => $this->type,
'event' => $this->event,
'data' => $this->data,
], JSON_UNESCAPED_UNICODE);
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace app\entity\gateway\ws;
use app\enum\gateway\WsEventEnum;
use app\enum\gateway\WsTypeEnum;
/**
* Ws消息入参
*/
class PutMessage
{
readonly public WsTypeEnum $type;
readonly public WsEventEnum $event;
readonly public array $data;
public string $wsClientId;
public int $userId = 0;
public string $userType = '';
public array $userClient = [];
public function __construct(string $ws_client_id, string $type, string $event, array $data = [])
{
$this->wsClientId = $ws_client_id;
$this->type = WsTypeEnum::from($type);
$this->event = WsEventEnum::from($event);
$this->data = $data;
}
public function get($name, $default = null)
{
return $this->data[$name] ?? $default;
}
public function login(int $user_id, string $user_type, array $user_client = []): bool
{
if (empty($user_id) || empty($user_type) || count($user_client) == 0) {
return false;
}
$this->userId = $user_id;
$this->userType = $user_type;
$this->userClient = $user_client;
return true;
}
public function getClientId(): string
{
return $this->userClient['id'] ?? '';
}
public function getClientName()
{
return $this->userClient['name'] ?? '';
}
public function getClientVersion()
{
return $this->userClient['version'] ?? '';
}
}

View File

@ -0,0 +1,17 @@
<?php
namespace app\enum\gateway;
/**
* Ws事件组类型
*/
enum WsEventEnum: string
{
case Login = 'login';
case Login_SUCCESS = 'login_success';
case Lock_Client = 'lock_client';
case Client_quit_room = 'client_quit_room';
case Notification = 'notification';
}

View File

@ -0,0 +1,13 @@
<?php
namespace app\enum\gateway;
/**
* Ws消息组类型
*/
enum WsTypeEnum: string
{
case System = 'system';
case Ping = 'ping';
case Pong = 'pong';
}

View File

@ -1,5 +1,6 @@
<?php
// 事件定义文件
use app\subscribe\GatewaySubscribe;
use app\subscribe\SysUserSubscribe;
return [
@ -15,6 +16,10 @@ return [
],
'subscribe' => [
'sysUser' => SysUserSubscribe::class
'sysUser' => SysUserSubscribe::class,
/*
* 网关事件订阅类
*/
'gateway' => GatewaySubscribe::class,
],
];

View File

@ -1,122 +0,0 @@
<?php
namespace app\event;
/**
* 用于检测业务代码死循环或者长时间阻塞等问题
* 如果发现业务卡死可以将下面declare打开去掉//注释并执行php start.php reload
* 然后观察一段时间workerman.log看是否有process_timeout异常
*/
//declare(ticks=1);
/**
* 聊天主逻辑
* 主要是处理 onMessage onClose
*/
use \GatewayWorker\Lib\Gateway;
class AdminGatewayEvents
{
public static function onMessage(string $client_id, string $message):void
{
var_dump($message);
// 客户端传递的是json数据
$message_data = json_decode($message, true);
if (!$message_data) {
return;
}
$message_data = array_merge([
'type' => '',
'event'=> '',
], $message_data);
// 根据类型执行不同的业务
switch ($message_data['type']) {
// 客户端回应服务端的心跳
case 'pong':
return;
case 'system':
switch ($message_data['event']) {
// 客户端登录 message格式: {type:login, name:xx, room_id:1} 添加到客户端广播给所有客户端xx进入聊天室
case 'login':
// 判断是否有房间号
if (!isset($message_data['room_id'])) {
throw new \Exception("\$message_data['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']} \$message:$message");
}
// 把房间号昵称放到session中
$room_id = $message_data['room_id'];
$client_name = htmlspecialchars($message_data['client_name']);
$_SESSION['room_id'] = $room_id;
$_SESSION['client_name'] = $client_name;
// 获取房间内所有用户列表
$clients_list = Gateway::getClientSessionsByGroup($room_id);
foreach ($clients_list as $tmp_client_id => $item) {
$clients_list[$tmp_client_id] = $item['client_name'];
}
$clients_list[$client_id] = $client_name;
// 转播给当前房间的所有客户端xx进入聊天室 message {type:login, client_id:xx, name:xx}
$new_message = array('type' => $message_data['type'], 'client_id' => $client_id, 'client_name' => htmlspecialchars($client_name), 'time' => date('Y-m-d H:i:s'));
Gateway::sendToGroup($room_id, json_encode($new_message));
Gateway::joinGroup($client_id, $room_id);
// 给当前用户发送用户列表
$new_message['client_list'] = $clients_list;
Gateway::sendToCurrentClient(json_encode($new_message));
return;
// 客户端发言 message: {type:say, to_client_id:xx, content:xx}
case 'say':
// 非法请求
if (!isset($_SESSION['room_id'])) {
throw new \Exception("\$_SESSION['room_id'] not set. client_ip:{$_SERVER['REMOTE_ADDR']}");
}
$room_id = $_SESSION['room_id'];
$client_name = $_SESSION['client_name'];
// 私聊
if ($message_data['to_client_id'] != 'all') {
$new_message = array(
'type' => 'say',
'from_client_id' => $client_id,
'from_client_name' => $client_name,
'to_client_id' => $message_data['to_client_id'],
'content' => "<b>对你说: </b>" . nl2br(htmlspecialchars($message_data['content'])),
'time' => date('Y-m-d H:i:s'),
);
Gateway::sendToClient($message_data['to_client_id'], json_encode($new_message));
$new_message['content'] = "<b>你对" . htmlspecialchars($message_data['to_client_name']) . "说: </b>" . nl2br(htmlspecialchars($message_data['content']));
Gateway::sendToCurrentClient(json_encode($new_message));
return;
}
$new_message = array(
'type' => 'say',
'from_client_id' => $client_id,
'from_client_name' => $client_name,
'to_client_id' => 'all',
'content' => nl2br(htmlspecialchars($message_data['content'])),
'time' => date('Y-m-d H:i:s'),
);
Gateway::sendToGroup($room_id, json_encode($new_message));
return;
}
return;
}
}
public static function onClose(string $client_id)
{
var_dump(['client_id' => $client_id, 'r'=>'CLOSET']);
// // 从房间的客户端列表中删除
// if (isset($_SESSION['room_id'])) {
// $room_id = $_SESSION['room_id'];
// $new_message = array('type' => 'logout', 'from_client_id' => $client_id, 'from_client_name' => $_SESSION['client_name'], 'time' => date('Y-m-d H:i:s'));
// Gateway::sendToGroup($room_id, json_encode($new_message));
// }
}
}

View File

@ -0,0 +1,164 @@
<?php
namespace app\event;
/**
* 用于检测业务代码死循环或者长时间阻塞等问题
* 如果发现业务卡死可以将下面declare打开去掉//注释并执行php start.php reload
* 然后观察一段时间workerman.log看是否有process_timeout异常
*/
//declare(ticks=1);
use app\entity\gateway\ws\OutMessage;
use app\entity\gateway\ws\PutMessage;
use app\entity\SysRole;
use app\entity\SysUser;
use app\enum\gateway\WsEventEnum;
use app\enum\gateway\WsTypeEnum;
use app\service\admin\LoginService;
use app\service\GatewayClientService;
use think\exception\ValidateException;
use think\facade\Event;
class SysGatewayEvent
{
public static function onMessage(string $client_id, string $message): void
{
// 1. 客户端应该传递标准的json数据
$message_data = json_decode($message, true);
if (!$message_data) {
return;
}
// 2. 封装进来的消息体
$put = new PutMessage(
$client_id,
isset($message_data['type']) ? (string)$message_data['type'] : '',
isset($message_data['event']) ? (string)$message_data['event'] : '',
isset($message_data['data']) ? (array)$message_data['data'] : []
);
/*
* 客户端登录
*/
if ($put->type == WsTypeEnum::System && $put->event == WsEventEnum::Login) {
self::authEvent($put);
return;
}
$isLogin = $put->login($_SESSION['user_id'] ?? 0,
$_SESSION['user_type'] ?? '',
$_SESSION['client'] ?? []
);
if (!$isLogin) {
echo "用户未登录\r\n";
// 未登录
return;
}
// 3. 调度: 根据类型执行不同的业务
switch ($put->type) {
// 客户端回应服务端的心跳
case WsTypeEnum::Pong:
break;
// 系统类型组操作
case WsTypeEnum::System:
switch ($put->event) {
case WsEventEnum::Lock_Client:
/**
* 触发后续其他事件
*/
Event::trigger("gateway.lockClient", [$put]);
break;
}
return;
}
}
public static function onClose(string $client_id)
{
echo "客户端:{$client_id},关闭了\r\n";
// 客户端离线的相关逻辑操作
if (isset($_SESSION['group_list']) && is_array($_SESSION['group_list'])) {
foreach ($_SESSION['group_list'] as $group) {
/*
* 客户端退出了某个房间
*/
GatewayClientService::sendToGroup($group, new OutMessage(
WsTypeEnum::System,
WsEventEnum::Client_quit_room,
));
}
}
/**
* 触发后续其他事件
*/
Event::trigger("gateway.clientClose", [$client_id]);
}
private static function authEvent(PutMessage $put): void
{
$token = $put->get('token', '');
$clientVersion = $put->get('clientVersion', '');
$clientName = $put->get('clientName', '');
$clientId = $put->get('clientId', '');
try {
$token = str_replace('Bearer ', '', $token);
$loginSrv = new LoginService();
$auth = $loginSrv->checkUserAccessToken($token);
} catch (ValidateException $e) {
// 校验失败
var_dump('E1.登录凭证校验失败: '.$e->getMessage());
return;
}
/**
* 加入同角色房间
*/
$sysUser = SysUser::find($auth->userId);
if ($sysUser) {
var_dump('E1.用户数据不存在');
return;
}
// 登录
$put->login($auth->userId, $auth->userType->value, [
'id' => $clientId,
'name' => $clientName,
'version' => $clientVersion
]);
/**
* 记录登录凭证信息到$SESSION中
*/
$_SESSION['user_id'] = $put->userId;
$_SESSION['user_type'] = $put->userType;
$_SESSION['client'] = [
'id' => $put->getClientId(),
'name' => $put->getClientName(),
'version' => $put->getClientVersion()
];
/**
* 推送登录成功的消息到客户端
*/
GatewayClientService::sendToClient($put->wsClientId, new OutMessage(
WsTypeEnum::System,
WsEventEnum::Login_SUCCESS,
['userId' => $auth->userId]
));
/**
*
*/
GatewayClientService::bindUid($put->wsClientId, $auth->userId);
/**
* 加入已登录的用户公共大厅
*/
GatewayClientService::joinGroup($put->wsClientId, 'PUBLIC_ROOM');
/**
* 加入同角色的房间
*/
$sysUser->roles->each(function (SysRole $role) use ($put) {
GatewayClientService::joinGroup($put->wsClientId, "ROLE_{$role['id']}");
});
/**
* 触发后续其他事件
*/
Event::trigger("gateway.clientLogin", [$put, $sysUser]);
}
}

View File

@ -0,0 +1,52 @@
<?php
namespace app\service;
use app\entity\gateway\ws\OutMessage;
use GatewayClient\Gateway;
class GatewayClientService
{
/**
* 向某个client_id对应的连接发消息
* @param string $clientId
* @param OutMessage $message
* @return void
*/
public static function sendToClient(string $clientId, OutMessage $message)
{
Gateway::sendToClient($clientId, $message->toJson());
}
/**
* 客户端加入某个组
* @param string $clientId
* @param string $group
* @return void
*/
public static function joinGroup(string $clientId, string $group)
{
Gateway::joinGroup($clientId, $group);
$group_list = $_SESSION['group_list'] ?? [];
$group_list[] = $group;
$_SESSION['group_list'] = $group_list;
}
/**
* 将client_id与uid 绑定
* @param string $clientId
* @param int $uid
* @return void
*/
public static function bindUid(string $clientId, int $uid)
{
Gateway::bindUid($clientId, $uid);
}
public static function sendToGroup($group, OutMessage $message, $exclude_client_id = null, $raw = false)
{
Gateway::sendToGroup($group, $message->toJson(), $exclude_client_id, $raw);
}
}

View File

@ -0,0 +1,110 @@
<?php
namespace app\subscribe;
use app\entity\gateway\el\ElNotification;
use app\entity\gateway\ws\OutMessage;
use app\entity\gateway\ws\PutMessage;
use app\entity\SysUser;
use app\entity\SysUserClient;
use app\entity\SysUserClientLog;
use app\enum\gateway\WsEventEnum;
use app\enum\gateway\WsTypeEnum;
use app\service\GatewayClientService;
use think\db\exception\DataNotFoundException;
use think\db\exception\DbException;
use think\db\exception\ModelNotFoundException;
/**
* 网关事件订阅类
*/
class GatewaySubscribe
{
/**
* Ws客户端登录
* @param PutMessage $put
* @param SysUser $sysUser
* @return void
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function onClientLogin(PutMessage $put, SysUser $sysUser): void
{
$client = SysUserClient::where([
'client_id' => $put->getClientId(),
'client_name'=> $put->getClientName(),
'user_id' => $put->userId,
])->find();
/*
* 判断客户端是否锁定状态
*/
if($client && $client['is_lock']) {
// 通知客户端锁定屏幕
GatewayClientService::sendToClient($put->wsClientId,new OutMessage(
WsTypeEnum::System,
WsEventEnum::Lock_Client,
));
}
/*
* 客户端登录通知
*/
$ip = $_SERVER['REMOTE_ADDR'] ?? '';
GatewayClientService::sendToClient($put->wsClientId, new OutMessage(
WsTypeEnum::System,
WsEventEnum::Notification,
new ElNotification(title: '登录成功,欢迎您', message: "当前登录Ip: $ip"),
));
}
/**
* Ws客户端锁定
* @param PutMessage $put
* @return void
* @throws DataNotFoundException
* @throws DbException
* @throws ModelNotFoundException
*/
public function onLockClient(PutMessage $put): void
{
$client = SysUserClient::where([
'client_id' => $put->getClientId(),
'client_name'=> $put->getClientName(),
'user_id' => $put->userId,
])->find();
if($client) {
// 加密传递的锁屏密码
$lock_password = password_hash($put->get('password',''), PASSWORD_DEFAULT);
// 保存锁屏信息
$client->save([
'is_lock'=>1,
'lock_password'=>$lock_password,
'lock_time'=>date('Y-m-d H:i:s')
]);
// 保存锁屏日志
SysUserClientLog::create([
'event' => "{$put->type->value}.{$put->event->value}",
'message'=> "A. 锁定客户端",
'data' => json_encode(['inputPass'=> $lock_password]),
'create_time' => date('Y-m-d H:i:s'),
'client_data_id' => $client['id']
]);
// 通知客户端锁定屏幕
GatewayClientService::sendToClient($put->wsClientId,new OutMessage(
WsTypeEnum::System,
WsEventEnum::Lock_Client,
));
}
}
/**
* Ws客户端连接被关闭
* @return void
*/
public function onClientClose()
{
}
}

View File

@ -1,6 +1,7 @@
# 开发环境接口地址
#VITE_API_URL=https://v2.eleadmin.com/api
VITE_API_URL=http://a.tcp.run/adminapi
VITE_LICENSE=dk9mcwJyetRWQlxWRiojIiwiIzVHbQ5Wa6ICdjVmaiV3ciQWaiwCN3YDNW9ERolFcMJiOpNnclZnIsIyViQjLxIiOi42bQf0NW==
# 禁请求加密
VITE_SKIP_REQUEST_ENCRYPTION=0
# 数据加密key

View File

@ -8,8 +8,8 @@ import i18n from './i18n';
import installer from './as-needed';
import { iconsInstaller } from '@/components/IconSelect/util';
import { WsConfig, WsPlugin } from '@/plugins/websocket';
import "@/plugins/notification"
import "@/plugins/service-worker"
import '@/plugins/notification';
import '@/plugins/service-worker';
import 'element-plus/theme-chalk/display.css';
import 'ele-admin-plus/es/style/nprogress.scss';
import './styles/themes/rounded.scss';
@ -21,7 +21,7 @@ const app = createApp(App);
// WebSocket配置
const websocketConfig: Partial<WsConfig> = {
url: 'ws://139.155.146.146:19980', // 你的WebSocket服务器地址
url: 'ws://139.155.146.146:19981', // 你的WebSocket服务器地址
reconnectAttempts: 10,
reconnectDelay: 5000,
autoConnect: true // 应用启动时自动连接