where('id', $targetUserId)->findOrEmpty(); if ($target->isEmpty()) { return self::fail('用户不存在'); } $pair = self::pairIds($userId, $targetUserId); $session = PrivateChatSession::where([ 'user_a_id' => $pair[0], 'user_b_id' => $pair[1], ])->findOrEmpty(); if ($session->isEmpty()) { if ($requireFollow && !self::isFollowing($userId, $targetUserId)) { return self::fail('请先加好友后再私聊'); } $now = time(); $session = PrivateChatSession::create([ 'user_a_id' => $pair[0], 'user_b_id' => $pair[1], 'last_active_time' => $now, 'create_time' => $now, 'update_time' => $now, ]); } return self::success(self::formatSession($session->toArray(), $userId)); } public static function sessions(int $userId, int $page, int $size): array { if ($userId <= 0) { return ['lists' => [], 'page_no' => $page, 'page_size' => $size, 'count' => 0]; } $page = max(1, $page); $size = max(1, min(50, $size)); $query = PrivateChatSession::where(function ($query) use ($userId) { $query->where('user_a_id', $userId)->whereOr('user_b_id', $userId); }); $count = (clone $query)->count(); $rows = $query->order('last_active_time desc') ->order('id desc') ->page($page, $size) ->select() ->toArray(); return [ 'lists' => array_map(fn($row) => self::formatSession($row, $userId), $rows), 'page_no' => $page, 'page_size' => $size, 'count' => $count, ]; } public static function messages(int $userId, int $sessionId, int $afterId, int $page, int $size): array { $session = self::getOwnedSession($sessionId, $userId); if (!$session) { return self::fail('会话不存在或无权访问'); } $page = max(1, $page); $size = max(1, min(100, $size)); $query = PrivateChatMessage::where('session_id', $sessionId); if ($afterId > 0) { $query->where('id', '>', $afterId)->order('id', 'asc')->limit($size); } else { $query->order('id', 'desc')->page($page, $size); } $rows = $query->select()->toArray(); if ($afterId <= 0) { $rows = array_reverse($rows); } self::markRead($session, $userId); $session = PrivateChatSession::where('id', $sessionId)->findOrEmpty(); return self::success([ 'session' => self::formatSession($session->toArray(), $userId), 'lists' => array_map(fn($row) => self::formatMessage($row, $userId), $rows), 'page_no' => $page, 'page_size' => $size, ]); } public static function send(int $userId, int $sessionId, string $messageType, string $content): array { $session = self::getOwnedSession($sessionId, $userId); if (!$session) { return self::fail('会话不存在或无权访问'); } $messageType = trim($messageType) ?: self::MESSAGE_TYPE_TEXT; if (!in_array($messageType, [self::MESSAGE_TYPE_TEXT, self::MESSAGE_TYPE_IMAGE], true)) { return self::fail('消息类型错误'); } $content = trim($content); if ($content === '') { return self::fail($messageType === self::MESSAGE_TYPE_IMAGE ? '请选择图片' : '请输入消息内容'); } if ($messageType === self::MESSAGE_TYPE_TEXT) { if (mb_strlen($content) > 1000) { return self::fail('文字消息最多1000字'); } } else { $content = mb_substr($content, 0, 500); } $row = null; Db::startTrans(); try { $now = time(); $receiverId = self::targetUserId($session->toArray(), $userId); $row = PrivateChatMessage::create([ 'session_id' => $sessionId, 'sender_id' => $userId, 'receiver_id' => $receiverId, 'message_type' => $messageType, 'content' => $content, 'read_time' => 0, 'create_time' => $now, 'update_time' => $now, ]); $unreadField = ((int) $session->user_a_id === $receiverId) ? 'user_a_unread' : 'user_b_unread'; PrivateChatSession::where('id', $sessionId) ->inc($unreadField) ->update([ 'last_message_id' => (int) $row->id, 'last_message_type' => $messageType, 'last_message_content' => self::lastMessageContent($messageType, $content), 'last_sender_id' => $userId, 'last_active_time' => $now, 'update_time' => $now, ]); Db::commit(); } catch (\Throwable $e) { Db::rollback(); return self::fail('发送失败:' . $e->getMessage()); } return self::success(self::formatMessage($row->toArray(), $userId)); } public static function relationship(int $userId, int $targetUserId): array { $isFollowedByMe = $userId > 0 && $targetUserId > 0 && self::isFollowing($userId, $targetUserId); $isFollowingMe = $userId > 0 && $targetUserId > 0 && self::isFollowing($targetUserId, $userId); $isMutual = $isFollowedByMe && $isFollowingMe; return [ 'is_followed_by_me' => $isFollowedByMe, 'is_following_me' => $isFollowingMe, 'is_mutual' => $isMutual, 'relationship_text' => $isMutual ? '互相关注' : '不是好友', ]; } public static function unreadCount(int $userId): int { if ($userId <= 0) { return 0; } return (int) PrivateChatSession::where('user_a_id', $userId)->sum('user_a_unread') + (int) PrivateChatSession::where('user_b_id', $userId)->sum('user_b_unread'); } private static function getOwnedSession(int $sessionId, int $userId): ?PrivateChatSession { if ($sessionId <= 0 || $userId <= 0) { return null; } $session = PrivateChatSession::where('id', $sessionId) ->where(function ($query) use ($userId) { $query->where('user_a_id', $userId)->whereOr('user_b_id', $userId); }) ->findOrEmpty(); return $session->isEmpty() ? null : $session; } private static function formatSession(array $row, int $viewerId): array { $targetUserId = self::targetUserId($row, $viewerId); $target = User::field('id,sn,nickname,avatar')->where('id', $targetUserId)->findOrEmpty(); $unread = ((int) $row['user_a_id'] === $viewerId) ? (int) ($row['user_a_unread'] ?? 0) : (int) ($row['user_b_unread'] ?? 0); return [ 'id' => (int) $row['id'], 'target_user_id' => $targetUserId, 'target_user' => $target->isEmpty() ? null : $target->toArray(), 'last_message_id' => (int) ($row['last_message_id'] ?? 0), 'last_message_type' => (string) ($row['last_message_type'] ?? self::MESSAGE_TYPE_TEXT), 'last_message_content' => (string) ($row['last_message_content'] ?? ''), 'last_sender_id' => (int) ($row['last_sender_id'] ?? 0), 'unread_count' => $unread, 'last_active_time' => $row['last_active_time'] ?? 0, 'create_time' => $row['create_time'] ?? 0, 'relationship' => self::relationship($viewerId, $targetUserId), ]; } private static function formatMessage(array $row, int $viewerId): array { return [ 'id' => (int) $row['id'], 'session_id' => (int) $row['session_id'], 'sender_id' => (int) $row['sender_id'], 'receiver_id' => (int) $row['receiver_id'], 'message_type' => (string) $row['message_type'], 'content' => (string) ($row['content'] ?? ''), 'is_self' => (int) $row['sender_id'] === $viewerId, 'read_time' => (int) ($row['read_time'] ?? 0), 'create_time' => $row['create_time'] ?? 0, ]; } private static function markRead(PrivateChatSession $session, int $viewerId): void { $now = time(); PrivateChatMessage::where('session_id', (int) $session->id) ->where('receiver_id', $viewerId) ->where('read_time', 0) ->update(['read_time' => $now, 'update_time' => $now]); $field = ((int) $session->user_a_id === $viewerId) ? 'user_a_unread' : 'user_b_unread'; PrivateChatSession::where('id', (int) $session->id)->update([ $field => 0, 'update_time' => $now, ]); } private static function targetUserId(array $session, int $viewerId): int { return ((int) $session['user_a_id'] === $viewerId) ? (int) $session['user_b_id'] : (int) $session['user_a_id']; } private static function pairIds(int $userId, int $targetUserId): array { return $userId < $targetUserId ? [$userId, $targetUserId] : [$targetUserId, $userId]; } private static function isFollowing(int $userId, int $targetUserId): bool { return CommunityFollow::where([ 'user_id' => $userId, 'follow_user_id' => $targetUserId, ])->count() > 0; } private static function lastMessageContent(string $messageType, string $content): string { if ($messageType === self::MESSAGE_TYPE_IMAGE) { return '[图片]'; } return mb_substr($content, 0, 200); } private static function success(array $data): array { return ['success' => true, 'data' => $data]; } private static function fail(string $error): array { return ['success' => false, 'error' => $error]; } }