ws新增
This commit is contained in:
@@ -53,6 +53,7 @@ class ResourceService
|
|||||||
$config['order']['id'] = 'asc';
|
$config['order']['id'] = 'asc';
|
||||||
break;
|
break;
|
||||||
case 'menuTreeList':
|
case 'menuTreeList':
|
||||||
|
$config['where']['system_id'] = ['in', [0, $p['system_id']]];
|
||||||
$config['where']['status'] = 1;
|
$config['where']['status'] = 1;
|
||||||
$config['order']['sort'] = 'asc';
|
$config['order']['sort'] = 'asc';
|
||||||
$config['order']['id'] = 'asc';
|
$config['order']['id'] = 'asc';
|
||||||
|
|||||||
@@ -0,0 +1,221 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace app\common\utils;
|
||||||
|
|
||||||
|
use GatewayWorker\Lib\Gateway;
|
||||||
|
use app\common\utils\WsResponse;
|
||||||
|
|
||||||
|
class WsEvents
|
||||||
|
{
|
||||||
|
|
||||||
|
private $registerAddress = '127.0.0.1:1236';
|
||||||
|
protected $clientId;
|
||||||
|
protected $eventData;
|
||||||
|
|
||||||
|
public function __construct()
|
||||||
|
{
|
||||||
|
Gateway::$registerAddress = $this->registerAddress;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 监听消息接收
|
||||||
|
*/
|
||||||
|
public function message($client_id, $message)
|
||||||
|
{
|
||||||
|
$this->clientId = $client_id;
|
||||||
|
|
||||||
|
$payload = json_decode($message, true);
|
||||||
|
$event = $payload['event'];
|
||||||
|
$this->eventData = $payload['data'] ?? [];
|
||||||
|
$traceId = $payload['trace_id'] ?? null;
|
||||||
|
|
||||||
|
$method = $this->eventToMethod($event);
|
||||||
|
|
||||||
|
if (method_exists($this, $method)) {
|
||||||
|
try {
|
||||||
|
$this->$method($traceId);
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
Gateway::sendToClient($client_id, WsResponse::error(
|
||||||
|
$event,
|
||||||
|
500,
|
||||||
|
$th->getMessage(),
|
||||||
|
$traceId
|
||||||
|
));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Gateway::sendToClient($client_id, WsResponse::error(
|
||||||
|
$event,
|
||||||
|
404,
|
||||||
|
"Event handler not found: $method",
|
||||||
|
$traceId
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ================= 系统事件 =================
|
||||||
|
/**
|
||||||
|
* 认证
|
||||||
|
*/
|
||||||
|
public function auth($client_id, $payload)
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
// 检查来源
|
||||||
|
if (!isset($payload['server']['HTTP_ORIGIN'])) {
|
||||||
|
flg('[x] [TOKEN认证] 未定义HTTP_ORIGIN', false);
|
||||||
|
Gateway::sendToClient($client_id, WsResponse::error(
|
||||||
|
'system.auth',
|
||||||
|
1001,
|
||||||
|
'[x] [TOKEN认证] 未定义HTTP_ORIGIN'
|
||||||
|
));
|
||||||
|
Gateway::closeClient($client_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// // 连接是否满足条件
|
||||||
|
// if ($payload['server']['HTTP_ORIGIN'] != 'https://wstool.js.org') {
|
||||||
|
// flg('[x] [TOKEN认证] HTTP_ORIGIN不满足条件', false);
|
||||||
|
// Gateway::sendToClient($client_id, WsResponse::error(
|
||||||
|
// 'system.auth',
|
||||||
|
// 1001,
|
||||||
|
// '[x] [TOKEN认证] HTTP_ORIGIN不满足条件'
|
||||||
|
// ));
|
||||||
|
// Gateway::closeClient($client_id);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
// 签名检查
|
||||||
|
if (!isset($payload['get']['token'])) {
|
||||||
|
flg('[x] [TOKEN认证] 未携带TOKEN参数', false);
|
||||||
|
Gateway::sendToClient($client_id, WsResponse::error(
|
||||||
|
'system.auth',
|
||||||
|
1001,
|
||||||
|
'[x] [TOKEN认证] 未携带TOKEN参数'
|
||||||
|
));
|
||||||
|
Gateway::closeClient($client_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解析TOKEN
|
||||||
|
$user = (new Jwt())->verifyToken($payload['get']['token']);
|
||||||
|
// 绑定用户ID
|
||||||
|
Gateway::bindUid($client_id, $user->uid);
|
||||||
|
|
||||||
|
// 响应
|
||||||
|
Gateway::sendToClient($client_id, WsResponse::success(
|
||||||
|
'system.auth',
|
||||||
|
['uid' => $user->uid]
|
||||||
|
));
|
||||||
|
} catch (\Throwable $th) {
|
||||||
|
flg('[x] [TOKEN认证] TOKEN认证失败:'.$th->getMessage(), false);
|
||||||
|
Gateway::sendToClient($client_id, WsResponse::error(
|
||||||
|
'system.auth',
|
||||||
|
1001,
|
||||||
|
'[x] [TOKEN认证] TOKEN认证失败:'.$th->getMessage()
|
||||||
|
));
|
||||||
|
Gateway::closeClient($client_id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 系统心跳
|
||||||
|
*
|
||||||
|
* @param string|null $traceId
|
||||||
|
*/
|
||||||
|
public function systemPing(?string $traceId)
|
||||||
|
{
|
||||||
|
Gateway::sendToClient($this->clientId, WsResponse::success(
|
||||||
|
'system.pong',
|
||||||
|
['time' => time()],
|
||||||
|
$traceId
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// ================= 消息通知事件 =================
|
||||||
|
/**
|
||||||
|
* 管理端主动推送通知
|
||||||
|
* data = [
|
||||||
|
* 'to_uids' => [uid1, uid2], // 可选,为空表示全员
|
||||||
|
* 'title' => '标题',
|
||||||
|
* 'content' => '内容',
|
||||||
|
* ]
|
||||||
|
*/
|
||||||
|
public function notifyPush(?string $traceId)
|
||||||
|
{
|
||||||
|
$title = $this->eventData['title'] ?? null;
|
||||||
|
$content = $this->eventData['content'] ?? null;
|
||||||
|
$toUids = $this->eventData['to_uids'] ?? [];
|
||||||
|
|
||||||
|
if (!$title || !$content) {
|
||||||
|
Gateway::sendToClient($this->clientId, WsResponse::error(
|
||||||
|
'notify.push',
|
||||||
|
400,
|
||||||
|
'title and content are required',
|
||||||
|
$traceId
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$msgId = rand(1000, 9999); // 生成消息ID,可用数据库存储
|
||||||
|
|
||||||
|
$payload = WsResponse::success('notify.push', [
|
||||||
|
'msg_id' => $msgId,
|
||||||
|
'title' => $title,
|
||||||
|
'content' => $content,
|
||||||
|
'created_at' => time()
|
||||||
|
], $traceId);
|
||||||
|
|
||||||
|
if (!empty($toUids)) {
|
||||||
|
// 推送给指定用户
|
||||||
|
foreach ($toUids as $uid) {
|
||||||
|
Gateway::sendToUid($uid, $payload);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 推送给全体在线用户
|
||||||
|
Gateway::sendToAll($payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复发送者确认
|
||||||
|
Gateway::sendToClient($this->clientId, WsResponse::success(
|
||||||
|
'notify.push.result',
|
||||||
|
['msg_id' => $msgId],
|
||||||
|
$traceId
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户端标记通知已读
|
||||||
|
* data = ['msg_ids' => [1,2,3]]
|
||||||
|
*/
|
||||||
|
public function notifyRead(?string $traceId)
|
||||||
|
{
|
||||||
|
$msgIds = $this->eventData['msg_ids'] ?? [];
|
||||||
|
if (empty($msgIds)) {
|
||||||
|
Gateway::sendToClient($this->clientId, WsResponse::error(
|
||||||
|
'notify.read',
|
||||||
|
400,
|
||||||
|
'msg_ids required',
|
||||||
|
$traceId
|
||||||
|
));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Gateway::sendToClient($this->clientId, WsResponse::success(
|
||||||
|
'notify.read.result',
|
||||||
|
['msg_ids' => $msgIds],
|
||||||
|
$traceId
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
// ================= 辅助方法 =================
|
||||||
|
protected function eventToMethod(string $event): string
|
||||||
|
{
|
||||||
|
$parts = explode('.', $event);
|
||||||
|
$method = array_shift($parts);
|
||||||
|
foreach ($parts as $part) {
|
||||||
|
$method .= ucfirst($part);
|
||||||
|
}
|
||||||
|
return $method;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,47 @@
|
|||||||
|
<?php
|
||||||
|
namespace app\common\utils;
|
||||||
|
|
||||||
|
class WsResponse
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* 成功返回
|
||||||
|
*
|
||||||
|
* @param string $event 事件名称
|
||||||
|
* @param array $data 返回数据
|
||||||
|
* @param string|null $traceId 请求追踪ID,可选,默认 null
|
||||||
|
* @param int $code 错误码
|
||||||
|
* @param string $message 错误信息
|
||||||
|
* @return string JSON
|
||||||
|
*/
|
||||||
|
public static function success(string $event, array $data = [], ?string $traceId = null, string $message = 'ok', int $code = 0): string
|
||||||
|
{
|
||||||
|
return json_encode([
|
||||||
|
'event' => $event,
|
||||||
|
'code' => $code,
|
||||||
|
'message' => $message,
|
||||||
|
'data' => $data,
|
||||||
|
'trace_id' => $traceId, // 如果没有传就是 null
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 错误返回
|
||||||
|
*
|
||||||
|
* @param string $event 事件名称
|
||||||
|
* @param int $code 错误码
|
||||||
|
* @param string $message 错误信息
|
||||||
|
* @param string|null $traceId 请求追踪ID,可选,默认 null
|
||||||
|
* @param array $data 附加数据(可选)
|
||||||
|
* @return string JSON
|
||||||
|
*/
|
||||||
|
public static function error(string $event, int $code, string $message, ?string $traceId = null, array $data = []): string
|
||||||
|
{
|
||||||
|
return json_encode([
|
||||||
|
'event' => $event,
|
||||||
|
'code' => $code,
|
||||||
|
'message' => $message,
|
||||||
|
'data' => $data,
|
||||||
|
'trace_id' => $traceId, // 如果没有传就是 null
|
||||||
|
], JSON_UNESCAPED_UNICODE);
|
||||||
|
}
|
||||||
|
}
|
||||||
+2
-1
@@ -45,7 +45,8 @@
|
|||||||
"symfony/filesystem": "^7.3",
|
"symfony/filesystem": "^7.3",
|
||||||
"workerman/crontab": "^1.0",
|
"workerman/crontab": "^1.0",
|
||||||
"phpoffice/phpspreadsheet": "^4.5",
|
"phpoffice/phpspreadsheet": "^4.5",
|
||||||
"phpmailer/phpmailer": "^6.10"
|
"phpmailer/phpmailer": "^6.10",
|
||||||
|
"webman/gateway-worker": "^1.0"
|
||||||
},
|
},
|
||||||
"suggest": {
|
"suggest": {
|
||||||
"ext-event": "For better performance. "
|
"ext-event": "For better performance. "
|
||||||
|
|||||||
Generated
+88
-1
@@ -4,7 +4,7 @@
|
|||||||
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
|
||||||
"This file is @generated automatically"
|
"This file is @generated automatically"
|
||||||
],
|
],
|
||||||
"content-hash": "505fb3d97c338ea53b787196fcc3daaa",
|
"content-hash": "7c70d8c8bda8a5098b8baed463db8839",
|
||||||
"packages": [
|
"packages": [
|
||||||
{
|
{
|
||||||
"name": "aliyuncs/oss-sdk-php",
|
"name": "aliyuncs/oss-sdk-php",
|
||||||
@@ -6634,6 +6634,39 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-04-04T13:23:25+00:00"
|
"time": "2025-04-04T13:23:25+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "webman/gateway-worker",
|
||||||
|
"version": "v1.0.9",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/webman-php/gateway-worker.git",
|
||||||
|
"reference": "6780c1c788c273a24699caee84feb14224234000"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/webman-php/gateway-worker/zipball/6780c1c788c273a24699caee84feb14224234000",
|
||||||
|
"reference": "6780c1c788c273a24699caee84feb14224234000",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"workerman/gateway-worker": ">=3.0"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"Webman\\GatewayWorker\\": "src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/webman-php/gateway-worker/issues",
|
||||||
|
"source": "https://github.com/webman-php/gateway-worker/tree/v1.0.9"
|
||||||
|
},
|
||||||
|
"time": "2024-04-30T01:05:42+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "webman/redis",
|
"name": "webman/redis",
|
||||||
"version": "v2.1.3",
|
"version": "v2.1.3",
|
||||||
@@ -6793,6 +6826,60 @@
|
|||||||
},
|
},
|
||||||
"time": "2025-01-15T07:20:50+00:00"
|
"time": "2025-01-15T07:20:50+00:00"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "workerman/gateway-worker",
|
||||||
|
"version": "v4.0.1",
|
||||||
|
"source": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/walkor/GatewayWorker.git",
|
||||||
|
"reference": "25e4430982caa98f82e9187d092ce34b2fddfc90"
|
||||||
|
},
|
||||||
|
"dist": {
|
||||||
|
"type": "zip",
|
||||||
|
"url": "https://api.github.com/repos/walkor/GatewayWorker/zipball/25e4430982caa98f82e9187d092ce34b2fddfc90",
|
||||||
|
"reference": "25e4430982caa98f82e9187d092ce34b2fddfc90",
|
||||||
|
"shasum": ""
|
||||||
|
},
|
||||||
|
"require": {
|
||||||
|
"php": ">=7.2",
|
||||||
|
"workerman/workerman": "^4.0.30 || ^5.1"
|
||||||
|
},
|
||||||
|
"type": "library",
|
||||||
|
"extra": {
|
||||||
|
"branch-alias": {
|
||||||
|
"dev-master": "4.0-dev"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"autoload": {
|
||||||
|
"psr-4": {
|
||||||
|
"GatewayWorker\\": "./src"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"notification-url": "https://packagist.org/downloads/",
|
||||||
|
"license": [
|
||||||
|
"MIT"
|
||||||
|
],
|
||||||
|
"homepage": "http://www.workerman.net",
|
||||||
|
"keywords": [
|
||||||
|
"communication",
|
||||||
|
"distributed"
|
||||||
|
],
|
||||||
|
"support": {
|
||||||
|
"issues": "https://github.com/walkor/GatewayWorker/issues",
|
||||||
|
"source": "https://github.com/walkor/GatewayWorker/tree/v4.0.1"
|
||||||
|
},
|
||||||
|
"funding": [
|
||||||
|
{
|
||||||
|
"url": "https://opencollective.com/walkor",
|
||||||
|
"type": "open_collective"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"url": "https://www.patreon.com/walkor",
|
||||||
|
"type": "patreon"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"time": "2025-06-27T01:30:09+00:00"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "workerman/redis",
|
"name": "workerman/redis",
|
||||||
"version": "v2.0.4",
|
"version": "v2.0.4",
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
<?php
|
||||||
|
return [
|
||||||
|
'enable' => true,
|
||||||
|
];
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Webman\GatewayWorker\Gateway;
|
||||||
|
use Webman\GatewayWorker\BusinessWorker;
|
||||||
|
use Webman\GatewayWorker\Register;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'gateway' => [
|
||||||
|
'handler' => Gateway::class,
|
||||||
|
'listen' => 'websocket://0.0.0.0:5151',
|
||||||
|
'count' => 2,
|
||||||
|
'reloadable' => false,
|
||||||
|
'constructor' => ['config' => [
|
||||||
|
'lanIp' => '127.0.0.1',
|
||||||
|
'startPort' => 2300,
|
||||||
|
'pingInterval' => 55,
|
||||||
|
'pingNotResponseLimit' => 1,
|
||||||
|
'pingData' => '',
|
||||||
|
'registerAddress' => '127.0.0.1:1236',
|
||||||
|
'onConnect' => function(){},
|
||||||
|
]]
|
||||||
|
],
|
||||||
|
'worker' => [
|
||||||
|
'handler' => BusinessWorker::class,
|
||||||
|
'count' => cpu_count()*2,
|
||||||
|
'constructor' => ['config' => [
|
||||||
|
'eventHandler' => plugin\webman\gateway\Events::class,
|
||||||
|
'name' => 'ChatBusinessWorker',
|
||||||
|
'registerAddress' => '127.0.0.1:1236',
|
||||||
|
]]
|
||||||
|
],
|
||||||
|
'register' => [
|
||||||
|
'handler' => Register::class,
|
||||||
|
'listen' => 'text://127.0.0.1:1236',
|
||||||
|
'count' => 1, // Must be 1
|
||||||
|
'reloadable' => false,
|
||||||
|
'constructor' => []
|
||||||
|
],
|
||||||
|
];
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace plugin\webman\gateway;
|
||||||
|
|
||||||
|
use app\common\utils\WsEvents;
|
||||||
|
use GatewayWorker\Lib\Gateway;
|
||||||
|
|
||||||
|
class Events
|
||||||
|
{
|
||||||
|
|
||||||
|
protected static $wsEvents;
|
||||||
|
|
||||||
|
public static function onWorkerStart($worker)
|
||||||
|
{
|
||||||
|
self::$wsEvents = new WsEvents();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function onConnect($client_id)
|
||||||
|
{
|
||||||
|
// 仅 TCP 连接,不一定有 WebSocket 握手
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function onWebSocketConnect($client_id, $data)
|
||||||
|
{
|
||||||
|
self::$wsEvents->auth($client_id, $data);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function onMessage($client_id, $message)
|
||||||
|
{
|
||||||
|
self::$wsEvents->message($client_id, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function onClose($client_id)
|
||||||
|
{
|
||||||
|
var_dump('关闭客户端ID:'.$client_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user