303 lines
11 KiB
PHP
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];
|
|
}
|
|
}
|