Airweave推送通知:Web Push API的实时提醒
概述
在现代Web应用中,实时通知是提升用户体验的关键功能。Airweave作为一个强大的数据同步和搜索平台,可以通过集成Web Push API来实现实时的推送通知功能。本文将深入探讨如何在Airweave中实现Web Push API,为用户提供实时的数据同步状态、搜索结果和系统事件通知。
Web Push API基础
什么是Web Push API?
Web Push API是一种浏览器技术,允许网站向用户发送推送通知,即使用户没有主动访问该网站。它基于Service Worker和Push API构建,是现代Web应用的重要功能。
核心组件
在Airweave中集成Web Push API
1. Service Worker注册
首先需要在Airweave前端注册Service Worker:
// frontend/src/utils/push-notification.ts
export async function registerServiceWorker(): Promise<ServiceWorkerRegistration> {
if (!('serviceWorker' in navigator)) {
throw new Error('Service workers are not supported');
}
const registration = await navigator.serviceWorker.register('/sw.js', {
scope: '/'
});
return registration;
}
export async function requestNotificationPermission(): Promise<NotificationPermission> {
return await Notification.requestPermission();
}
2. Service Worker实现
创建Service Worker文件处理推送事件:
// frontend/public/sw.js
self.addEventListener('push', (event) => {
if (event.data) {
const data = event.data.json();
const options = {
body: data.body,
icon: '/logo-192x192.png',
badge: '/badge-72x72.png',
vibrate: [200, 100, 200],
tag: data.tag || 'airweave-notification',
data: {
url: data.url || '/'
}
};
event.waitUntil(
self.registration.showNotification(data.title, options)
);
}
});
self.addEventListener('notificationclick', (event) => {
event.notification.close();
if (event.notification.data && event.notification.data.url) {
event.waitUntil(
clients.openWindow(event.notification.data.url)
);
}
});
3. 后端推送服务
在Airweave后端实现推送服务:
# backend/airweave/core/push_service.py
import json
from typing import Dict, List
from pywebpush import webpush, WebPushException
import jwt
from datetime import datetime, timedelta
from airweave.core.config import settings
class PushNotificationService:
def __init__(self):
self.vapid_private_key = settings.VAPID_PRIVATE_KEY
self.vapid_public_key = settings.VAPID_PUBLIC_KEY
self.vapid_claims = {
"sub": f"mailto:{settings.SUPPORT_EMAIL}",
"exp": datetime.utcnow() + timedelta(hours=12)
}
async def send_notification(
self,
subscription: Dict,
title: str,
body: str,
data: Dict = None
) -> bool:
"""
发送推送通知
Args:
subscription: 用户订阅信息
title: 通知标题
body: 通知内容
data: 附加数据
Returns:
bool: 发送是否成功
"""
payload = {
"title": title,
"body": body,
"data": data or {}
}
try:
vapid_claims = {
"sub": f"mailto:{settings.SUPPORT_EMAIL}",
"exp": datetime.utcnow() + timedelta(hours=12)
}
webpush(
subscription_info=subscription,
data=json.dumps(payload),
vapid_private_key=self.vapid_private_key,
vapid_claims=vapid_claims
)
return True
except WebPushException as e:
if e.response.status_code == 410:
# 订阅已过期,需要从数据库中删除
return False
return False
推送通知场景实现
1. 数据同步完成通知
# backend/airweave/core/sync_service.py
from airweave.core.push_service import PushNotificationService
class EnhancedSyncService(SyncService):
def __init__(self, db: AsyncSession):
super().__init__(db)
self.push_service = PushNotificationService()
async def handle_sync_completion(
self,
sync_job_id: UUID,
user_id: UUID,
status: SyncJobStatus
):
"""处理同步完成事件并发送推送通知"""
# 获取用户推送订阅
user_subscriptions = await self.get_user_push_subscriptions(user_id)
if status == SyncJobStatus.COMPLETED:
message = "数据同步已完成"
elif status == SyncJobStatus.FAILED:
message = "数据同步失败,请检查日志"
else:
message = f"同步状态更新: {status.value}"
for subscription in user_subscriptions:
await self.push_service.send_notification(
subscription=subscription,
title="Airweave同步通知",
body=message,
data={
"type": "sync_completion",
"sync_job_id": str(sync_job_id),
"status": status.value
}
)
2. 实时搜索结果通知
// frontend/src/hooks/usePushNotifications.ts
import { useCallback } from 'react';
import { useToast } from './use-toast';
export const usePushNotifications = () => {
const { toast } = useToast();
const subscribeToPush = useCallback(async (): Promise<PushSubscription | null> => {
try {
const registration = await navigator.serviceWorker.ready;
const subscription = await registration.pushManager.subscribe({
userVisibleOnly: true,
applicationServerKey: process.env.VAPID_PUBLIC_KEY
});
// 将订阅信息发送到后端
await fetch('/api/push/subscribe', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(subscription),
});
toast({
title: '推送通知已启用',
description: '您将收到实时的搜索和同步通知',
});
return subscription;
} catch (error) {
console.error('推送订阅失败:', error);
toast({
title: '推送通知启用失败',
description: '请检查浏览器设置',
variant: 'destructive',
});
return null;
}
}, [toast]);
return { subscribeToPush };
};
安全性和最佳实践
VAPID密钥管理
# backend/airweave/core/config.py
from pydantic_settings import BaseSettings
class Settings(BaseSettings):
# VAPID配置
VAPID_PRIVATE_KEY: str
VAPID_PUBLIC_KEY: str
VAPID_SUBJECT: str = "mailto:support@airweave.ai"
class Config:
env_file = ".env"
settings = Settings()
通知权限管理
// frontend/src/components/NotificationPermission.tsx
import { useState } from 'react';
import { Button } from './ui/button';
import { Bell, BellOff } from 'lucide-react';
import { usePushNotifications } from '../hooks/usePushNotifications';
export const NotificationPermission: React.FC = () => {
const [permission, setPermission] = useState(Notification.permission);
const { subscribeToPush } = usePushNotifications();
const handleEnableNotifications = async () => {
if (permission === 'default') {
const newPermission = await Notification.requestPermission();
setPermission(newPermission);
if (newPermission === 'granted') {
await subscribeToPush();
}
} else if (permission === 'denied') {
// 指导用户如何手动启用通知
alert('请在浏览器设置中启用通知权限');
}
};
return (
<Button
variant="outline"
onClick={handleEnableNotifications}
disabled={permission === 'granted'}
>
{permission === 'granted' ? (
<>
<Bell className="w-4 h-4 mr-2" />
通知已启用
</>
) : (
<>
<BellOff className="w-4 h-4 mr-2" />
启用通知
</>
)}
</Button>
);
};
性能优化
批量通知发送
# backend/airweave/core/batch_push_service.py
import asyncio
from typing import List, Dict
from airweave.core.push_service import PushNotificationService
class BatchPushService:
def __init__(self, max_concurrent: int = 10):
self.push_service = PushNotificationService()
self.max_concurrent = max_concurrent
self.semaphore = asyncio.Semaphore(max_concurrent)
async def send_batch_notifications(
self,
subscriptions: List[Dict],
title: str,
body: str,
data: Dict = None
) -> Dict[str, int]:
"""
批量发送推送通知
Returns:
Dict: 包含成功和失败数量的统计
"""
results = {
"success": 0,
"failed": 0,
"expired": 0
}
async def send_single(subscription: Dict):
async with self.semaphore:
try:
success = await self.push_service.send_notification(
subscription, title, body, data
)
if success:
results["success"] += 1
else:
results["expired"] += 1
except Exception:
results["failed"] += 1
tasks = [send_single(sub) for sub in subscriptions]
await asyncio.gather(*tasks, return_exceptions=True)
return results
通知去重和节流
# backend/airweave/core/notification_throttler.py
from datetime import datetime, timedelta
from collections import defaultdict
import asyncio
class NotificationThrottler:
def __init__(self, cooldown_period: timedelta = timedelta(minutes=5)):
self.cooldown_period = cooldown_period
self.last_notification_time = defaultdict(lambda: datetime.min)
def should_send_notification(self, user_id: str, notification_type: str) -> bool:
key = f"{user_id}:{notification_type}"
now = datetime.now()
if now - self.last_notification_time[key] >= self.cooldown_period:
self.last_notification_time[key] = now
return True
return False
监控和日志
推送通知监控
# backend/airweave/core/monitoring.py
from prometheus_client import Counter, Histogram
# 指标定义
PUSH_NOTIFICATIONS_SENT = Counter(
'push_notifications_sent_total',
'Total push notifications sent',
['status']
)
PUSH_NOTIFICATION_LATENCY = Histogram(
'push_notification_latency_seconds',
'Push notification delivery latency',
['status']
)
class PushNotificationMonitor:
@staticmethod
def record_notification_sent(success: bool):
status = 'success' if success else 'failure'
PUSH_NOTIFICATIONS_SENT.labels(status=status).inc()
@staticmethod
def record_latency(latency: float, success: bool):
status = 'success' if success else 'failure'
PUSH_NOTIFICATION_LATENCY.labels(status=status).observe(latency)
部署配置
Docker配置
# 在Dockerfile中添加推送相关配置
ENV VAPID_PUBLIC_KEY=your_public_key_here
ENV VAPID_PRIVATE_KEY=your_private_key_here
ENV VAPID_SUBJECT=mailto:support@airweave.ai
# 确保Service Worker文件被正确服务
COPY frontend/public/sw.js /app/frontend/public/sw.js
Nginx配置
# 确保Service Worker文件可以被正确缓存
location /sw.js {
add_header Cache-Control "public, max-age=0";
add_header Service-Worker-Allowed "/";
try_files $uri $uri/ =404;
}
location /push-manifest.json {
add_header Cache-Control "public, max-age=3600";
}
测试策略
单元测试
# tests/unit/core/test_push_service.py
import pytest
from unittest.mock import AsyncMock, patch
from airweave.core.push_service import PushNotificationService
@pytest.mark.asyncio
async def test_send_notification_success():
service = PushNotificationService()
subscription = {
"endpoint": "https://fcm.googleapis.com/fcm/send/test",
"keys": {
"p256dh": "test_key",
"auth": "test_auth"
}
}
with patch('pywebpush.webpush') as mock_webpush:
mock_webpush.return_value = None
result = await service.send_notification(
subscription, "Test", "Message"
)
assert result is True
mock_webpush.assert_called_once()
集成测试
// frontend/src/__tests__/push-notification.test.ts
import { registerServiceWorker } from '../utils/push-notification';
describe('Push Notification Service', () => {
beforeEach(() => {
// 模拟Service Worker API
Object.defineProperty(navigator, 'serviceWorker', {
value: {
register: jest.fn().mockResolvedValue({}),
},
writable: true,
});
});
test('should register service worker successfully', async () => {
const registration = await registerServiceWorker();
expect(registration).toBeDefined();
expect(navigator.serviceWorker.register).toHaveBeenCalledWith('/sw.js', {
scope: '/'
});
});
});
总结
通过集成Web Push API,Airweave可以为用户提供实时的数据同步状态、搜索结果和系统事件通知,大大提升了用户体验。本文详细介绍了从Service Worker注册到后端推送服务的完整实现方案,包括安全性、性能优化和监控等关键方面。
关键优势:
- 实时性: 用户无需刷新页面即可收到最新通知
- 跨平台: 支持桌面和移动设备
- 个性化: 基于用户订阅的精准通知推送
- 可扩展: 支持大规模并发推送
通过合理的架构设计和最佳实践,Airweave的推送通知系统可以稳定可靠地服务大量用户,为用户提供卓越的实时体验。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



