310 lines
11 KiB
PHP
310 lines
11 KiB
PHP
<?php
|
||
|
||
/**
|
||
* 文件功能:好友系统控制器
|
||
*
|
||
* 处理聊天室内的好友关系管理:
|
||
* 1. 添加好友(addFriend)
|
||
* 2. 删除好友(removeFriend)
|
||
* 3. 查询与指定用户的好友关系(status)
|
||
* 4. 查询当前用户的好友列表(index)
|
||
*
|
||
* 好友关系模型:单向存储,互相添加才构成双向好友。
|
||
* 使用原版 friend_requests 表(字段:who / towho / sub_time)。
|
||
*
|
||
* @author ChatRoom Laravel
|
||
*
|
||
* @version 1.0.0
|
||
*/
|
||
|
||
namespace App\Http\Controllers;
|
||
|
||
use App\Events\FriendAdded;
|
||
use App\Events\FriendRemoved;
|
||
use App\Models\FriendRequest;
|
||
use App\Models\User;
|
||
use App\Services\ChatStateService;
|
||
use Illuminate\Http\JsonResponse;
|
||
use Illuminate\Http\Request;
|
||
use Illuminate\Support\Facades\Auth;
|
||
|
||
class FriendController extends Controller
|
||
{
|
||
/**
|
||
* 注入 Redis 状态服务,用于推送悄悄话通知。
|
||
*/
|
||
public function __construct(
|
||
private readonly ChatStateService $chatState,
|
||
) {}
|
||
|
||
/**
|
||
* 查询当前用户与目标用户的好友关系状态。
|
||
*
|
||
* 返回:
|
||
* - is_friend: 当前用户是否已将对方加为好友
|
||
* - mutual: 是否互相添加(双向好友)
|
||
*
|
||
* @param string $username 目标用户名
|
||
*/
|
||
public function status(string $username): JsonResponse
|
||
{
|
||
$me = Auth::user();
|
||
|
||
// 我是否已将对方加为好友
|
||
$iAdded = FriendRequest::where('who', $me->username)
|
||
->where('towho', $username)
|
||
->exists();
|
||
|
||
// 对方是否也将我加为好友
|
||
$theyAdded = FriendRequest::where('who', $username)
|
||
->where('towho', $me->username)
|
||
->exists();
|
||
|
||
return response()->json([
|
||
'is_friend' => $iAdded,
|
||
'mutual' => $iAdded && $theyAdded,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 添加好友。
|
||
*
|
||
* 流程:
|
||
* 1. 校验目标用户存在、且不是自己
|
||
* 2. 检查是否已经添加过
|
||
* 3. 写入 friend_requests 记录
|
||
* 4. 检查是否互相好友(B 是否已将 A 加为好友)
|
||
* 5. 广播 FriendAdded 事件通知对方(携带互相状态)
|
||
* 6. 若对方在线,向对方发送正确的悄悄话
|
||
*
|
||
* @param string $username 目标用户名
|
||
*/
|
||
public function addFriend(Request $request, string $username): JsonResponse
|
||
{
|
||
$me = Auth::user();
|
||
|
||
// 不能加自己
|
||
if ($me->username === $username) {
|
||
return response()->json(['status' => 'error', 'message' => '不能将自己加为好友'], 422);
|
||
}
|
||
|
||
// 检查目标用户是否存在
|
||
$target = User::where('username', $username)->first();
|
||
if (! $target) {
|
||
return response()->json(['status' => 'error', 'message' => '用户不存在'], 404);
|
||
}
|
||
|
||
// 是否已添加
|
||
$exists = FriendRequest::where('who', $me->username)->where('towho', $username)->exists();
|
||
if ($exists) {
|
||
return response()->json(['status' => 'error', 'message' => '已是好友,无需重复添加'], 422);
|
||
}
|
||
|
||
// 写入好友关系(A → B)
|
||
FriendRequest::create([
|
||
'who' => $me->username,
|
||
'towho' => $username,
|
||
'sub_time' => now(),
|
||
]);
|
||
|
||
// 检查 B 是否已将 A 加为好友(互相好友判断)
|
||
$hasAddedBack = FriendRequest::where('who', $username)
|
||
->where('towho', $me->username)
|
||
->exists();
|
||
|
||
// 广播给对方(仅对方可见),携带是否已回加的状态;用数字 ID 作为频道名,避免中文名
|
||
broadcast(new FriendAdded($me->username, $username, $target->id, $hasAddedBack));
|
||
|
||
// 若对方在线,推送聊天区悄悄话(文案根据互相状态区分)
|
||
$this->notifyOnlineUser($username, $me->username, 'added', $request->input('room_id'), $hasAddedBack);
|
||
|
||
return response()->json([
|
||
'status' => 'success',
|
||
'message' => '已成功添加 '.$username.' 为好友 🎉',
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 删除好友。
|
||
*
|
||
* 流程:
|
||
* 1. 删除 friend_requests 中「我 → 对方」的记录
|
||
* 2. 检查对方是否也将我加为好友(之前是否互相)
|
||
* 3. 广播 FriendRemoved 事件通知对方
|
||
* 4. 若对方在线,向对方发送悄悄话
|
||
*
|
||
* @param string $username 目标用户名
|
||
*/
|
||
public function removeFriend(Request $request, string $username): JsonResponse
|
||
{
|
||
$me = Auth::user();
|
||
|
||
$deleted = FriendRequest::where('who', $me->username)
|
||
->where('towho', $username)
|
||
->delete();
|
||
|
||
if (! $deleted) {
|
||
return response()->json(['status' => 'error', 'message' => '好友关系不存在'], 404);
|
||
}
|
||
|
||
// 检查 B 之前是否也将 A 加为好友(删除前的互相状态)
|
||
$hadAddedBack = FriendRequest::where('who', $username)
|
||
->where('towho', $me->username)
|
||
->exists();
|
||
|
||
// 查询目标用户 ID(用于私有频道,避免中文名非法)
|
||
$targetUser = User::where('username', $username)->first();
|
||
|
||
// 广播给对方,携带之前的互相好友状态;用数字 ID 避免中文频道名
|
||
broadcast(new FriendRemoved($me->username, $username, $targetUser?->id ?? 0, $hadAddedBack));
|
||
|
||
// 若对方在线,推送聊天区悄悄话(文案根据互相状态区分)
|
||
$this->notifyOnlineUser($username, $me->username, 'removed', $request->input('room_id'), $hadAddedBack);
|
||
|
||
return response()->json([
|
||
'status' => 'success',
|
||
'message' => '已将 '.$username.' 从好友列表移除',
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 获取当前用户的完整好友数据,供好友面板使用。
|
||
*
|
||
* 返回两个列表:
|
||
* - friends:我已添加的好友(含互相状态、添加时间)
|
||
* - pending:对方已加我但我还未加对方的(含对方添加我的时间)
|
||
*/
|
||
public function index(): JsonResponse
|
||
{
|
||
$me = Auth::user();
|
||
|
||
// ── 我添加的好友及添加时间 ──
|
||
$myRows = FriendRequest::where('who', $me->username)
|
||
->get(['towho', 'sub_time'])
|
||
->keyBy('towho');
|
||
|
||
// ── 把我加了的人(用于互相判断 + pending 列表)──
|
||
$addedMeRows = FriendRequest::where('towho', $me->username)
|
||
->get(['who', 'sub_time'])
|
||
->keyBy('who');
|
||
|
||
$myAddedNames = $myRows->keys();
|
||
$addedMeNames = $addedMeRows->keys();
|
||
|
||
// ── 查询全局在线用户(所有房间合并)──
|
||
$onlineUsernames = collect($this->chatState->getAllOnlineUsernames());
|
||
|
||
// 我添加的好友详情
|
||
$friends = User::whereIn('username', $myAddedNames)
|
||
->get(['username', 'usersf', 'user_level', 'sex'])
|
||
->map(function ($u) use ($myRows, $addedMeNames, $onlineUsernames) {
|
||
$row = $myRows->get($u->username);
|
||
|
||
return [
|
||
'username' => $u->username,
|
||
'headface' => $u->headface,
|
||
'user_level' => $u->user_level,
|
||
'sex' => $u->sex,
|
||
'mutual' => $addedMeNames->contains($u->username), // 是否互相添加
|
||
'sub_time' => $row?->sub_time?->format('Y-m-d H:i') ?? '',
|
||
'is_online' => $onlineUsernames->contains($u->username),
|
||
];
|
||
})
|
||
->sortByDesc('is_online') // 在线好友排在前面
|
||
->values();
|
||
|
||
// 对方加了我但我还未加的(pending)
|
||
$pendingNames = $addedMeNames->diff($myAddedNames);
|
||
$pending = User::whereIn('username', $pendingNames)
|
||
->get(['username', 'usersf', 'user_level', 'sex'])
|
||
->map(function ($u) use ($addedMeRows, $onlineUsernames) {
|
||
$row = $addedMeRows->get($u->username);
|
||
|
||
return [
|
||
'username' => $u->username,
|
||
'headface' => $u->headface,
|
||
'user_level' => $u->user_level,
|
||
'sex' => $u->sex,
|
||
'added_at' => $row?->sub_time?->format('Y-m-d H:i') ?? '',
|
||
'is_online' => $onlineUsernames->contains($u->username),
|
||
];
|
||
})
|
||
->sortByDesc('is_online')
|
||
->values();
|
||
|
||
return response()->json([
|
||
'status' => 'success',
|
||
'friends' => $friends,
|
||
'pending' => $pending,
|
||
]);
|
||
}
|
||
|
||
/**
|
||
* 若目标用户在线,向其发送系统悄悄话通知。
|
||
*
|
||
* 根据 $action 和 $mutual 显示不同文案,避免「你们已是好友」的误导提示。
|
||
*
|
||
* @param string $targetUsername 接收通知的用户名
|
||
* @param string $fromUsername 发起操作的用户名
|
||
* @param string $action 'added' | 'removed' | 'online'
|
||
* @param int|null $roomId 当前房间 ID
|
||
* @param bool $mutual 是否互相好友(added: B 是否已回加;removed: 之前是否互相)
|
||
*/
|
||
private function notifyOnlineUser(
|
||
string $targetUsername,
|
||
string $fromUsername,
|
||
string $action,
|
||
?int $roomId = null,
|
||
bool $mutual = false,
|
||
): void {
|
||
if (! $roomId) {
|
||
return;
|
||
}
|
||
|
||
// 检查对方是否在该房间在线
|
||
$onlineUsers = $this->chatState->getRoomUsers($roomId);
|
||
if (! isset($onlineUsers[$targetUsername])) {
|
||
return;
|
||
}
|
||
|
||
// 根据操作类型和互相状态生成不同文案(含内联快捷操作链接)
|
||
$btnStyle = 'font-weight:bold;text-decoration:underline;margin-left:6px;';
|
||
$btnAdd = "<a href='#' onclick=\"quickFriendAction('add','{$fromUsername}',this);return false;\" style='color:#16a34a;{$btnStyle}'>➕ 回加好友</a>";
|
||
$btnRemove = "<a href='#' onclick=\"quickFriendAction('remove','{$fromUsername}',this);return false;\" style='color:#6b7280;{$btnStyle}'>🗑️ 同步移除</a>";
|
||
|
||
$content = match ($action) {
|
||
'added' => $mutual
|
||
? "💚 <b>{$fromUsername}</b> 将你加为好友了!你们现在互为好友 🎉"
|
||
: "💚 <b>{$fromUsername}</b> 将你加为好友了!但你还没有添加对方为好友。{$btnAdd}",
|
||
'removed' => $mutual
|
||
? "💔 <b>{$fromUsername}</b> 已将你从好友列表移除。你的好友列表中仍保留对方。{$btnRemove}"
|
||
: "💔 <b>{$fromUsername}</b> 已将你从他的好友列表移除。",
|
||
'online' => "🟢 你的好友 <b>{$fromUsername}</b> 上线啦!",
|
||
default => '',
|
||
};
|
||
|
||
if (! $content) {
|
||
return;
|
||
}
|
||
|
||
// 删除相关用灰色,其他用绿色
|
||
$fontColor = $action === 'removed' ? '#6b7280' : '#16a34a';
|
||
|
||
// 构建系统悄悄话消息
|
||
$msg = [
|
||
'id' => $this->chatState->nextMessageId($roomId),
|
||
'room_id' => $roomId,
|
||
'from_user' => '系统',
|
||
'to_user' => $targetUsername,
|
||
'content' => $content,
|
||
'is_secret' => true,
|
||
'font_color' => $fontColor,
|
||
'action' => '',
|
||
'sent_at' => now()->toDateTimeString(),
|
||
];
|
||
|
||
$this->chatState->pushMessage($roomId, $msg);
|
||
broadcast(new \App\Events\MessageSent($roomId, $msg));
|
||
}
|
||
}
|