初始化项目
This commit is contained in:
402
app/Services/WebSocket/WebSocketService.php
Normal file
402
app/Services/WebSocket/WebSocketService.php
Normal file
@@ -0,0 +1,402 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\WebSocket;
|
||||
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use Swoole\WebSocket\Server;
|
||||
|
||||
/**
|
||||
* WebSocket Service
|
||||
*
|
||||
* Provides helper functions for WebSocket operations
|
||||
*/
|
||||
class WebSocketService
|
||||
{
|
||||
/**
|
||||
* Get Swoole WebSocket Server instance
|
||||
*
|
||||
* @return Server|null
|
||||
*/
|
||||
public function getServer(): ?Server
|
||||
{
|
||||
return app('swoole.server');
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to a specific user
|
||||
*
|
||||
* @param int $userId
|
||||
* @param array $data
|
||||
* @return bool
|
||||
*/
|
||||
public function sendToUser(int $userId, array $data): bool
|
||||
{
|
||||
$server = $this->getServer();
|
||||
|
||||
if (!$server || !isset($server->wsTable)) {
|
||||
Log::warning('WebSocket server not available', ['user_id' => $userId]);
|
||||
return false;
|
||||
}
|
||||
|
||||
$fdInfo = $server->wsTable->get('uid:' . $userId);
|
||||
|
||||
if (!$fdInfo || !$fdInfo['value']) {
|
||||
Log::info('User not connected to WebSocket', ['user_id' => $userId]);
|
||||
return false;
|
||||
}
|
||||
|
||||
$fd = (int)$fdInfo['value'];
|
||||
|
||||
if (!$server->isEstablished($fd)) {
|
||||
Log::info('WebSocket connection not established', ['user_id' => $userId, 'fd' => $fd]);
|
||||
// Clean up stale connection
|
||||
$server->wsTable->del('uid:' . $userId);
|
||||
$server->wsTable->del('fd:' . $fd);
|
||||
return false;
|
||||
}
|
||||
|
||||
$server->push($fd, json_encode($data));
|
||||
|
||||
Log::info('Message sent to user via WebSocket', [
|
||||
'user_id' => $userId,
|
||||
'fd' => $fd,
|
||||
'data' => $data
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to multiple users
|
||||
*
|
||||
* @param array $userIds
|
||||
* @param array $data
|
||||
* @return array Array of user IDs who received the message
|
||||
*/
|
||||
public function sendToUsers(array $userIds, array $data): array
|
||||
{
|
||||
$sentTo = [];
|
||||
|
||||
foreach ($userIds as $userId) {
|
||||
if ($this->sendToUser($userId, $data)) {
|
||||
$sentTo[] = $userId;
|
||||
}
|
||||
}
|
||||
|
||||
return $sentTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* Broadcast message to all connected clients
|
||||
*
|
||||
* @param array $data
|
||||
* @param int|null $excludeUserId User ID to exclude from broadcast
|
||||
* @return int Number of clients the message was sent to
|
||||
*/
|
||||
public function broadcast(array $data, ?int $excludeUserId = null): int
|
||||
{
|
||||
$server = $this->getServer();
|
||||
|
||||
if (!$server) {
|
||||
Log::warning('WebSocket server not available for broadcast');
|
||||
return 0;
|
||||
}
|
||||
|
||||
$message = json_encode($data);
|
||||
$count = 0;
|
||||
|
||||
foreach ($server->connections as $fd) {
|
||||
if (!$server->isEstablished($fd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check if we should exclude this user
|
||||
if ($excludeUserId) {
|
||||
$fdInfo = $server->wsTable->get('fd:' . $fd);
|
||||
if ($fdInfo && $fdInfo['value'] == $excludeUserId) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$server->push($fd, $message);
|
||||
$count++;
|
||||
}
|
||||
|
||||
Log::info('Broadcast sent via WebSocket', [
|
||||
'data' => $data,
|
||||
'exclude_user_id' => $excludeUserId,
|
||||
'count' => $count
|
||||
]);
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send message to all subscribers of a channel
|
||||
*
|
||||
* @param string $channel
|
||||
* @param array $data
|
||||
* @return int Number of subscribers who received the message
|
||||
*/
|
||||
public function sendToChannel(string $channel, array $data): int
|
||||
{
|
||||
$server = $this->getServer();
|
||||
|
||||
if (!$server || !isset($server->wsTable)) {
|
||||
Log::warning('WebSocket server not available for channel broadcast', ['channel' => $channel]);
|
||||
return 0;
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
$message = json_encode($data);
|
||||
|
||||
// Iterate through all connections and check if they're subscribed to the channel
|
||||
foreach ($server->connections as $fd) {
|
||||
if (!$server->isEstablished($fd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$subscription = $server->wsTable->get('channel:' . $channel . ':fd:' . $fd);
|
||||
|
||||
if ($subscription) {
|
||||
$server->push($fd, $message);
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
Log::info('Channel message sent via WebSocket', [
|
||||
'channel' => $channel,
|
||||
'data' => $data,
|
||||
'count' => $count
|
||||
]);
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get online user count
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function getOnlineUserCount(): int
|
||||
{
|
||||
$server = $this->getServer();
|
||||
|
||||
if (!$server || !isset($server->wsTable)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Count established connections
|
||||
$count = 0;
|
||||
foreach ($server->connections as $fd) {
|
||||
if ($server->isEstablished($fd)) {
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
|
||||
return $count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a user is online
|
||||
*
|
||||
* @param int $userId
|
||||
* @return bool
|
||||
*/
|
||||
public function isUserOnline(int $userId): bool
|
||||
{
|
||||
$server = $this->getServer();
|
||||
|
||||
if (!$server || !isset($server->wsTable)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fdInfo = $server->wsTable->get('uid:' . $userId);
|
||||
|
||||
if (!$fdInfo || !$fdInfo['value']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fd = (int)$fdInfo['value'];
|
||||
|
||||
return $server->isEstablished($fd);
|
||||
}
|
||||
|
||||
/**
|
||||
* Disconnect a user from WebSocket
|
||||
*
|
||||
* @param int $userId
|
||||
* @return bool
|
||||
*/
|
||||
public function disconnectUser(int $userId): bool
|
||||
{
|
||||
$server = $this->getServer();
|
||||
|
||||
if (!$server || !isset($server->wsTable)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fdInfo = $server->wsTable->get('uid:' . $userId);
|
||||
|
||||
if (!$fdInfo || !$fdInfo['value']) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$fd = (int)$fdInfo['value'];
|
||||
|
||||
if ($server->isEstablished($fd)) {
|
||||
$server->push($fd, json_encode([
|
||||
'type' => 'disconnect',
|
||||
'data' => [
|
||||
'message' => 'You have been disconnected',
|
||||
'timestamp' => time()
|
||||
]
|
||||
]));
|
||||
|
||||
// Close the connection
|
||||
$server->disconnect($fd);
|
||||
|
||||
// Clean up
|
||||
$server->wsTable->del('uid:' . $userId);
|
||||
$server->wsTable->del('fd:' . $fd);
|
||||
|
||||
Log::info('User disconnected from WebSocket by server', [
|
||||
'user_id' => $userId,
|
||||
'fd' => $fd
|
||||
]);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all online user IDs
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function getOnlineUserIds(): array
|
||||
{
|
||||
$server = $this->getServer();
|
||||
|
||||
if (!$server || !isset($server->wsTable)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$userIds = [];
|
||||
|
||||
foreach ($server->connections as $fd) {
|
||||
if (!$server->isEstablished($fd)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$fdInfo = $server->wsTable->get('fd:' . $fd);
|
||||
|
||||
if ($fdInfo && $fdInfo['value']) {
|
||||
$userIds[] = (int)$fdInfo['value'];
|
||||
}
|
||||
}
|
||||
|
||||
return array_unique($userIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send system notification to all online users
|
||||
*
|
||||
* @param string $title
|
||||
* @param string $message
|
||||
* @param string $type
|
||||
* @param array $extraData
|
||||
* @return int
|
||||
*/
|
||||
public function sendSystemNotification(string $title, string $message, string $type = 'info', array $extraData = []): int
|
||||
{
|
||||
$data = [
|
||||
'type' => 'notification',
|
||||
'data' => [
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'type' => $type, // info, success, warning, error
|
||||
'timestamp' => time(),
|
||||
...$extraData
|
||||
]
|
||||
];
|
||||
|
||||
return $this->broadcast($data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send notification to specific users
|
||||
*
|
||||
* @param array $userIds
|
||||
* @param string $title
|
||||
* @param string $message
|
||||
* @param string $type
|
||||
* @param array $extraData
|
||||
* @return array
|
||||
*/
|
||||
public function sendNotificationToUsers(array $userIds, string $title, string $message, string $type = 'info', array $extraData = []): array
|
||||
{
|
||||
$data = [
|
||||
'type' => 'notification',
|
||||
'data' => [
|
||||
'title' => $title,
|
||||
'message' => $message,
|
||||
'type' => $type,
|
||||
'timestamp' => time(),
|
||||
...$extraData
|
||||
]
|
||||
];
|
||||
|
||||
return $this->sendToUsers($userIds, $data);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push data update to specific users
|
||||
*
|
||||
* @param array $userIds
|
||||
* @param string $resourceType
|
||||
* @param string $action
|
||||
* @param array $data
|
||||
* @return array
|
||||
*/
|
||||
public function pushDataUpdate(array $userIds, string $resourceType, string $action, array $data): array
|
||||
{
|
||||
$message = [
|
||||
'type' => 'data_update',
|
||||
'data' => [
|
||||
'resource_type' => $resourceType, // e.g., 'user', 'order', 'product'
|
||||
'action' => $action, // create, update, delete
|
||||
'data' => $data,
|
||||
'timestamp' => time()
|
||||
]
|
||||
];
|
||||
|
||||
return $this->sendToUsers($userIds, $message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Push data update to a channel
|
||||
*
|
||||
* @param string $channel
|
||||
* @param string $resourceType
|
||||
* @param string $action
|
||||
* @param array $data
|
||||
* @return int
|
||||
*/
|
||||
public function pushDataUpdateToChannel(string $channel, string $resourceType, string $action, array $data): int
|
||||
{
|
||||
$message = [
|
||||
'type' => 'data_update',
|
||||
'data' => [
|
||||
'resource_type' => $resourceType,
|
||||
'action' => $action,
|
||||
'data' => $data,
|
||||
'timestamp' => time()
|
||||
]
|
||||
];
|
||||
|
||||
return $this->sendToChannel($channel, $message);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user