diff --git a/app/common/service/ResourceService.php b/app/common/service/ResourceService.php index ad3f85b..c1654a5 100644 --- a/app/common/service/ResourceService.php +++ b/app/common/service/ResourceService.php @@ -53,6 +53,7 @@ class ResourceService $config['order']['id'] = 'asc'; break; case 'menuTreeList': + $config['where']['system_id'] = ['in', [0, $p['system_id']]]; $config['where']['status'] = 1; $config['order']['sort'] = 'asc'; $config['order']['id'] = 'asc'; diff --git a/app/common/utils/WsEvents.php b/app/common/utils/WsEvents.php new file mode 100644 index 0000000..0916078 --- /dev/null +++ b/app/common/utils/WsEvents.php @@ -0,0 +1,221 @@ +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; + } +} \ No newline at end of file diff --git a/app/common/utils/WsResponse.php b/app/common/utils/WsResponse.php new file mode 100644 index 0000000..6cf0b28 --- /dev/null +++ b/app/common/utils/WsResponse.php @@ -0,0 +1,47 @@ + $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); + } +} \ No newline at end of file diff --git a/composer.json b/composer.json index 98e3e54..c474010 100644 --- a/composer.json +++ b/composer.json @@ -45,7 +45,8 @@ "symfony/filesystem": "^7.3", "workerman/crontab": "^1.0", "phpoffice/phpspreadsheet": "^4.5", - "phpmailer/phpmailer": "^6.10" + "phpmailer/phpmailer": "^6.10", + "webman/gateway-worker": "^1.0" }, "suggest": { "ext-event": "For better performance. " diff --git a/composer.lock b/composer.lock index edf286f..7d41f97 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "505fb3d97c338ea53b787196fcc3daaa", + "content-hash": "7c70d8c8bda8a5098b8baed463db8839", "packages": [ { "name": "aliyuncs/oss-sdk-php", @@ -6634,6 +6634,39 @@ }, "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", "version": "v2.1.3", @@ -6793,6 +6826,60 @@ }, "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", "version": "v2.0.4", diff --git a/config/plugin/webman/gateway-worker/app.php b/config/plugin/webman/gateway-worker/app.php new file mode 100644 index 0000000..8f9c426 --- /dev/null +++ b/config/plugin/webman/gateway-worker/app.php @@ -0,0 +1,4 @@ + true, +]; \ No newline at end of file diff --git a/config/plugin/webman/gateway-worker/process.php b/config/plugin/webman/gateway-worker/process.php new file mode 100644 index 0000000..5c7efaa --- /dev/null +++ b/config/plugin/webman/gateway-worker/process.php @@ -0,0 +1,39 @@ + [ + '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' => [] + ], +]; diff --git a/plugin/webman/gateway/Events.php b/plugin/webman/gateway/Events.php new file mode 100644 index 0000000..d63708e --- /dev/null +++ b/plugin/webman/gateway/Events.php @@ -0,0 +1,38 @@ +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); + } + +} \ No newline at end of file