webSocketService = app(WebSocketService::class); } /** * 获取通知列表 * * @param array $params * @return array */ public function getList(array $params): array { $query = Notification::query(); // 过滤用户ID if (!empty($params['user_id'])) { $query->where('user_id', $params['user_id']); } // 关键字搜索 if (!empty($params['keyword'])) { $query->where(function ($q) use ($params) { $q->where('title', 'like', '%' . $params['keyword'] . '%') ->orWhere('content', 'like', '%' . $params['keyword'] . '%'); }); } // 过滤阅读状态 if (isset($params['is_read']) && $params['is_read'] !== '') { $query->where('is_read', $params['is_read']); } // 过滤通知类型 if (!empty($params['type'])) { $query->where('type', $params['type']); } // 过滤通知分类 if (!empty($params['category'])) { $query->where('category', $params['category']); } // 日期范围 if (!empty($params['start_date'])) { $query->where('created_at', '>=', $params['start_date']); } if (!empty($params['end_date'])) { $query->where('created_at', '<=', $params['end_date']); } $query->orderBy('created_at', 'desc'); $pageSize = $params['page_size'] ?? 20; $list = $query->paginate($pageSize); return [ 'list' => $list->items(), 'total' => $list->total(), 'page' => $list->currentPage(), 'page_size' => $list->perPage(), ]; } /** * 获取未读通知列表 * * @param int $userId * @param int $limit * @return array */ public function getUnreadNotifications(int $userId, int $limit = 10): array { return Notification::where('user_id', $userId) ->where('is_read', false) ->orderBy('created_at', 'desc') ->limit($limit) ->get() ->toArray(); } /** * 获取未读通知数量 * * @param int $userId * @return int */ public function getUnreadCount(int $userId): int { return Notification::where('user_id', $userId) ->where('is_read', false) ->count(); } /** * 根据ID获取通知 * * @param int $id * @return Notification|null */ public function getById(int $id): ?Notification { return Notification::find($id); } /** * 创建通知 * * @param array $data * @return Notification */ public function create(array $data): Notification { $notification = Notification::create($data); // 如果用户在线,立即通过WebSocket发送 if ($this->webSocketService->isUserOnline($data['user_id'])) { $this->sendViaWebSocket($notification); } return $notification; } /** * 批量创建通知 * * @param array $notificationsData * @return array */ public function batchCreate(array $notificationsData): array { $notifications = []; DB::beginTransaction(); try { foreach ($notificationsData as $data) { $notification = Notification::create($data); $notifications[] = $notification; // 如果用户在线,立即通过WebSocket发送 if ($this->webSocketService->isUserOnline($data['user_id'])) { $this->sendViaWebSocket($notification); } } DB::commit(); } catch (\Exception $e) { DB::rollBack(); Log::error('批量创建通知失败', [ 'error' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); throw $e; } return $notifications; } /** * 发送系统通知给单个用户 * * @param int $userId * @param string $title * @param string $content * @param string $type * @param string $category * @param array $extraData * @return Notification */ public function sendToUser( int $userId, string $title, string $content, string $type = Notification::TYPE_INFO, string $category = Notification::CATEGORY_SYSTEM, array $extraData = [] ): Notification { $data = [ 'user_id' => $userId, 'title' => $title, 'content' => $content, 'type' => $type, 'category' => $category, 'data' => $extraData, 'is_read' => false, ]; return $this->create($data); } /** * 发送系统通知给多个用户 * * @param array $userIds * @param string $title * @param string $content * @param string $type * @param string $category * @param array $extraData * @return array */ public function sendToUsers( array $userIds, string $title, string $content, string $type = Notification::TYPE_INFO, string $category = Notification::CATEGORY_SYSTEM, array $extraData = [] ): array { $notificationsData = []; foreach ($userIds as $userId) { $notificationsData[] = [ 'user_id' => $userId, 'title' => $title, 'content' => $content, 'type' => $type, 'category' => $category, 'data' => $extraData, 'is_read' => false, 'created_at' => now(), 'updated_at' => now(), ]; } return $this->batchCreate($notificationsData); } /** * 发送系统广播通知(所有用户) * * @param string $title * @param string $content * @param string $type * @param string $category * @param array $extraData * @return array */ public function broadcast( string $title, string $content, string $type = Notification::TYPE_INFO, string $category = Notification::CATEGORY_ANNOUNCEMENT, array $extraData = [] ): array { // 获取所有用户ID $userIds = \App\Models\Auth\User::where('status', 1)->pluck('id')->toArray(); return $this->sendToUsers($userIds, $title, $content, $type, $category, $extraData); } /** * 通过WebSocket发送通知 * * @param Notification $notification * @return bool */ protected function sendViaWebSocket(Notification $notification): bool { $data = [ 'type' => 'notification', 'data' => [ 'id' => $notification->id, 'title' => $notification->title, 'content' => $notification->content, 'type' => $notification->type, 'category' => $notification->category, 'data' => $notification->data, 'action_type' => $notification->action_type, 'action_data' => $notification->action_data, 'timestamp' => $notification->created_at->timestamp, ] ]; $result = $this->webSocketService->sendToUser($notification->user_id, $data); if ($result) { $notification->markAsSent(); } else { $notification->incrementRetry(); } return $result; } /** * 重试发送未发送的通知 * * @param int $limit * @return int */ public function retryUnsentNotifications(int $limit = 100): int { $notifications = Notification::where('sent_via_websocket', false) ->where('retry_count', '<', 3) ->where('created_at', '>', now()->subHours(24)) ->orderBy('created_at', 'desc') ->limit($limit) ->get(); $sentCount = 0; foreach ($notifications as $notification) { if ($this->webSocketService->isUserOnline($notification->user_id)) { if ($this->sendViaWebSocket($notification)) { $sentCount++; } } } return $sentCount; } /** * 标记通知为已读 * * @param int $id * @param int $userId * @return bool */ public function markAsRead(int $id, int $userId): bool { $notification = Notification::where('id', $id) ->where('user_id', $userId) ->first(); if (!$notification) { return false; } return $notification->markAsRead(); } /** * 批量标记通知为已读 * * @param array $ids * @param int $userId * @return int */ public function batchMarkAsRead(array $ids, int $userId): int { return Notification::where('user_id', $userId) ->whereIn('id', $ids) ->update([ 'is_read' => true, 'read_at' => now(), ]); } /** * 标记所有通知为已读 * * @param int $userId * @return int */ public function markAllAsRead(int $userId): int { return Notification::where('user_id', $userId) ->where('is_read', false) ->update([ 'is_read' => true, 'read_at' => now(), ]); } /** * 删除通知 * * @param int $id * @param int $userId * @return bool */ public function delete(int $id, int $userId): bool { $notification = Notification::where('id', $id) ->where('user_id', $userId) ->first(); if (!$notification) { return false; } return $notification->delete(); } /** * 批量删除通知 * * @param array $ids * @param int $userId * @return int */ public function batchDelete(array $ids, int $userId): int { return Notification::where('user_id', $userId) ->whereIn('id', $ids) ->delete(); } /** * 清空已读通知 * * @param int $userId * @return int */ public function clearReadNotifications(int $userId): int { return Notification::where('user_id', $userId) ->where('is_read', true) ->delete(); } /** * 获取通知统计 * * @param int $userId * @return array */ public function getStatistics(int $userId): array { $total = Notification::where('user_id', $userId)->count(); $unread = Notification::where('user_id', $userId)->where('is_read', false)->count(); $read = Notification::where('user_id', $userId)->where('is_read', true)->count(); // 按类型统计 $byType = Notification::where('user_id', $userId) ->selectRaw('type, COUNT(*) as count') ->groupBy('type') ->pluck('count', 'type') ->toArray(); // 按分类统计 $byCategory = Notification::where('user_id', $userId) ->selectRaw('category, COUNT(*) as count') ->groupBy('category') ->pluck('count', 'category') ->toArray(); return [ 'total' => $total, 'unread' => $unread, 'read' => $read, 'by_type' => $byType, 'by_category' => $byCategory, ]; } /** * 发送任务通知 * * @param int $userId * @param string $title * @param string $content * @param array $taskData * @return Notification */ public function sendTaskNotification(int $userId, string $title, string $content, array $taskData = []): Notification { return $this->sendToUser( $userId, $title, $content, Notification::TYPE_TASK, Notification::CATEGORY_TASK, array_merge(['task' => $taskData], $taskData) ); } /** * 发送系统维护通知 * * @param string $title * @param string $content * @param array $maintenanceData * @return array */ public function sendMaintenanceNotification(string $title, string $content, array $maintenanceData = []): array { return $this->broadcast( $title, $content, Notification::TYPE_WARNING, Notification::CATEGORY_ANNOUNCEMENT, array_merge(['maintenance' => $maintenanceData], $maintenanceData) ); } /** * 发送新消息通知 * * @param int $userId * @param string $title * @param string $content * @param array $messageData * @return Notification */ public function sendNewMessageNotification(int $userId, string $title, string $content, array $messageData = []): Notification { return $this->sendToUser( $userId, $title, $content, Notification::TYPE_INFO, Notification::CATEGORY_MESSAGE, array_merge(['message' => $messageData], $messageData) ); } /** * 发送提醒通知 * * @param int $userId * @param string $title * @param string $content * @param array $reminderData * @return Notification */ public function sendReminderNotification(int $userId, string $title, string $content, array $reminderData = []): Notification { return $this->sendToUser( $userId, $title, $content, Notification::TYPE_WARNING, Notification::CATEGORY_REMINDER, array_merge(['reminder' => $reminderData], $reminderData) ); } }