diff --git a/app/Http/Controllers/Auth/Admin/Permission.php b/app/Http/Controllers/Auth/Admin/Permission.php index 0582a9d..3a294c9 100644 --- a/app/Http/Controllers/Auth/Admin/Permission.php +++ b/app/Http/Controllers/Auth/Admin/Permission.php @@ -89,12 +89,28 @@ class Permission extends Controller 'type' => 'required|in:menu,api,button', 'route' => 'nullable|string|max:200', 'component' => 'nullable|string|max:200', - 'parent_id' => 'nullable|integer|exists:auth_permissions,id', + 'parent_id' => 'nullable|integer|min:0', 'sort' => 'nullable|integer|min:0', 'status' => 'nullable|integer|in:0,1', 'meta' => 'nullable|array', + ], [], [ + 'parent_id.exists' => '父级权限不存在', ]); + // 额外验证:如果 parent_id 不为 0,则必须存在 + if (!empty($validated['parent_id']) && $validated['parent_id'] != 0) { + $parent = \App\Models\Auth\Permission::find($validated['parent_id']); + if (!$parent) { + return response()->json([ + 'code' => 422, + 'message' => '验证失败', + 'data' => [ + 'parent_id' => ['父级权限不存在'] + ] + ], 422); + } + } + $result = $this->permissionService->create($validated); return response()->json([ @@ -115,12 +131,28 @@ class Permission extends Controller 'type' => 'nullable|in:menu,api,button', 'route' => 'nullable|string|max:200', 'component' => 'nullable|string|max:200', - 'parent_id' => 'nullable|integer|exists:auth_permissions,id', + 'parent_id' => 'nullable|integer|min:0', 'sort' => 'nullable|integer|min:0', 'status' => 'nullable|integer|in:0,1', 'meta' => 'nullable|array', + ], [], [ + 'parent_id.exists' => '父级权限不存在', ]); + // 额外验证:如果 parent_id 不为 0,则必须存在 + if (isset($validated['parent_id']) && !empty($validated['parent_id']) && $validated['parent_id'] != 0) { + $parent = \App\Models\Auth\Permission::find($validated['parent_id']); + if (!$parent) { + return response()->json([ + 'code' => 422, + 'message' => '验证失败', + 'data' => [ + 'parent_id' => ['父级权限不存在'] + ] + ], 422); + } + } + $result = $this->permissionService->update($id, $validated); return response()->json([ diff --git a/app/Http/Controllers/System/Admin/Notification.php b/app/Http/Controllers/System/Admin/Notification.php index fabcfc3..7e9f564 100644 --- a/app/Http/Controllers/System/Admin/Notification.php +++ b/app/Http/Controllers/System/Admin/Notification.php @@ -55,16 +55,16 @@ class Notification extends Controller public function unread(Request $request): JsonResponse { $userId = auth('admin')->id(); - $limit = $request->input('limit', 10); + $limit = $request->input('limit', $request->input('page_size', 10)); + $page = $request->input('page', 1); + $type = $request->input('type'); - $notifications = $this->notificationService->getUnreadNotifications($userId, $limit); + $result = $this->notificationService->getUnreadNotifications($userId, $limit, $page, $type); return response()->json([ 'code' => 200, 'message' => 'success', - 'data' => [ - 'list' => $notifications - ] + 'data' => $result ]); } diff --git a/app/Services/Auth/UserService.php b/app/Services/Auth/UserService.php index e8d619a..8748e43 100644 --- a/app/Services/Auth/UserService.php +++ b/app/Services/Auth/UserService.php @@ -222,9 +222,9 @@ class UserService DB::commit(); // 发送更新通知(如果更新的是其他用户) - // if ($id !== $this->getCurrentUserId()) { + if ($id !== $this->getCurrentUserId()) { $this->sendUserUpdateNotification($user, $data); - // } + } return $user; } catch (\Exception $e) { diff --git a/app/Services/System/NotificationService.php b/app/Services/System/NotificationService.php index 7b60d12..0f62f61 100644 --- a/app/Services/System/NotificationService.php +++ b/app/Services/System/NotificationService.php @@ -86,16 +86,31 @@ class NotificationService * * @param int $userId * @param int $limit + * @param int $page + * @param string|null $type * @return array */ - public function getUnreadNotifications(int $userId, int $limit = 10): array + public function getUnreadNotifications(int $userId, int $limit = 10, int $page = 1, ?string $type = null): array { - return Notification::where('user_id', $userId) - ->where('is_read', false) - ->orderBy('created_at', 'desc') - ->limit($limit) - ->get() - ->toArray(); + $query = Notification::where('user_id', $userId) + ->where('is_read', false); + + // 按类型过滤 + if (!empty($type)) { + $query->where('type', $type); + } + + $query->orderBy('created_at', 'desc'); + + // 分页处理 + $list = $query->paginate($limit, ['*'], 'page', $page); + + return [ + 'list' => $list->items(), + 'total' => $list->total(), + 'page' => $list->currentPage(), + 'page_size' => $list->perPage(), + ]; } /** diff --git a/app/Services/WebSocket/WebSocketHandler.php b/app/Services/WebSocket/WebSocketHandler.php index 2c865f9..22e5301 100644 --- a/app/Services/WebSocket/WebSocketHandler.php +++ b/app/Services/WebSocket/WebSocketHandler.php @@ -58,7 +58,7 @@ class WebSocketHandler implements WebSocketHandlerInterface // 用户认证 if (!$userId || !$token) { - $server->push($request->fd, json_encode([ + $this->safePush($server, $request->fd, json_encode([ 'type' => 'error', 'data' => [ 'message' => '认证失败:缺少 user_id 或 token', @@ -82,7 +82,7 @@ class WebSocketHandler implements WebSocketHandlerInterface 'query_user_id' => $userId ]); - $server->push($request->fd, json_encode([ + $this->safePush($server, $request->fd, json_encode([ 'type' => 'error', 'data' => [ 'message' => '认证失败:用户 ID 不匹配', @@ -102,7 +102,7 @@ class WebSocketHandler implements WebSocketHandlerInterface 'current_time' => time() ]); - $server->push($request->fd, json_encode([ + $this->safePush($server, $request->fd, json_encode([ 'type' => 'error', 'data' => [ 'message' => '认证失败:token 已过期', @@ -124,7 +124,7 @@ class WebSocketHandler implements WebSocketHandlerInterface 'error' => $e->getMessage() ]); - $server->push($request->fd, json_encode([ + $this->safePush($server, $request->fd, json_encode([ 'type' => 'error', 'data' => [ 'message' => '认证失败:无效的 token', @@ -148,7 +148,7 @@ class WebSocketHandler implements WebSocketHandlerInterface ]); // 发送欢迎消息 - $server->push($request->fd, json_encode([ + $this->safePush($server, $request->fd, json_encode([ 'type' => 'connected', 'data' => [ 'message' => '欢迎连接到 LaravelS WebSocket', @@ -170,7 +170,7 @@ class WebSocketHandler implements WebSocketHandlerInterface 'fd' => $request->fd ]); - $server->push($request->fd, json_encode([ + $this->safePush($server, $request->fd, json_encode([ 'type' => 'error', 'data' => [ 'message' => '连接错误:' . $e->getMessage(), @@ -181,6 +181,30 @@ class WebSocketHandler implements WebSocketHandlerInterface } } + /** + * 安全的推送消息(检查连接是否建立) + * + * @param Server $server WebSocket 服务器对象 + * @param int $fd 文件描述符 + * @param string $data 要发送的数据 + * @return bool 是否发送成功 + */ + protected function safePush(Server $server, int $fd, string $data): bool + { + try { + if ($server->isEstablished($fd)) { + return $server->push($fd, $data); + } + return false; + } catch (\Exception $e) { + Log::warning('WebSocket push 失败', [ + 'fd' => $fd, + 'error' => $e->getMessage() + ]); + return false; + } + } + /** * 处理接收消息事件 * @@ -207,7 +231,7 @@ class WebSocketHandler implements WebSocketHandlerInterface $message = json_decode($frame->data, true); if (!$message || !isset($message['type'])) { - $server->push($frame->fd, json_encode([ + $this->safePush($server, $frame->fd, json_encode([ 'type' => 'error', 'data' => [ 'message' => '无效的消息格式', @@ -230,7 +254,7 @@ class WebSocketHandler implements WebSocketHandlerInterface switch ($type) { case 'ping': // 响应 ping - $server->push($frame->fd, json_encode([ + $this->safePush($server, $frame->fd, json_encode([ 'type' => 'pong', 'data' => $data ])); @@ -238,7 +262,7 @@ class WebSocketHandler implements WebSocketHandlerInterface case 'heartbeat': // 心跳确认 - $server->push($frame->fd, json_encode([ + $this->safePush($server, $frame->fd, json_encode([ 'type' => 'heartbeat_ack', 'data' => array_merge($data, [ 'timestamp' => time() @@ -268,7 +292,7 @@ class WebSocketHandler implements WebSocketHandlerInterface default: // 未知消息类型 - $server->push($frame->fd, json_encode([ + $this->safePush($server, $frame->fd, json_encode([ 'type' => 'error', 'data' => [ 'message' => '未知的消息类型:' . $type, @@ -345,7 +369,7 @@ class WebSocketHandler implements WebSocketHandlerInterface $toUserId = $data['to_user_id'] ?? 0; if (!$toUserId) { - $server->push($frame->fd, json_encode([ + $this->safePush($server, $frame->fd, json_encode([ 'type' => 'error', 'data' => [ 'message' => '缺少 to_user_id', @@ -359,7 +383,7 @@ class WebSocketHandler implements WebSocketHandlerInterface $recipientInfo = $wsTable->get('uid:' . $toUserId); if ($recipientInfo === false) { - $server->push($frame->fd, json_encode([ + $this->safePush($server, $frame->fd, json_encode([ 'type' => 'error', 'data' => [ 'message' => '用户不在线', @@ -373,7 +397,7 @@ class WebSocketHandler implements WebSocketHandlerInterface $toFd = (int)$recipientInfo['value']; // 发送消息给接收者 - $server->push($toFd, json_encode([ + $this->safePush($server, $toFd, json_encode([ 'type' => 'chat', 'data' => array_merge($data, [ 'from_user_id' => $fromUserId, @@ -413,9 +437,7 @@ class WebSocketHandler implements WebSocketHandlerInterface continue; } - if ($server->isEstablished($fd)) { - $server->push($fd, $message); - } + $this->safePush($server, $fd, $message); } } } @@ -435,7 +457,7 @@ class WebSocketHandler implements WebSocketHandlerInterface $channel = $data['channel'] ?? ''; if (!$channel) { - $server->push($frame->fd, json_encode([ + $this->safePush($server, $frame->fd, json_encode([ 'type' => 'error', 'data' => [ 'message' => '缺少频道名称', @@ -452,7 +474,7 @@ class WebSocketHandler implements WebSocketHandlerInterface 'expiry' => time() + 3600 ]); - $server->push($frame->fd, json_encode([ + $this->safePush($server, $frame->fd, json_encode([ 'type' => 'subscribed', 'data' => [ 'channel' => $channel, @@ -483,7 +505,7 @@ class WebSocketHandler implements WebSocketHandlerInterface $channel = $data['channel'] ?? ''; if (!$channel) { - $server->push($frame->fd, json_encode([ + $this->safePush($server, $frame->fd, json_encode([ 'type' => 'error', 'data' => [ 'message' => '缺少频道名称', @@ -497,7 +519,7 @@ class WebSocketHandler implements WebSocketHandlerInterface $channelKey = 'channel:' . $channel . ':fd:' . $frame->fd; $wsTable->del($channelKey); - $server->push($frame->fd, json_encode([ + $this->safePush($server, $frame->fd, json_encode([ 'type' => 'unsubscribed', 'data' => [ 'channel' => $channel, diff --git a/app/Services/WebSocket/WebSocketService.php b/app/Services/WebSocket/WebSocketService.php index 4e40b53..25a6f25 100644 --- a/app/Services/WebSocket/WebSocketService.php +++ b/app/Services/WebSocket/WebSocketService.php @@ -125,7 +125,7 @@ class WebSocketService if (strpos($key, 'uid:') !== 0) { continue; } - + Log::info($key . json_encode($row)); $userId = (int)substr($key, 4); // 移除 'uid:' 前缀 $fd = (int)$row['value']; diff --git a/resources/admin/src/App.vue b/resources/admin/src/App.vue index f8a257e..255cb7b 100644 --- a/resources/admin/src/App.vue +++ b/resources/admin/src/App.vue @@ -5,7 +5,7 @@ import { useI18nStore } from "./stores/modules/i18n"; import { useLayoutStore } from "./stores/modules/layout"; import { useUserStore } from "./stores/modules/user"; import { useMessageStore } from "./stores/modules/message"; -import { useWebSocket } from "./composables/useWebSocket"; +import { useWebSocket } from "./hooks/useWebSocket"; import { theme } from "ant-design-vue"; import i18n from "./i18n"; import zhCN from "ant-design-vue/es/locale/zh_CN"; diff --git a/resources/admin/src/composables/useWebSocket.js b/resources/admin/src/hooks/useWebSocket.js similarity index 100% rename from resources/admin/src/composables/useWebSocket.js rename to resources/admin/src/hooks/useWebSocket.js