input('platform')) { $query->where('Platform', $platform); } if ($channel = $request->input('channel')) { $query->where('Channel', $channel); } if ($installed = $request->input('installed')) { $query->where('Installed', $installed); } if ($status = $request->input('status', 1)) { $query->where('Status', $status); } if ($userid = $request->input('userid')) { $query->where('UserID', $userid); } $list = $query->orderBy('ID', 'desc')->paginate(20); // 统计数据 $stats = [ 'total' => DB::table('QPAccountsDB.dbo.WebPushSubscriptions')->where('Status', 1)->count(), 'android' => DB::table('QPAccountsDB.dbo.WebPushSubscriptions')->where('Status', 1)->where('Platform', 'Android')->count(), 'ios' => DB::table('QPAccountsDB.dbo.WebPushSubscriptions')->where('Status', 1)->where('Platform', 'iOS')->count(), 'installed' => DB::table('QPAccountsDB.dbo.WebPushSubscriptions')->where('Status', 1)->where('Installed', 1)->count(), 'today' => DB::table('QPAccountsDB.dbo.WebPushSubscriptions') ->where('Status', 1) ->whereRaw('CONVERT(date, CreatedAt) = CONVERT(date, GETDATE())') ->count(), ]; return view('admin.webpush.subscriptions', [ 'list' => $list, 'stats' => $stats, 'platform' => $request->input('platform', ''), 'channel' => $request->input('channel', ''), 'installed' => $request->input('installed', ''), 'status' => $request->input('status', '1'), 'userid' => $request->input('userid', ''), ]); } /** * 广播列表页面 */ public function broadcasts(Request $request) { $query = DB::table('QPAccountsDB.dbo.WebPushBroadcasts'); if ($status = $request->input('status')) { $query->where('Status', $status); } $list = $query->orderBy('ID', 'desc')->paginate(20); return view('admin.webpush.broadcasts', [ 'list' => $list, 'status' => $request->input('status', ''), ]); } /** * 创建广播页面 */ public function createBroadcast() { // 获取可选的渠道列表 $channels = DB::table('QPAccountsDB.dbo.WebPushSubscriptions') ->select('Channel') ->where('Status', 1) ->distinct() ->pluck('Channel'); return view('admin.webpush.create_broadcast', [ 'channels' => $channels, ]); } /** * 保存广播 */ public function storeBroadcast(Request $request) { $request->validate([ 'title' => 'required|max:255', 'body' => 'required', ]); try { $title = $request->input('title'); $body = $request->input('body'); $url = $request->input('url', ''); $icon = $request->input('icon', ''); $image = $request->input('image', ''); $targetType = $request->input('target_type', 'all'); $targetValue = $request->input('target_value', []); $filterInstalled = $request->input('filter_installed'); $sendNow = $request->input('send_now', false); // 计算目标数量 $query = DB::table('QPAccountsDB.dbo.WebPushSubscriptions') ->where('Status', 1); if ($targetType === 'channel' && !empty($targetValue)) { $query->whereIn('Channel', $targetValue); } elseif ($targetType === 'platform' && !empty($targetValue)) { $query->whereIn('Platform', $targetValue); } if ($filterInstalled !== null && $filterInstalled !== '') { $query->where('Installed', $filterInstalled); } $totalCount = $query->count(); if ($totalCount === 0) { return redirect()->back()->with('error', '没有找到符合条件的订阅用户'); } // 创建广播记录 $broadcastId = DB::table('QPAccountsDB.dbo.WebPushBroadcasts')->insertGetId([ 'Title' => $title, 'Body' => $body, 'Icon' => $icon, 'Image' => $image, 'URL' => $url, 'TargetType' => $targetType, 'TargetValue' => json_encode($targetValue), 'FilterInstalled' => $filterInstalled, 'Status' => $sendNow ? 'processing' : 'pending', 'TotalCount' => $totalCount, 'CreatedBy' => session('admin_user')['nickname'] ?? 'Admin', 'CreatedAt' => date('Y-m-d H:i:s'), 'UpdatedAt' => date('Y-m-d H:i:s') ]); // 如果立即发送 if ($sendNow) { $this->processBroadcast($broadcastId); return redirect()->route('admin.webpush.broadcasts')->with('success', "广播已发送,ID: {$broadcastId}, 目标用户: {$totalCount}"); } return redirect()->route('admin.webpush.broadcasts')->with('success', "广播已创建,ID: {$broadcastId}, 目标用户: {$totalCount}"); } catch (\Exception $e) { Log::error('Create broadcast error: ' . $e->getMessage()); return redirect()->back()->with('error', '创建失败: ' . $e->getMessage()); } } /** * 发送广播 */ public function sendBroadcast($id) { $broadcast = DB::table('QPAccountsDB.dbo.WebPushBroadcasts') ->where('ID', $id) ->first(); if (!$broadcast) { return redirect()->back()->with('error', '广播不存在'); } if ($broadcast->Status === 'completed') { return redirect()->back()->with('error', '该广播已发送'); } $result = $this->processBroadcast($id); if ($result['success']) { return redirect()->back()->with('success', $result['message']); } else { return redirect()->back()->with('error', $result['message']); } } /** * 禁用订阅 */ public function disableSubscription($id) { DB::table('QPAccountsDB.dbo.WebPushSubscriptions') ->where('ID', $id) ->update(['Status' => 0, 'UpdatedAt' => date('Y-m-d H:i:s')]); return redirect()->back()->with('success', '订阅已禁用'); } /** * 删除订阅 */ public function deleteSubscription($id) { DB::table('QPAccountsDB.dbo.WebPushSubscriptions') ->where('ID', $id) ->delete(); return redirect()->back()->with('success', '订阅已删除'); } /** * 发送日志 */ public function sendLogs(Request $request) { $broadcastId = $request->input('broadcast_id'); $query = DB::table('QPAccountsDB.dbo.WebPushSendLogs as wps') ->leftJoin('QPAccountsDB.dbo.WebPushSubscriptions as sub', 'wps.SubscriptionID', '=', 'sub.ID') ->select('wps.*', 'sub.UserID', 'sub.FPID', 'sub.Platform'); if ($broadcastId) { $query->where('wps.BroadcastID', $broadcastId); } $list = $query->orderBy('wps.ID', 'desc')->paginate(50); return view('admin.webpush.send_logs', [ 'list' => $list, 'broadcast_id' => $broadcastId ?? '', ]); } /** * 处理广播发送(核心逻辑) */ private function processBroadcast($broadcastId) { try { $broadcast = DB::table('QPAccountsDB.dbo.WebPushBroadcasts') ->where('ID', $broadcastId) ->first(); if (!$broadcast) { return ['success' => false, 'message' => '广播不存在']; } // 更新状态为处理中 DB::table('QPAccountsDB.dbo.WebPushBroadcasts') ->where('ID', $broadcastId) ->update([ 'Status' => 'processing', 'SentAt' => date('Y-m-d H:i:s'), 'UpdatedAt' => date('Y-m-d H:i:s') ]); // 获取目标订阅 $query = DB::table('QPAccountsDB.dbo.WebPushSubscriptions') ->where('Status', 1); if ($broadcast->TargetType === 'channel') { $channels = json_decode($broadcast->TargetValue, true); $query->whereIn('Channel', $channels); } elseif ($broadcast->TargetType === 'platform') { $platforms = json_decode($broadcast->TargetValue, true); $query->whereIn('Platform', $platforms); } if ($broadcast->FilterInstalled !== null) { $query->where('Installed', $broadcast->FilterInstalled); } $subscriptions = $query->get(); // Web Push配置 $auth = [ 'VAPID' => [ 'subject' => env('VAPID_SUBJECT', 'mailto:admin@example.com'), 'publicKey' => env('VAPID_PUBLIC_KEY', ''), 'privateKey' => env('VAPID_PRIVATE_KEY', ''), ] ]; $webPush = new WebPush($auth); // 准备推送数据 $payload = json_encode([ 'title' => $broadcast->Title, 'body' => $broadcast->Body, 'icon' => $broadcast->Icon, 'image' => $broadcast->Image, 'url' => $broadcast->URL, 'tag' => 'broadcast-' . $broadcastId, 'requireInteraction' => false ]); $successCount = 0; $failedCount = 0; // 批量发送 foreach ($subscriptions as $sub) { try { $subscriptionInfo = json_decode($sub->SubscriptionInfo, true); $subscription = Subscription::create([ 'endpoint' => $subscriptionInfo['endpoint'], 'keys' => $subscriptionInfo['keys'] ?? [] ]); $webPush->queueNotification($subscription, $payload); } catch (\Exception $e) { Log::error("Queue notification error for subscription {$sub->ID}: " . $e->getMessage()); } } // 发送所有排队的通知 foreach ($webPush->flush() as $report) { $endpoint = $report->getEndpoint(); $subscription = collect($subscriptions)->first(function ($sub) use ($endpoint) { return $sub->Endpoint === $endpoint; }); if ($subscription) { if ($report->isSuccess()) { $successCount++; $status = 'success'; $errorMsg = null; // 更新订阅的推送统计 DB::table('QPAccountsDB.dbo.WebPushSubscriptions') ->where('ID', $subscription->ID) ->update([ 'LastPushAt' => date('Y-m-d H:i:s'), 'PushCount' => DB::raw('PushCount + 1') ]); } else { $failedCount++; $status = 'failed'; $errorMsg = $report->getReason(); // 如果订阅已失效,禁用它 if (in_array($report->getStatusCode(), [404, 410])) { DB::table('QPAccountsDB.dbo.WebPushSubscriptions') ->where('ID', $subscription->ID) ->update(['Status' => 0]); } } // 记录发送日志 DB::table('QPAccountsDB.dbo.WebPushSendLogs')->insert([ 'BroadcastID' => $broadcastId, 'SubscriptionID' => $subscription->ID, 'Status' => $status, 'ErrorMsg' => $errorMsg, 'SentAt' => date('Y-m-d H:i:s') ]); } } // 更新广播状态 DB::table('QPAccountsDB.dbo.WebPushBroadcasts') ->where('ID', $broadcastId) ->update([ 'Status' => 'completed', 'SuccessCount' => $successCount, 'FailedCount' => $failedCount, 'UpdatedAt' => date('Y-m-d H:i:s') ]); return [ 'success' => true, 'message' => "推送完成! 成功: {$successCount}, 失败: {$failedCount}" ]; } catch (\Exception $e) { Log::error("Process broadcast error: " . $e->getMessage()); DB::table('QPAccountsDB.dbo.WebPushBroadcasts') ->where('ID', $broadcastId) ->update([ 'Status' => 'failed', 'UpdatedAt' => date('Y-m-d H:i:s') ]); return ['success' => false, 'message' => '发送失败: ' . $e->getMessage()]; } } }