Refactor ChatConnect and related classes to use dynamic typing for properties and method parameters. Update event handling to use a consistent naming convention for emitted events. Adjust WebSocket message handling methods to accept event arrays instead of specific data structures. Update configuration for Swoole to increase ping intervals and timeouts.
This commit is contained in:
@@ -6,6 +6,7 @@ use app\models\chat\ChatMessage;
|
||||
use app\models\chat\ChatSession;
|
||||
use app\utils\Snowflake;
|
||||
use think\facade\Cache;
|
||||
use think\swoole\WebSocket;
|
||||
|
||||
/**
|
||||
* 消息服务
|
||||
@@ -20,12 +21,173 @@ class MessageService
|
||||
private const MAX_CONTENT_LENGTH = 500;
|
||||
private const RECONNECT_FETCH_LIMIT = 50;
|
||||
|
||||
// Redis Key 前缀
|
||||
private const CONN_USER_PREFIX = 'cs:conn:user:';
|
||||
private const CONN_AGENT_PREFIX = 'cs:conn:agent:';
|
||||
|
||||
/**
|
||||
* 发送消息(WebSocket事件处理入口)
|
||||
* @param WebSocket $ws
|
||||
* @param array $data [sessionId, msgType, content, clientMsgId]
|
||||
*/
|
||||
public function sendMessage($ws, $data)
|
||||
{
|
||||
echo "[MessageService] ========== 收到发送消息请求 ==========\n";
|
||||
echo "[MessageService] 数据: " . json_encode($data, JSON_UNESCAPED_UNICODE) . "\n";
|
||||
|
||||
$sessionId = $data['sessionId'] ?? 0;
|
||||
$msgType = $data['msgType'] ?? ChatMessage::MSG_TYPE_TEXT;
|
||||
$content = $data['content'] ?? '';
|
||||
$clientMsgId = $data['clientMsgId'] ?? null;
|
||||
|
||||
// 验证会话
|
||||
$session = ChatSession::find($sessionId);
|
||||
if (!$session) {
|
||||
echo "[MessageService] 会话不存在: {$sessionId}\n";
|
||||
$ws->emit('chat_error', ['error' => 'session_not_found']);
|
||||
return;
|
||||
}
|
||||
$session = $session->toArray();
|
||||
echo "[MessageService] 会话信息: user_id={$session['user_id']}, admin_id=" . ($session['admin_id'] ?? 'null') . "\n";
|
||||
|
||||
// 验证内容
|
||||
$validation = $this->validateContent($content, $msgType);
|
||||
if (!$validation['valid']) {
|
||||
echo "[MessageService] 内容验证失败: {$validation['error']}\n";
|
||||
$ws->emit('chat_error', ['error' => $validation['error']]);
|
||||
return;
|
||||
}
|
||||
|
||||
// 判断发送者类型(通过fd查找)
|
||||
$fd = $ws->getSender();
|
||||
$redis = $this->getRedis();
|
||||
|
||||
echo "[MessageService] 发送者FD: {$fd}\n";
|
||||
|
||||
// 检查是用户还是客服
|
||||
$senderType = null;
|
||||
$senderId = null;
|
||||
$targetFd = null;
|
||||
|
||||
// 检查是否是客服发送
|
||||
$agentId = $session['admin_id'];
|
||||
if ($agentId) {
|
||||
$agentFd = $redis->get(self::CONN_AGENT_PREFIX . $agentId);
|
||||
echo "[MessageService] 客服FD (Redis): " . ($agentFd ?: 'null') . "\n";
|
||||
if ($agentFd && (int)$agentFd === $fd) {
|
||||
$senderType = ChatMessage::SENDER_ADMIN;
|
||||
$senderId = $agentId;
|
||||
// 目标是用户
|
||||
$targetFd = $redis->get(self::CONN_USER_PREFIX . $session['user_id']);
|
||||
echo "[MessageService] 识别为客服发送, 目标用户FD: " . ($targetFd ?: 'null') . "\n";
|
||||
}
|
||||
}
|
||||
|
||||
// 检查是否是用户发送
|
||||
if ($senderType === null) {
|
||||
$userId = $session['user_id'];
|
||||
$userFd = $redis->get(self::CONN_USER_PREFIX . $userId);
|
||||
echo "[MessageService] 用户FD (Redis): " . ($userFd ?: 'null') . "\n";
|
||||
if ($userFd && (int)$userFd === $fd) {
|
||||
$senderType = ChatMessage::SENDER_USER;
|
||||
$senderId = $userId;
|
||||
// 目标是客服
|
||||
if ($agentId) {
|
||||
$targetFd = $redis->get(self::CONN_AGENT_PREFIX . $agentId);
|
||||
echo "[MessageService] 识别为用户发送, 目标客服FD: " . ($targetFd ?: 'null') . "\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($senderType === null) {
|
||||
echo "[MessageService] 无法识别发送者身份, FD: {$fd}\n";
|
||||
$ws->emit('chat_error', ['error' => 'unauthorized']);
|
||||
return;
|
||||
}
|
||||
|
||||
echo "[MessageService] 发送者类型: {$senderType}, 发送者ID: {$senderId}\n";
|
||||
|
||||
// 创建消息
|
||||
$message = $this->createMessage([
|
||||
'session_id' => $sessionId,
|
||||
'sender_type' => $senderType,
|
||||
'sender_id' => $senderId,
|
||||
'msg_type' => $msgType,
|
||||
'content' => $content,
|
||||
'client_msg_id' => $clientMsgId,
|
||||
]);
|
||||
|
||||
// 发送确认给发送者
|
||||
$ws->emit('chat_message_sent', [
|
||||
'success' => true,
|
||||
'msgId' => $message['msg_id'],
|
||||
'clientMsgId' => $clientMsgId,
|
||||
'sessionId' => $sessionId,
|
||||
]);
|
||||
|
||||
// 推送给目标
|
||||
if ($targetFd) {
|
||||
$targetFd = (int)$targetFd;
|
||||
$server = app('swoole.server');
|
||||
|
||||
echo "[MessageService] 准备推送消息到FD: {$targetFd}\n";
|
||||
|
||||
if ($server->isEstablished($targetFd)) {
|
||||
$payload = [
|
||||
'event' => 'chat_message_new',
|
||||
'data' => [
|
||||
'msgId' => $message['msg_id'],
|
||||
'sessionId' => $sessionId,
|
||||
'senderType' => $senderType,
|
||||
'msgType' => $msgType,
|
||||
'content' => $content,
|
||||
'createTime' => $message['create_time'],
|
||||
]
|
||||
];
|
||||
|
||||
// 使用 Socket.IO 协议格式发送
|
||||
$result = $this->emitToFd($targetFd, 'chat_message_new', $payload['data']);
|
||||
echo "[MessageService] 推送结果: " . ($result ? '成功' : '失败') . "\n";
|
||||
|
||||
// 更新消息状态为已发送
|
||||
$this->updateMessageStatus($message['msg_id'], ChatMessage::STATUS_SENT);
|
||||
} else {
|
||||
echo "[MessageService] 目标FD {$targetFd} 连接已断开\n";
|
||||
}
|
||||
} else {
|
||||
echo "[MessageService] 目标FD为空,无法推送\n";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 向指定fd发送Socket.IO事件
|
||||
*/
|
||||
private function emitToFd($fd, $event, $data)
|
||||
{
|
||||
$server = app('swoole.server');
|
||||
if (!$server->isEstablished($fd)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Socket.IO v2 协议格式: 42["event",{data}]
|
||||
$packet = '42' . json_encode([$event, $data]);
|
||||
return $server->push($fd, $packet);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取Redis实例
|
||||
*/
|
||||
private function getRedis()
|
||||
{
|
||||
return Cache::store('redis')->handler();
|
||||
}
|
||||
|
||||
/**
|
||||
* 创建消息
|
||||
* @param array $data [session_id, sender_type, sender_id, msg_type, content]
|
||||
* @return array 消息数据
|
||||
*/
|
||||
public function createMessage(array $data): array
|
||||
public function createMessage($data)
|
||||
{
|
||||
// 生成雪花ID
|
||||
$msgId = Snowflake::getInstance()->nextId();
|
||||
@@ -74,7 +236,7 @@ class MessageService
|
||||
* @param array $payload 消息内容
|
||||
* @return bool
|
||||
*/
|
||||
public function pushMessage(int $msgId, int $targetFd, array $payload): bool
|
||||
public function pushMessage($msgId, $targetFd, $payload)
|
||||
{
|
||||
$server = app('swoole.server');
|
||||
|
||||
@@ -110,7 +272,7 @@ class MessageService
|
||||
/**
|
||||
* 更新消息状态
|
||||
*/
|
||||
public function updateMessageStatus(int $msgId, int $status): void
|
||||
public function updateMessageStatus($msgId, $status)
|
||||
{
|
||||
$update = ['status' => $status];
|
||||
|
||||
@@ -126,7 +288,7 @@ class MessageService
|
||||
/**
|
||||
* 获取会话未读消息 (重连后拉取)
|
||||
*/
|
||||
public function getUnreadMessages(int $sessionId, int $receiverType, int $limit = null): array
|
||||
public function getUnreadMessages($sessionId, $receiverType, $limit = null)
|
||||
{
|
||||
$limit = $limit ?? self::RECONNECT_FETCH_LIMIT;
|
||||
return ChatMessage::getUnreadBySessionId($sessionId, $receiverType, $limit);
|
||||
@@ -135,7 +297,7 @@ class MessageService
|
||||
/**
|
||||
* 批量标记消息为已读
|
||||
*/
|
||||
public function markMessagesAsRead(array $msgIds): void
|
||||
public function markMessagesAsRead($msgIds)
|
||||
{
|
||||
if (empty($msgIds)) {
|
||||
return;
|
||||
@@ -152,7 +314,7 @@ class MessageService
|
||||
/**
|
||||
* 标记消息为已送达
|
||||
*/
|
||||
public function markMessageDelivered(int $msgId): void
|
||||
public function markMessageDelivered($msgId)
|
||||
{
|
||||
ChatMessage::where('msg_id', $msgId)
|
||||
->where('status', '<', ChatMessage::STATUS_DELIVERED)
|
||||
@@ -165,7 +327,7 @@ class MessageService
|
||||
/**
|
||||
* 获取会话消息历史
|
||||
*/
|
||||
public function getMessageHistory(int $sessionId, int $limit = 50, int $lastId = 0): array
|
||||
public function getMessageHistory($sessionId, $limit = 50, $lastId = 0)
|
||||
{
|
||||
return ChatMessage::getBySessionId($sessionId, $limit, $lastId);
|
||||
}
|
||||
@@ -173,7 +335,7 @@ class MessageService
|
||||
/**
|
||||
* 增加重试次数
|
||||
*/
|
||||
private function incrementRetryCount(int $msgId): void
|
||||
private function incrementRetryCount($msgId)
|
||||
{
|
||||
ChatMessage::where('msg_id', $msgId)->inc('retry_count')->update();
|
||||
}
|
||||
@@ -181,7 +343,7 @@ class MessageService
|
||||
/**
|
||||
* 验证消息内容
|
||||
*/
|
||||
public function validateContent(string $content, int $msgType): array
|
||||
public function validateContent($content, $msgType)
|
||||
{
|
||||
if ($msgType === ChatMessage::MSG_TYPE_TEXT) {
|
||||
if (empty(trim($content))) {
|
||||
|
||||
Reference in New Issue
Block a user