Files
2026-06-11 16:24:22 +08:00

303 lines
11 KiB
PHP

<?php
namespace app\common\service\chat;
use app\common\model\chat\PrivateChatMessage;
use app\common\model\chat\PrivateChatSession;
use app\common\model\community\CommunityFollow;
use app\common\model\user\User;
use think\facade\Db;
class PrivateChatService
{
public const MESSAGE_TYPE_TEXT = PrivateChatMessage::TYPE_TEXT;
public const MESSAGE_TYPE_IMAGE = PrivateChatMessage::TYPE_IMAGE;
public const RELATIONSHIP_NOT_MUTUAL_TEXT = '对方未添加好友';
public static function open(int $userId, int $targetUserId, bool $requireFollow = true): array
{
if ($userId <= 0) {
return self::fail('请先登录');
}
if ($targetUserId <= 0) {
return self::fail('请选择要私聊的用户');
}
if ($userId === $targetUserId) {
return self::fail('不能和自己私聊');
}
$target = User::field('id,sn,nickname,avatar')->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 ? '互相关注' : self::RELATIONSHIP_NOT_MUTUAL_TEXT,
];
}
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];
}
}