当前位置: 首页 > news >正文

Service Worker介绍及应用(实现Web Push机制)

Service Worker

定义

  • 在浏览器后台运行的 JavaScript 脚本,它独立于网页的主线程。可以把它理解为一个代理服务器,位于浏览器和后端服务器之间。

特点

  • 独立于主线程: 不会阻塞页面的 UI 渲染。

  • 事件驱动: 只有在需要时才会被唤醒,空闲时会终止,节省资源。

  • 生命周期: 有独立的安装、激活和控制页面等生命周期。

  • 需要 HTTPS: 出于安全考虑,Service Worker 只能在 HTTPS 环境下使用。

  • 无 DOM 访问: 无法直接操作 DOM,需要通过 postMessage 与主页面通信。

作用

  • 离线访问和缓存
    • 拦截网络请求: Service Worker 可以拦截所有由其控制的页面发出的网络请求。
    • 自定义响应: 它可以决定如何响应这些请求,例如从缓存中获取资源(即使离线),或者在网络可用时再发送请求。
    • “离线优先”策略: 优先从缓存中加载资源,从而在用户离线或网络条件不佳时也能提供内容,提升用户体验。
    • 精确控制缓存: 比传统的浏览器缓存(如 AppCache,已被废弃)提供更精细的缓存控制能力。
  • 推送通知
    • 即使浏览器页面未打开,Service Worker 也能接收来自服务器的推送消息,并向用户展示通知。这使得 Web 应用能够像原生应用一样向用户发送实时提醒。
  • 后台同步
    • 允许应用程序在用户离线时暂存数据,待网络连接恢复时自动同步到服务器。例如,用户在离线状态下发送消息或提交表单,Service Worker 可以将其暂存并在联机时发送。
  • 提高性能
    • 通过缓存静态资源和优化网络请求,可以显著加快页面加载速度,尤其对于重复访问的用户。
  • 渐进式 Web 应用 (PWA) 的核心
    • Service Worker 是实现 PWA 的关键技术之一,它让 Web 应用具备了离线工作、可安装到主屏幕、接收推送通知等能力,从而提升用户粘性和体验。

Web Push实践

基本概念
  1. Push API:浏览器提供的接口,用于订阅接收来自服务器推送消息
  2. Web Push Protocol:定义了服务器如何将消息发送给推送服务,再由推送服务转发给浏览器的协议。
  3. VAPID :一种安全规范,通过公私钥对,使服务器能向推送服务验证自身身份
  4. Push Subscription (推送订阅对象)用户授权后,浏览器返回的JSON对象,包含推送服务的endpoint URL加密密钥供服务器推送消息。
消息推送的完整流程
  1. 客户端注册与授权
    • Web应用在浏览器中注册Service Worker
    • 应用请求用户授权发送通知。
    • 用户授权后,通过Push API获取唯一的PushSubscription对象
    • 客户端将该PushSubscription发送给服务器
  2. 服务器存储与触发
    • 服务器存储PushSubscription并与用户关联。
    • 当事件发生时,服务器准备推送通知
  3. 服务器发送与推送服务中继
    • 服务器使用PushSubscription和VAPID密钥,通过Web Push Protocol向推送服务发送消息。
    • 推送服务接收并路由消息到目标浏览器
  4. 客户端接收与显示
    • 目标浏览器中的Service Worker被唤醒并接收push事件
    • Service Worker调用showNotification() API向用户显示通知
客户端实现
  • 在主 JavaScript 文件中:注册 Service Worker、请求用户授权并订阅推送

    // main.js// 替换为你的 VAPID 公钥 (由服务端生成)
    const VAPID_PUBLIC_KEY = 'YOUR_SERVER_GENERATED_VAPID_PUBLIC_KEY'initializePushNotifications()// 推送订阅
    const initializePushNotifications = async () => {//向用户申请权限const permission = await Notification.requestPermission();if (permission !== 'granted') {return;}// 触发订阅 const registration = await navigator.serviceWorker.ready;// 获取订阅信息const applicationServerKey = urlBase64ToUint8Array(VAPID_PUBLIC_KEY);const subscription = await registration.pushManager.subscribe({userVisibleOnly: true, // 必须为 true,每次推送都对用户可见applicationServerKey: applicationServerKey});// 将订阅信息发送到服务器,服务端需将它和用户ID的映射进行保存await fetch('/api/subscribe', { // 你的后端 API 端点method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify(subscription),});}
    };// 辅助函数:Base64 字符串转 Uint8Array
    const urlBase64ToUint8Array = (base64String) => {const padding = '='.repeat((4 - base64String.length % 4) % 4);const base64 = (base64String + padding).replace(/-/g, '+').replace(/_/g, '/');const rawData = window.atob(base64);return Uint8Array.from([...rawData].map(char => char.charCodeAt(0)));
    };
    
  • Service Worker 脚本(sw.js) :负责接收和展示通知

    // 监听 'push' 事件
    self.addEventListener('push', event => {const data = event.data.json();//显示通知给用户event.waitUntil(self.registration.showNotification(data.title, {body: data.body,icon: '/icon.png',data: { url: data.url }}));
    });// 当用户点击由 Service Worker 显示的通知时,会触发这个事件。
    self.addEventListener('notificationclick', event => {// 关闭当前被点击的通知。event.notification.close();// 在新的浏览器窗口或标签页中打开指定的 URLevent.waitUntil(clients.openWindow(event.notification.data.url));
    });
    
服务端实现 (Java)
  • Maven 依赖 (pom.xml):

    <dependency><groupId>nl.martijndwars</groupId><artifactId>web-push</artifactId><version>5.1.1</version> </dependency>
    <dependency><groupId>org.bouncycastle</groupId><artifactId>bcprov-jdk18on</artifactId><version>1.78.1</version>
    </dependency>
    
  • 生成和管理 VAPID 密钥:使用 web-push 库或其他工具(如 GCM/FCM 控制台或 web-push 命令行工具)生成 VAPID 公钥和私钥。私钥保密, 公钥则提供给前端

    // 生成 EC 密钥对,使用 prime256v1 曲线(VAPID 要求)
    KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC", "BC");
    keyPairGenerator.initialize(new ECGenParameterSpec("secp256r1"), new SecureRandom());
    KeyPair keyPair = keyPairGenerator.generateKeyPair();// 获取并编码私钥和公钥(Base64)
    String privateKey = Base64.getUrlEncoder().withoutPadding().encodeToString(keyPair.getPrivate().getEncoded());
    String publicKey = Base64.getUrlEncoder().withoutPadding().encodeToString(keyPair.getPublic().getEncoded());// 输出
    System.out.println("==== VAPID 密钥对 ====");
    System.out.println("公钥 (Base64): " + publicKey);
    System.out.println("私钥 (Base64): " + privateKey);
    
  • Push Service 类:负责推送消息

    @Service
    public class WebPushService {@Value("${app.vapid.public-key}")private String vapidPublicKey;@Value("${app.vapid.private-key}")private String vapidPrivateKey;@Value("${app.vapid.subject}")private String vapidSubject;private PushService pushService;@PostConstructprivate void init() throws GeneralSecurityException {// 确保 BouncyCastle Provider 已添加if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {Security.addProvider(new BouncyCastleProvider());}this.pushService = new PushService(vapidPublicKey, vapidPrivateKey, vapidSubject);}// 发送推送通知public void sendNotification(Subscription subscription, String payloadJson) {try {// 构建通知对象Notification notification = new Notification(subscription, payloadJson);// 发送通知并获取响应HttpResponse httpResponse = pushService.send(notification);} catch (Exception e) {System.out.println("发送推送通知失败:" + e.getMessage());}}
    }
    

相关文章:

  • 华为AP6050DN无线接入点瘦模式转胖模式
  • 【数据结构初阶】顺序表的应用
  • PostgreSQL 内置扩展列表
  • 嵌入式通用集成电路卡市场潜力报告:物联网浪潮下的机遇与挑战剖析
  • Parasoft C++Test软件单元测试_实例讲解(对多次调用的函数打桩)
  • Java复习Day21
  • 常用 Linux 命令---服务器开发和运维相关命令
  • JAVA网络编程——socket套接字的介绍下(详细)
  • 互联网大厂Java求职面试:AI与云原生架构实战解析
  • 深度学习---注意力机制(Attention Mechanism)
  • 自动化测试常见函数(下篇)
  • Golang | 代理模式
  • Spring Boot项目中实现单点登录(SSO)完整指南
  • Python爬虫第22节- 结合Selenium识别滑动验证码实战
  • 算法刷题记录:滑动窗口经典题目解析
  • 我们来学mysql -- 输出一份“数据备份还原”sh脚本
  • Mac M1编译OpenCV获取libopencv_java490.dylib文件
  • webpack的安装及其后序部分
  • 基于 HEC-RAS 与 ArcGIS 的洪水危险性评估技术— 从地形分析到淹没模拟的全流程实践
  • Python爬虫(40)基于Selenium与ScrapyRT构建高并发动态网页爬虫架构:原理、实现与性能优化
  • 打开部分网站很慢/google永久免费的服务器
  • 无为县做互联网网站/百度推广销售
  • 海珠免费网站建设/视频广告
  • 湖南营销型网站建设案例/百度大搜
  • 电影网站怎么做的/快速排名点击工具
  • 网站制作的发展趋势/chrome官网