Socket/app/services/chat/SessionService.php
2026-01-28 23:48:20 +08:00

327 lines
8.5 KiB
PHP

<?php
namespace app\services\chat;
use app\models\chat\ChatSession;
use app\models\chat\ChatMessage;
use think\facade\Cache;
use think\facade\Db;
/**
* 会话服务
* Class SessionService
* @package app\services\chat
*/
class SessionService
{
// Redis Key 前缀
private const USER_ACTIVE_SESSION_PREFIX = 'cs:user:active_session:';
private const CONN_USER_PREFIX = 'cs:conn:user:';
private const CONN_AGENT_PREFIX = 'cs:conn:agent:';
private AssignService $assignService;
public function __construct()
{
$this->assignService = new AssignService();
}
/**
* 创建会话
* @param int $userId 用户ID
* @param int $source 来源 1=PC 2=Game 3=Portal
* @return array 会话数据
*/
public function createSession(int $userId, int $source): array
{
$redis = $this->getRedis();
// 检查是否已有活跃会话
$existingSession = ChatSession::getActiveByUserId($userId);
if ($existingSession) {
return $existingSession;
}
// 创建新会话
$session = [
'user_id' => $userId,
'source' => $source,
'status' => ChatSession::STATUS_PENDING,
'create_time' => time(),
'update_time' => time(),
];
$sessionId = ChatSession::insertGetId($session);
$session['id'] = $sessionId;
// 缓存用户活跃会话
$redis->set(self::USER_ACTIVE_SESSION_PREFIX . $userId, $sessionId);
// 尝试分配客服
$adminId = $this->assignService->assignSession($userId, $sessionId);
if ($adminId !== null) {
$session['admin_id'] = $adminId;
$session['status'] = ChatSession::STATUS_ACTIVE;
}
return $session;
}
/**
* 获取用户活跃会话
*/
public function getActiveSession(int $userId): ?array
{
return ChatSession::getActiveByUserId($userId);
}
/**
* 获取会话详情(含用户信息)
*/
public function getSessionDetail(int $sessionId): ?array
{
$session = ChatSession::find($sessionId);
if (!$session) {
return null;
}
$session = $session->toArray();
// 获取用户信息
$user = Db::name('user')->where('id', $session['user_id'])->find();
if ($user) {
$session['user_info'] = [
'id' => $user['id'],
'username' => $user['username'],
'nickname' => $user['nickname'] ?? $user['username'],
'money' => $user['money'],
'agent_id' => $user['agent_id'] ?? null,
];
}
return $session;
}
/**
* 结束会话
*/
public function endSession(int $sessionId, int $operatorId): bool
{
$session = ChatSession::find($sessionId);
if (!$session) {
return false;
}
$session = $session->toArray();
// 只能结束进行中的会话
if ($session['status'] === ChatSession::STATUS_ENDED) {
return true;
}
// 更新会话状态
ChatSession::where('id', $sessionId)->update([
'status' => ChatSession::STATUS_ENDED,
'end_time' => time(),
'update_time' => time(),
]);
// 清理Redis缓存
$redis = $this->getRedis();
$redis->del(self::USER_ACTIVE_SESSION_PREFIX . $session['user_id']);
// 释放客服会话配额
if ($session['admin_id']) {
$this->assignService->releaseSession($sessionId, $session['admin_id']);
}
return true;
}
/**
* 转接会话
*/
public function transferSession(int $sessionId, int $newAdminId): bool
{
$session = ChatSession::find($sessionId);
if (!$session || $session['status'] !== ChatSession::STATUS_ACTIVE) {
return false;
}
$oldAdminId = $session['admin_id'];
// 更新会话归属
ChatSession::where('id', $sessionId)->update([
'admin_id' => $newAdminId,
'update_time' => time(),
]);
// 更新Redis
$redis = $this->getRedis();
$redis->set('cs:session:owner:' . $sessionId, $newAdminId);
// 调整负载计数
if ($oldAdminId) {
$oldLoad = (int)$redis->get('cs:agent:load:' . $oldAdminId) ?: 0;
if ($oldLoad > 0) {
$redis->decr('cs:agent:load:' . $oldAdminId);
}
}
$redis->incr('cs:agent:load:' . $newAdminId);
return true;
}
/**
* 会话评价
*/
public function rateSession(int $sessionId, int $rating, ?string $content = null): bool
{
$session = ChatSession::find($sessionId);
if (!$session) {
return false;
}
// 评分范围 1-5
$rating = max(1, min(5, $rating));
ChatSession::where('id', $sessionId)->update([
'rating' => $rating,
'rating_content' => $content ? mb_substr($content, 0, 500) : null,
'update_time' => time(),
]);
return true;
}
/**
* 注册用户连接
*/
public function registerUserConnection(int $userId, int $fd): void
{
$redis = $this->getRedis();
$redis->setex(self::CONN_USER_PREFIX . $userId, 60, $fd);
}
/**
* 注册客服连接
*/
public function registerAgentConnection(int $adminId, int $fd): void
{
$redis = $this->getRedis();
$redis->setex(self::CONN_AGENT_PREFIX . $adminId, 60, $fd);
}
/**
* 刷新用户连接TTL
*/
public function refreshUserConnection(int $userId): void
{
$redis = $this->getRedis();
$redis->expire(self::CONN_USER_PREFIX . $userId, 60);
}
/**
* 刷新客服连接TTL
*/
public function refreshAgentConnection(int $adminId): void
{
$redis = $this->getRedis();
$redis->expire(self::CONN_AGENT_PREFIX . $adminId, 60);
}
/**
* 获取用户连接FD
*/
public function getUserFd(int $userId): ?int
{
$redis = $this->getRedis();
$fd = $redis->get(self::CONN_USER_PREFIX . $userId);
return $fd ? (int)$fd : null;
}
/**
* 获取客服连接FD
*/
public function getAgentFd(int $adminId): ?int
{
$redis = $this->getRedis();
$fd = $redis->get(self::CONN_AGENT_PREFIX . $adminId);
return $fd ? (int)$fd : null;
}
/**
* 清理用户连接
*/
public function clearUserConnection(int $userId): void
{
$redis = $this->getRedis();
$redis->del(self::CONN_USER_PREFIX . $userId);
}
/**
* 清理客服连接
*/
public function clearAgentConnection(int $adminId): void
{
$redis = $this->getRedis();
$redis->del(self::CONN_AGENT_PREFIX . $adminId);
}
/**
* 获取客服会话列表
*/
public function getAgentSessions(int $adminId): array
{
$sessions = ChatSession::getActiveByAdminId($adminId);
// 附加用户信息和未读数
foreach ($sessions as &$session) {
$user = Db::name('user')->where('id', $session['user_id'])->find();
$session['user_info'] = [
'username' => $user['username'] ?? '',
'nickname' => $user['nickname'] ?? $user['username'] ?? '',
'money' => $user['money'] ?? 0,
];
// 未读消息数
$session['unread_count'] = ChatMessage::where('session_id', $session['id'])
->where('sender_type', ChatMessage::SENDER_USER)
->where('status', '<', ChatMessage::STATUS_READ)
->count();
}
return $sessions;
}
/**
* 获取待分配会话列表
*/
public function getPendingSessions(): array
{
$sessions = ChatSession::where('status', ChatSession::STATUS_PENDING)
->order('create_time', 'asc')
->select()
->toArray();
foreach ($sessions as &$session) {
$user = Db::name('user')->where('id', $session['user_id'])->find();
$session['user_info'] = [
'username' => $user['username'] ?? '',
'nickname' => $user['nickname'] ?? $user['username'] ?? '',
'money' => $user['money'] ?? 0,
];
}
return $sessions;
}
/**
* 获取Redis实例
*/
private function getRedis(): \Redis
{
return Cache::store('redis')->handler();
}
}