This commit is contained in:
2026-02-18 19:41:03 +08:00
parent a0c2350662
commit 6543e2ccdd
18 changed files with 4885 additions and 1196 deletions

View File

@@ -0,0 +1,548 @@
<?php
namespace App\Services\System;
use App\Models\System\Notification;
use App\Services\WebSocket\WebSocketService;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
class NotificationService
{
/**
* @var WebSocketService
*/
protected $webSocketService;
/**
* NotificationService constructor
*/
public function __construct()
{
$this->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)
);
}
}