no message
This commit is contained in:
@@ -0,0 +1,301 @@
|
||||
<?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 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 ? '互相关注' : '不是好友',
|
||||
];
|
||||
}
|
||||
|
||||
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];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user