327 lines
8.3 KiB
PHP
327 lines
8.3 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;
|
|
|
|
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($userId, $source)
|
|
{
|
|
$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($userId)
|
|
{
|
|
return ChatSession::getActiveByUserId($userId);
|
|
}
|
|
|
|
/**
|
|
* 获取会话详情(含用户信息)
|
|
*/
|
|
public function getSessionDetail($sessionId)
|
|
{
|
|
$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($sessionId, $operatorId)
|
|
{
|
|
$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($sessionId, $newAdminId)
|
|
{
|
|
$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($sessionId, $rating, $content = null)
|
|
{
|
|
$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($userId, $fd)
|
|
{
|
|
$redis = $this->getRedis();
|
|
$redis->setex(self::CONN_USER_PREFIX . $userId, 60, $fd);
|
|
}
|
|
|
|
/**
|
|
* 注册客服连接
|
|
*/
|
|
public function registerAgentConnection($adminId, $fd)
|
|
{
|
|
$redis = $this->getRedis();
|
|
$redis->setex(self::CONN_AGENT_PREFIX . $adminId, 60, $fd);
|
|
}
|
|
|
|
/**
|
|
* 刷新用户连接TTL
|
|
*/
|
|
public function refreshUserConnection($userId)
|
|
{
|
|
$redis = $this->getRedis();
|
|
$redis->expire(self::CONN_USER_PREFIX . $userId, 60);
|
|
}
|
|
|
|
/**
|
|
* 刷新客服连接TTL
|
|
*/
|
|
public function refreshAgentConnection($adminId)
|
|
{
|
|
$redis = $this->getRedis();
|
|
$redis->expire(self::CONN_AGENT_PREFIX . $adminId, 60);
|
|
}
|
|
|
|
/**
|
|
* 获取用户连接FD
|
|
*/
|
|
public function getUserFd($userId): ?int
|
|
{
|
|
$redis = $this->getRedis();
|
|
$fd = $redis->get(self::CONN_USER_PREFIX . $userId);
|
|
return $fd ? (int)$fd : null;
|
|
}
|
|
|
|
/**
|
|
* 获取客服连接FD
|
|
*/
|
|
public function getAgentFd($adminId): ?int
|
|
{
|
|
$redis = $this->getRedis();
|
|
$fd = $redis->get(self::CONN_AGENT_PREFIX . $adminId);
|
|
return $fd ? (int)$fd : null;
|
|
}
|
|
|
|
/**
|
|
* 清理用户连接
|
|
*/
|
|
public function clearUserConnection($userId)
|
|
{
|
|
$redis = $this->getRedis();
|
|
$redis->del(self::CONN_USER_PREFIX . $userId);
|
|
}
|
|
|
|
/**
|
|
* 清理客服连接
|
|
*/
|
|
public function clearAgentConnection($adminId)
|
|
{
|
|
$redis = $this->getRedis();
|
|
$redis->del(self::CONN_AGENT_PREFIX . $adminId);
|
|
}
|
|
|
|
/**
|
|
* 获取客服会话列表
|
|
*/
|
|
public function getAgentSessions($adminId)
|
|
{
|
|
$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()
|
|
{
|
|
$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()
|
|
{
|
|
return Cache::store('redis')->handler();
|
|
}
|
|
}
|