优化更新

This commit is contained in:
2026-02-18 22:28:08 +08:00
parent b6c133952b
commit 0ecb088569
8 changed files with 107 additions and 38 deletions

View File

@@ -89,12 +89,28 @@ class Permission extends Controller
'type' => 'required|in:menu,api,button', 'type' => 'required|in:menu,api,button',
'route' => 'nullable|string|max:200', 'route' => 'nullable|string|max:200',
'component' => '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', 'sort' => 'nullable|integer|min:0',
'status' => 'nullable|integer|in:0,1', 'status' => 'nullable|integer|in:0,1',
'meta' => 'nullable|array', '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); $result = $this->permissionService->create($validated);
return response()->json([ return response()->json([
@@ -115,12 +131,28 @@ class Permission extends Controller
'type' => 'nullable|in:menu,api,button', 'type' => 'nullable|in:menu,api,button',
'route' => 'nullable|string|max:200', 'route' => 'nullable|string|max:200',
'component' => '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', 'sort' => 'nullable|integer|min:0',
'status' => 'nullable|integer|in:0,1', 'status' => 'nullable|integer|in:0,1',
'meta' => 'nullable|array', '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); $result = $this->permissionService->update($id, $validated);
return response()->json([ return response()->json([

View File

@@ -55,16 +55,16 @@ class Notification extends Controller
public function unread(Request $request): JsonResponse public function unread(Request $request): JsonResponse
{ {
$userId = auth('admin')->id(); $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([ return response()->json([
'code' => 200, 'code' => 200,
'message' => 'success', 'message' => 'success',
'data' => [ 'data' => $result
'list' => $notifications
]
]); ]);
} }

View File

@@ -222,9 +222,9 @@ class UserService
DB::commit(); DB::commit();
// 发送更新通知(如果更新的是其他用户) // 发送更新通知(如果更新的是其他用户)
// if ($id !== $this->getCurrentUserId()) { if ($id !== $this->getCurrentUserId()) {
$this->sendUserUpdateNotification($user, $data); $this->sendUserUpdateNotification($user, $data);
// } }
return $user; return $user;
} catch (\Exception $e) { } catch (\Exception $e) {

View File

@@ -86,16 +86,31 @@ class NotificationService
* *
* @param int $userId * @param int $userId
* @param int $limit * @param int $limit
* @param int $page
* @param string|null $type
* @return array * @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) $query = Notification::where('user_id', $userId)
->where('is_read', false) ->where('is_read', false);
->orderBy('created_at', 'desc')
->limit($limit) // 按类型过滤
->get() if (!empty($type)) {
->toArray(); $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(),
];
} }
/** /**

View File

@@ -58,7 +58,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
// 用户认证 // 用户认证
if (!$userId || !$token) { if (!$userId || !$token) {
$server->push($request->fd, json_encode([ $this->safePush($server, $request->fd, json_encode([
'type' => 'error', 'type' => 'error',
'data' => [ 'data' => [
'message' => '认证失败:缺少 user_id 或 token', 'message' => '认证失败:缺少 user_id 或 token',
@@ -82,7 +82,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
'query_user_id' => $userId 'query_user_id' => $userId
]); ]);
$server->push($request->fd, json_encode([ $this->safePush($server, $request->fd, json_encode([
'type' => 'error', 'type' => 'error',
'data' => [ 'data' => [
'message' => '认证失败:用户 ID 不匹配', 'message' => '认证失败:用户 ID 不匹配',
@@ -102,7 +102,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
'current_time' => time() 'current_time' => time()
]); ]);
$server->push($request->fd, json_encode([ $this->safePush($server, $request->fd, json_encode([
'type' => 'error', 'type' => 'error',
'data' => [ 'data' => [
'message' => '认证失败token 已过期', 'message' => '认证失败token 已过期',
@@ -124,7 +124,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
'error' => $e->getMessage() 'error' => $e->getMessage()
]); ]);
$server->push($request->fd, json_encode([ $this->safePush($server, $request->fd, json_encode([
'type' => 'error', 'type' => 'error',
'data' => [ 'data' => [
'message' => '认证失败:无效的 token', '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', 'type' => 'connected',
'data' => [ 'data' => [
'message' => '欢迎连接到 LaravelS WebSocket', 'message' => '欢迎连接到 LaravelS WebSocket',
@@ -170,7 +170,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
'fd' => $request->fd 'fd' => $request->fd
]); ]);
$server->push($request->fd, json_encode([ $this->safePush($server, $request->fd, json_encode([
'type' => 'error', 'type' => 'error',
'data' => [ 'data' => [
'message' => '连接错误:' . $e->getMessage(), '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); $message = json_decode($frame->data, true);
if (!$message || !isset($message['type'])) { if (!$message || !isset($message['type'])) {
$server->push($frame->fd, json_encode([ $this->safePush($server, $frame->fd, json_encode([
'type' => 'error', 'type' => 'error',
'data' => [ 'data' => [
'message' => '无效的消息格式', 'message' => '无效的消息格式',
@@ -230,7 +254,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
switch ($type) { switch ($type) {
case 'ping': case 'ping':
// 响应 ping // 响应 ping
$server->push($frame->fd, json_encode([ $this->safePush($server, $frame->fd, json_encode([
'type' => 'pong', 'type' => 'pong',
'data' => $data 'data' => $data
])); ]));
@@ -238,7 +262,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
case 'heartbeat': case 'heartbeat':
// 心跳确认 // 心跳确认
$server->push($frame->fd, json_encode([ $this->safePush($server, $frame->fd, json_encode([
'type' => 'heartbeat_ack', 'type' => 'heartbeat_ack',
'data' => array_merge($data, [ 'data' => array_merge($data, [
'timestamp' => time() 'timestamp' => time()
@@ -268,7 +292,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
default: default:
// 未知消息类型 // 未知消息类型
$server->push($frame->fd, json_encode([ $this->safePush($server, $frame->fd, json_encode([
'type' => 'error', 'type' => 'error',
'data' => [ 'data' => [
'message' => '未知的消息类型:' . $type, 'message' => '未知的消息类型:' . $type,
@@ -345,7 +369,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
$toUserId = $data['to_user_id'] ?? 0; $toUserId = $data['to_user_id'] ?? 0;
if (!$toUserId) { if (!$toUserId) {
$server->push($frame->fd, json_encode([ $this->safePush($server, $frame->fd, json_encode([
'type' => 'error', 'type' => 'error',
'data' => [ 'data' => [
'message' => '缺少 to_user_id', 'message' => '缺少 to_user_id',
@@ -359,7 +383,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
$recipientInfo = $wsTable->get('uid:' . $toUserId); $recipientInfo = $wsTable->get('uid:' . $toUserId);
if ($recipientInfo === false) { if ($recipientInfo === false) {
$server->push($frame->fd, json_encode([ $this->safePush($server, $frame->fd, json_encode([
'type' => 'error', 'type' => 'error',
'data' => [ 'data' => [
'message' => '用户不在线', 'message' => '用户不在线',
@@ -373,7 +397,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
$toFd = (int)$recipientInfo['value']; $toFd = (int)$recipientInfo['value'];
// 发送消息给接收者 // 发送消息给接收者
$server->push($toFd, json_encode([ $this->safePush($server, $toFd, json_encode([
'type' => 'chat', 'type' => 'chat',
'data' => array_merge($data, [ 'data' => array_merge($data, [
'from_user_id' => $fromUserId, 'from_user_id' => $fromUserId,
@@ -413,9 +437,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
continue; continue;
} }
if ($server->isEstablished($fd)) { $this->safePush($server, $fd, $message);
$server->push($fd, $message);
}
} }
} }
} }
@@ -435,7 +457,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
$channel = $data['channel'] ?? ''; $channel = $data['channel'] ?? '';
if (!$channel) { if (!$channel) {
$server->push($frame->fd, json_encode([ $this->safePush($server, $frame->fd, json_encode([
'type' => 'error', 'type' => 'error',
'data' => [ 'data' => [
'message' => '缺少频道名称', 'message' => '缺少频道名称',
@@ -452,7 +474,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
'expiry' => time() + 3600 'expiry' => time() + 3600
]); ]);
$server->push($frame->fd, json_encode([ $this->safePush($server, $frame->fd, json_encode([
'type' => 'subscribed', 'type' => 'subscribed',
'data' => [ 'data' => [
'channel' => $channel, 'channel' => $channel,
@@ -483,7 +505,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
$channel = $data['channel'] ?? ''; $channel = $data['channel'] ?? '';
if (!$channel) { if (!$channel) {
$server->push($frame->fd, json_encode([ $this->safePush($server, $frame->fd, json_encode([
'type' => 'error', 'type' => 'error',
'data' => [ 'data' => [
'message' => '缺少频道名称', 'message' => '缺少频道名称',
@@ -497,7 +519,7 @@ class WebSocketHandler implements WebSocketHandlerInterface
$channelKey = 'channel:' . $channel . ':fd:' . $frame->fd; $channelKey = 'channel:' . $channel . ':fd:' . $frame->fd;
$wsTable->del($channelKey); $wsTable->del($channelKey);
$server->push($frame->fd, json_encode([ $this->safePush($server, $frame->fd, json_encode([
'type' => 'unsubscribed', 'type' => 'unsubscribed',
'data' => [ 'data' => [
'channel' => $channel, 'channel' => $channel,

View File

@@ -125,7 +125,7 @@ class WebSocketService
if (strpos($key, 'uid:') !== 0) { if (strpos($key, 'uid:') !== 0) {
continue; continue;
} }
Log::info($key . json_encode($row));
$userId = (int)substr($key, 4); // 移除 'uid:' 前缀 $userId = (int)substr($key, 4); // 移除 'uid:' 前缀
$fd = (int)$row['value']; $fd = (int)$row['value'];

View File

@@ -5,7 +5,7 @@ import { useI18nStore } from "./stores/modules/i18n";
import { useLayoutStore } from "./stores/modules/layout"; import { useLayoutStore } from "./stores/modules/layout";
import { useUserStore } from "./stores/modules/user"; import { useUserStore } from "./stores/modules/user";
import { useMessageStore } from "./stores/modules/message"; import { useMessageStore } from "./stores/modules/message";
import { useWebSocket } from "./composables/useWebSocket"; import { useWebSocket } from "./hooks/useWebSocket";
import { theme } from "ant-design-vue"; import { theme } from "ant-design-vue";
import i18n from "./i18n"; import i18n from "./i18n";
import zhCN from "ant-design-vue/es/locale/zh_CN"; import zhCN from "ant-design-vue/es/locale/zh_CN";