Files
laravel_swoole/app/Services/System/NotificationService.php
2026-02-18 19:41:03 +08:00

549 lines
14 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?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)
);
}
}