feat: 新增WebSocket 服务

This commit is contained in:
2025-12-19 11:56:14 +08:00
parent 4e5d16e48c
commit 498122f57e
305 changed files with 42480 additions and 7 deletions

View File

@@ -0,0 +1,207 @@
<?php
namespace app\api\controller;
use Ratchet\ConnectionInterface;
use Ratchet\MessageComponentInterface;
/**
* WebSocket基类用于各个addon继承实现自己的WebSocket控制器
* 提供统一的接口和基本功能确保各个addon的WebSocket相互隔离
*/
abstract class WebSocketBase implements MessageComponentInterface
{
protected $clients;
protected $clientData;
protected $addonName;
// 控制器属性
public $params;
public $token;
protected $member_id;
protected $site_id;
protected $uniacid;
protected $site_ids = [];
public $app_type;
/**
* 构造函数
*/
public function __construct($addonName = '')
{
$this->clients = new \SplObjectStorage;
$this->clientData = [];
$this->addonName = $addonName;
// 初始化控制器属性
$this->params = [];
$this->token = '';
$this->member_id = 0;
$this->site_id = 0;
$this->uniacid = 0;
$this->app_type = 'weapp'; // 默认微信小程序
}
/**
* 当有新客户端连接时调用
* @param ConnectionInterface $conn
*/
public function onOpen(ConnectionInterface $conn)
{
// 存储新连接的客户端
$this->clients->attach($conn);
$this->clientData[$conn->resourceId] = [
'connection' => $conn,
'site_id' => null,
'member_id' => null,
'token' => null,
'is_authenticated' => false,
'conversation_id' => null,
'addon_name' => $this->addonName, // 记录当前连接所属的addon
];
echo "[{$this->addonName}] New connection! ({$conn->resourceId})\n";
}
/**
* 当从客户端收到消息时调用
* @param ConnectionInterface $conn
* @param string $message
*/
public function onMessage(ConnectionInterface $conn, $message)
{
$numRecv = count($this->clients) - 1;
echo sprintf('[{$this->addonName}] Connection %d sending message "%s" to %d other connection%s' . "\n",
$conn->resourceId, $message, $numRecv, $numRecv == 1 ? '' : 's');
// 解析消息
try {
$data = json_decode($message, true);
if (json_last_error() !== JSON_ERROR_NONE) {
throw new \Exception('Invalid JSON format');
}
// 处理认证
if (isset($data['action']) && $data['action'] === 'auth') {
$this->handleAuth($conn, $data);
return;
}
// 检查是否已认证
if (!$this->clientData[$conn->resourceId]['is_authenticated']) {
$conn->send(json_encode(['type' => 'error', 'message' => 'Not authenticated']));
return;
}
// 处理聊天消息
if (isset($data['action']) && $data['action'] === 'chat') {
$this->handleChat($conn, $data);
return;
}
// 处理心跳
if (isset($data['action']) && $data['action'] === 'ping') {
$conn->send(json_encode(['type' => 'pong']));
return;
}
$conn->send(json_encode(['type' => 'error', 'message' => 'Unknown action']));
} catch (\Exception $e) {
$conn->send(json_encode(['type' => 'error', 'message' => $e->getMessage()]));
}
}
/**
* 当客户端连接关闭时调用
* @param ConnectionInterface $conn
*/
public function onClose(ConnectionInterface $conn)
{
// 移除连接
$this->clients->detach($conn);
unset($this->clientData[$conn->resourceId]);
echo "[{$this->addonName}] Connection {$conn->resourceId} has disconnected\n";
}
/**
* 当连接发生错误时调用
* @param ConnectionInterface $conn
* @param \Exception $e
*/
public function onError(ConnectionInterface $conn, \Exception $e)
{
echo "[{$this->addonName}] An error has occurred: {$e->getMessage()}\n";
$conn->close();
}
/**
* 处理客户端认证
* @param ConnectionInterface $conn
* @param array $data
*/
protected function handleAuth(ConnectionInterface $conn, $data)
{
try {
$site_id = $data['site_id'] ?? null;
$member_id = $data['member_id'] ?? null;
$token = $data['token'] ?? null;
if (empty($site_id) || empty($member_id) || empty($token)) {
throw new \Exception('Missing authentication parameters');
}
// 子类可以重写此方法来实现更严格的认证逻辑
$this->doAuth($conn, $site_id, $member_id, $token);
$this->clientData[$conn->resourceId]['site_id'] = $site_id;
$this->clientData[$conn->resourceId]['member_id'] = $member_id;
$this->clientData[$conn->resourceId]['token'] = $token;
$this->clientData[$conn->resourceId]['is_authenticated'] = true;
$conn->send(json_encode(['type' => 'auth_success', 'message' => 'Authenticated successfully', 'addon' => $this->addonName]));
} catch (\Exception $e) {
$conn->send(json_encode(['type' => 'auth_error', 'message' => $e->getMessage()]));
}
}
/**
* 实际的认证逻辑,子类可以重写此方法
* @param ConnectionInterface $conn
* @param int $site_id
* @param int $member_id
* @param string $token
*/
protected function doAuth(ConnectionInterface $conn, $site_id, $member_id, $token)
{
// 默认实现,子类应该重写此方法
// 这里可以添加更严格的认证逻辑例如验证token的有效性
}
/**
* 处理聊天消息
* @param ConnectionInterface $conn
* @param array $data
*/
abstract protected function handleChat(ConnectionInterface $conn, $data);
/**
* 发送消息给指定客户端
* @param ConnectionInterface $conn
* @param array $message
*/
protected function sendMessage(ConnectionInterface $conn, $message)
{
$conn->send(json_encode(array_merge(['addon' => $this->addonName], $message)));
}
/**
* 获取当前addon名称
* @return string
*/
public function getAddonName()
{
return $this->addonName;
}
}