feat: 好友系统全实现
后端:
- FriendController:add/remove/status/index 四个接口
- FriendAdded / FriendRemoved 广播事件(私有频道)
- channels.php 注册 user.{username} 私有频道鉴权
- routes/web.php 注册好友路由
- ChatController::init() 修复 DutyLog 在 return 后执行的 bug
- ChatController::notifyFriendsOnline() 上线时悄悄话通知好友
前端:
- user-actions:写私信 → 加好友/删好友按钮(动态状态)
- toggleFriend() 方法 + fetchUser 后加载好友状态
- scripts:监听私有频道 FriendAdded/FriendRemoved
- showFriendToast() 右下角浮窗通知(5秒自动消失)
- global-dialog 加 fdSlideIn 动画
This commit is contained in:
234
app/Http/Controllers/FriendController.php
Normal file
234
app/Http/Controllers/FriendController.php
Normal file
@@ -0,0 +1,234 @@
|
||||
<?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. 广播 FriendAdded 事件通知对方
|
||||
* 5. 若对方当前在线(Redis),向对方发送悄悄话
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
// 写入好友关系
|
||||
FriendRequest::create([
|
||||
'who' => $me->username,
|
||||
'towho' => $username,
|
||||
'sub_time' => now(),
|
||||
]);
|
||||
|
||||
// 广播给对方(仅对方可见)
|
||||
broadcast(new FriendAdded($me->username, $username));
|
||||
|
||||
// 若对方在线,推送聊天区悄悄话
|
||||
$this->notifyOnlineUser($username, $me->username, 'added', $request->input('room_id'));
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => '已成功添加 '.$username.' 为好友 🎉',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除好友。
|
||||
*
|
||||
* 流程:
|
||||
* 1. 删除 friend_requests 中「我 → 对方」的记录
|
||||
* 2. 广播 FriendRemoved 事件通知对方
|
||||
* 3. 若对方在线,向对方发送悄悄话
|
||||
*
|
||||
* @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);
|
||||
}
|
||||
|
||||
// 广播给对方
|
||||
broadcast(new FriendRemoved($me->username, $username));
|
||||
|
||||
// 若对方在线,推送聊天区悄悄话
|
||||
$this->notifyOnlineUser($username, $me->username, 'removed', $request->input('room_id'));
|
||||
|
||||
return response()->json([
|
||||
'status' => 'success',
|
||||
'message' => '已将 '.$username.' 从好友列表移除',
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取当前用户的好友列表(我添加的 + 对方也添加我的 = 双向好友标记)。
|
||||
*/
|
||||
public function index(): JsonResponse
|
||||
{
|
||||
$me = Auth::user();
|
||||
|
||||
// 我添加的所有人
|
||||
$myAdded = FriendRequest::where('who', $me->username)->pluck('towho');
|
||||
|
||||
// 也把我加了的
|
||||
$addedMe = FriendRequest::where('towho', $me->username)->pluck('who');
|
||||
|
||||
$friends = User::whereIn('username', $myAdded)->get(['username', 'usersf', 'user_level', 'sex'])->map(function ($u) use ($addedMe) {
|
||||
return [
|
||||
'username' => $u->username,
|
||||
'headface' => $u->headface,
|
||||
'user_level' => $u->user_level,
|
||||
'sex' => $u->sex,
|
||||
'mutual' => $addedMe->contains($u->username), // 是否互相添加
|
||||
];
|
||||
});
|
||||
|
||||
return response()->json(['status' => 'success', 'friends' => $friends]);
|
||||
}
|
||||
|
||||
/**
|
||||
* 若目标用户在线,向其发送系统悄悄话通知。
|
||||
*
|
||||
* 好友上线/下线使用此方法,不公开广播,只有本人可见。
|
||||
*
|
||||
* @param string $targetUsername 接收通知的用户名
|
||||
* @param string $fromUsername 发起操作的用户名
|
||||
* @param string $action 'added' | 'removed' | 'online'
|
||||
* @param int|null $roomId 当前房间 ID(用于推送到对应房间频道)
|
||||
*/
|
||||
private function notifyOnlineUser(
|
||||
string $targetUsername,
|
||||
string $fromUsername,
|
||||
string $action,
|
||||
?int $roomId = null
|
||||
): void {
|
||||
if (! $roomId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查对方是否在该房间在线
|
||||
$onlineUsers = $this->chatState->getRoomUsers($roomId);
|
||||
if (! isset($onlineUsers[$targetUsername])) {
|
||||
return;
|
||||
}
|
||||
|
||||
$content = match ($action) {
|
||||
'added' => "💚 <b>{$fromUsername}</b> 将你加为好友了!你们现在是好友了 🎉",
|
||||
'removed' => "💔 <b>{$fromUsername}</b> 已将你从好友列表移除。",
|
||||
'online' => "🟢 你的好友 <b>{$fromUsername}</b> 上线啦!",
|
||||
default => '',
|
||||
};
|
||||
|
||||
if (! $content) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 构建系统悄悄话消息
|
||||
$msg = [
|
||||
'id' => $this->chatState->nextMessageId($roomId),
|
||||
'room_id' => $roomId,
|
||||
'from_user' => '系统',
|
||||
'to_user' => $targetUsername,
|
||||
'content' => $content,
|
||||
'is_secret' => true,
|
||||
'font_color' => '#16a34a',
|
||||
'action' => '',
|
||||
'sent_at' => now()->toDateTimeString(),
|
||||
];
|
||||
|
||||
$this->chatState->pushMessage($roomId, $msg);
|
||||
broadcast(new \App\Events\MessageSent($roomId, $msg));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user