Files
fdesk/app/common/utils/WsEvents.php
T
2025-10-21 19:55:36 +08:00

360 lines
11 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
namespace app\common\utils;
use app\common\model\UserRoles;
use GatewayWorker\Lib\Gateway;
use app\common\utils\WsResponse;
use app\common\service\MessageNotificationsService;
use support\Redis;
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
));
}
}
protected function eventToMethod(string $event): string
{
$parts = explode('.', $event);
$method = array_shift($parts);
foreach ($parts as $part) {
$method .= ucfirst($part);
}
return $method;
}
// ================= 系统事件 =================
/**
* 认证
*/
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 (getenv('ENVIRONMENT') === 'master' && $payload['server']['HTTP_ORIGIN'] === getenv('MAIN_DOMAIN')) {
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);
// 绑定SESSION
$_SESSION['uid'] = $user->uid;
// $_SESSION['name'] = $user->uname;
// 响应
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;
}
}
public function logout(string $client_id)
{
// var_dump('====== ====== ====== start ====== ====== ======');
// var_dump('关闭客户端ID:'.$client_id);
// if ($_SESSION !== null) {
// var_dump($_SESSION);
// }
// var_dump('====== ====== ====== end ====== ====== ======');
}
/**
* 系统心跳
*
* @param string|null $traceId
*/
public function systemPing(?string $traceId)
{
Gateway::sendToClient($this->clientId, WsResponse::success(
'system.pong',
['time' => time()],
$traceId
));
}
public function systemConnect(?string $traceId)
{
// 响应
Gateway::sendToClient($this->clientId, WsResponse::success(
'system.connect.receive',
['time' => time()],
$traceId
));
// 获取审批待办数量
$num = (new \app\common\utils\approval\service\ApprovalFlowService())->getInitTodoCount($_SESSION['uid']);
$this->todoCounts($_SESSION['uid'], $num);
// 绑定检查
$initData = $this->initCheck($_SESSION['uid']);
Gateway::sendToClient($this->clientId, WsResponse::success(
'system.init.check',
$initData,
$traceId
));
}
// ================= 消息通知事件 =================
/**
* 管理端主动推送通知
*/
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.result',
400,
'标题和内容不能为空',
$traceId
));
return;
}
// 写消息
$msgService = new MessageNotificationsService();
$msgId = $msgService->createMessage([
'sender_id' => $this->eventData['sender_id'] ?? 0,
'sender_type' => $this->eventData['sender_type'] ?? 'system',
'title' => $title,
'content' => $content,
'scene' => $this->eventData['scene'] ?? 0,
'recipient' => $this->eventData['to_uids'] ? implode(',', $this->eventData['to_uids']) : '',
'push_method' => isset($this->eventData['push_method']) ? $this->eventData['push_method'] : 0
]);
// 写用户状态
$uids = !empty($toUids) ? $toUids : $msgService->getAllUserIds();
$msgService->addUserStatus($msgId, $uids);
$payload = WsResponse::success('notify.receive', [
'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,
'消息发送成功'
));
}
/**
* 客户端标记通知已读
*/
public function notifyRead(?string $traceId)
{
$msgIds = $this->eventData['msg_ids'];
if (!is_array($msgIds) && $msgIds !== 'all') {
Gateway::sendToClient($this->clientId, WsResponse::error(
'notify.read.result',
400,
'消息ID不能为空',
$traceId
));
return;
}
// 更新消息为已读
$msgService = new MessageNotificationsService();
$msgService->markAsRead($msgIds, Gateway::getUidByClientId($this->clientId));
Gateway::sendToClient($this->clientId, WsResponse::success(
'notify.read.result',
['msg_ids' => $msgIds],
$traceId
));
}
/*
approval.todo.counts 审批待办数量
*/
// ================= 主动推送 =================
public function pushWebsocket(string $event, int|string $uid, array $data)
{
return match (gettype($uid)) {
'integer' => Gateway::sendToUid($uid, WsResponse::success($event, $data)),
'string' => Gateway::sendToClient($uid, WsResponse::success($event, $data)),
default => throw new \InvalidArgumentException('UID must be integer or string'),
};
}
// ================= 业务处理 =================
/**
* 初始化检查
*/
public function initCheck(int $userId)
{
$data = [
// 'is_report_to' => false,
// 'is_office_clerk' => false,
'is_sign' => false
];
$user = \app\common\model\User::find($userId);
// if ($user->report_to > 0) {
// $data['is_report_to'] = true;
// }
if ($user->sign_img !== null && $user->sign_img !== '') {
$data['is_sign'] = true;
}
// $ug = \app\common\model\UserGroupMembers::with(['group' => function($query) {
// $query->where('scenario_code', 'stock_orders');
// }])->where('user_id', $userId)->first();
// if ($ug !== null) {
// $data['is_office_clerk'] = true;
// }
if ($data['is_sign']) {
Redis::hSet('user:check:sign'.$userId, 'status', 1);
}
return $data;
}
// 审批待办数量
public function todoCounts(int $userId, int $num = 0)
{
$ly = [
1 => 186,
3 => 224,
];
if ($num === 0) {
$num = (new \app\common\utils\approval\service\ApprovalFlowService())->getInitTodoCount($userId);
}
$systemIds = \app\common\model\UserSystems::where('user_id', $userId)->pluck('system_id')->toArray();
if (count($systemIds) > 0) {
foreach ($systemIds as $systemId) {
if (isset($ly[$systemId])) {
$chain = \app\common\service\MenuBadgeService::buildBadgeChain(menuId: $ly[$systemId], leafCount: $num);
$this->pushWebsocket('approval.todo.counts', $userId, $chain);
}
}
}
}
}