深入 ServiceWorker,消息推送,后台同步,一网打尽

news2024/6/30 0:06:50

上一章讲到了ServiceWorker的基础使用,但是它的功能不仅仅只有这些,还有很多很多,比如消息推送,后台同步,甚至还有WebRTC,这一章我们来进阶ServiceWorker

前期准备

在开始之前,我们先做一下前期的准备,还是使用我上一篇的例子,但是我们需要删除service-worker.js里面的缓存代码,因为这一章用不上,新看到的小伙伴可以直接用我下面的代码也行:

  • index.html
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>Title</title>
</head>
<body>
<script> if ('serviceWorker' in navigator) {// 在调试阶段,如果发现 service worker 没有更新,可以先用下面的代码强制更新// navigator.serviceWorker.getRegistrations().then((registrations) => {// for (let registration of registrations) {// registration.unregister()// }// });navigator.serviceWorker.register('/service-worker.js', {scope: '/'}).then((registration) => {// 先空着});} </script>
</body>
</html> 
  • service-worker.js里面暂时不写任何代码

消息推送

使用ServiceWorker是一定要了解消息推送的,因为它是ServiceWorker的一个重要功能,它可以让你的网站在用户不在的时候,也能够接收到消息,这对于一些即时通讯的网站来说,是非常重要的。

按照正常的消息推送流程,我们需要经历下面几个步骤:

1.注册ServiceWorker获得registration对象
2.通过registration对象获得PushManager对象
3.通过PushManager对象订阅消息推送,获得subscription对象
4.将subscription对象发送给服务器,由服务器保存
5.服务器通过subscription对象推送消息
6.ServiceWorker通过监听push事件,获得推送的消息
7.ServiceWorker通过showNotification方法,显示消息

在讲推送流程之前,我们先来体验一下消息推送的快感。

1. 体验消息推送

啥也不多说,直接上代码:

if ('serviceWorker' in navigator) {navigator.serviceWorker.register('/service-worker.js', {scope: '/'}).then((registration) => {// 直接调用showNotification方法,显示消息registration.showNotification('Hello World');});
} 

上面的方法直接让我们在window上显示了一个消息,但是这个消息是没有任何交互的,我们可以通过Notification对象来实现交互:

// 省略注册 ServiceWorker 的代码

self.showNotification('Hello World<img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/703a0cee8b0b494eadd27adc61883956~tplv-k3u1fbpfcp-watermark.image?',actions: [{action: 'yes',title: 'Yes'}, {action: 'no',title: 'No'}" style="margin: auto" />
}); 

这样我们就可以在消息上添加交互了,但是这个交互是没有任何效果的,我们需要通过监听notificationclick事件来实现交互:

// service-worker.js

// 监听 notificationclick 事件
self.addEventListener('notificationclick', (event) => {// 判断点击的是哪个按钮if (event.action === 'yes') {console.log('yes');} else if (event.action === 'no') {console.log('no');}
}); 

这个时候我们就可以在控制台看到点击的是哪个按钮了,接下来我们就来认识一下showNotification方法。

2. showNotification

showNotification方法是ServiceWorkerRegistration对象的一个方法,它可以用来显示消息,直接看函数签名:

 interface ServiceWorkerRegistration extends EventTarget {/** * 显示消息 * @param title 消息标题 * @param options 消息配置 *options.body 消息内容 *options.icon 消息图标 *options.tag 消息标签 *options.data 消息数据 *options.actions 消息交互按钮 * @returns Promise */showNotification(title: string, options?: NotificationOptions): Promise<Notification>;
} 

上面的options参数并没有完全列出来,可以参考MDN。

通过上述的实验代码,不知道大家有没有发现一个问题,就是showNotification方法调用出来的消息是直接显示在window上的,而不是显示在浏览器的通知栏上。

这是因为消息推送是浏览器行为,作用在window系统上,而不是作用在网页上。

通过这个案例,有没有产生一个想法,就是大家在写业务代码的时候,遇到消息通知都是自己手写一个弹窗消息,切换个网页就看不到了;

这个时候我们使用showNotification方法,直接推送到window上,虽然样式不能自定义,但是是不是逼格更高呢?

而我们的showNotification方法,其实是通过Notification对象来实现的,点到为止,继续学习ServiceWorker

消息推送流程

为什么要先体验消息推送的效果呢,再来讲消息推送的流程呢?

因为不想打击大家的热情,消息推送因为会被墙,所以在国内没有多少人使用,所以我们先体验一下,再来讲消息推送的流程。

现在我们直接上代码来查看整体流程是怎么样的:

  • index.html
if ('serviceWorker' in navigator) {// 1. 注册`ServiceWorker`获得`registration`对象// 2. 通过`registration`对象获得`PushManager`对象// 3. 通过`PushManager`对象订阅消息推送,获得`subscription`对象// 4. 将`subscription`对象发送给服务器,由服务器保存// 5. 服务器通过`subscription`对象推送消息// 6. `ServiceWorker`通过监听`push`事件,获得推送的消息// 7. `ServiceWorker`通过`showNotification`方法,显示消息// 1. 注册`ServiceWorker`获得`registration`对象navigator.serviceWorker.register('/service-worker.js', {scope: '/'}).then((registration) => {// 2. 通过`registration`对象获得`PushManager`对象const pushManager = registration.pushManager;// 3. 通过`PushManager`对象订阅消息推送,获得`subscription`对象pushManager.subscribe({userVisibleOnly: true,applicationServerKey: ''}).then((subscription) => {// 4. 将`subscription`对象发送给服务器,由服务器保存console.log(subscription);});});
} 

前面的四步都是在客户端完成的,这里的关键在于第三步,通过PushManager对象订阅消息推送,获得subscription对象。

我们先来看一下subscribe方法的参数和简介:

interface PushSubscriptionOptions {userVisibleOnly?: boolean;applicationServerKey?: BufferSource;
}

interface PushManager {/** * 订阅消息推送 * @param options *options.userVisibleOnly: 是否只有用户可见 *options.applicationServerKey: 服务器公钥 * @returns {Promise<PushSubscription>} */subscribe(options?: PushSubscriptionOptions): Promise<PushSubscription>;
} 

这里的options在文档上显示是可选参数,但是在部分浏览器上是必填参数,所以我们在调用subscribe方法的时候,需要传入options参数。

参数介绍:

  • userVisibleOnly:布尔值,表示返回的推送订阅将只能被用于对用户可见的消息;在部分浏览器下,如果为false,则会抛出错误。
  • applicationServerKey:服务器公钥,用于加密推送消息,这个参数在部分浏览器下是必填参数。

这里的重点就是applicationServerKey,这个是由我们自己生成的,接下来我们来看一下如何生成。

生成服务器公钥

我们可以通过web-push这个库来生成服务器公钥,这个库是Google开发的,旨意是打通多端消息推送的通道,它提供了一些工具,可以帮助我们生成服务器公钥,以及发送推送消息。

web-push的库在这里:web-push

安装web-push会被墙,可以通过切换npm源来解决:

npm config set registry https://registry.npm.taobao.org 

安装web-push

npm install web-push 

安装完成后,我们就可以使用web-push来生成服务器公钥了:

const webpush = require('web-push');

// 生成服务器公钥
const vapidKeys = webpush.generateVAPIDKeys();

console.log(vapidKeys); 

我们也可以全局安装web-push,然后通过命令行来生成服务器公钥:

npm install web-push -g

web-push generate-vapid-keys --json 

上面生成的密钥对会有一个公钥和一个私钥,私钥放在服务器保存,公钥用于注册推送订阅:

pushManager.subscribe({userVisibleOnly: true,applicationServerKey: 'BHXrxJPYpQSwGMwcN-HprCaU_Po9POIUvqWFLFq9UUNHP5SNJKxk_Io59y8_twMTOuB5SbpbcPBwHFo2kBUj7vQ'
}).then((subscription) => {// 4. 将`subscription`对象发送给服务器,由服务器保存console.log(subscription);
}); 

上面注册成功后,会返回一个subscription对象,这个对象就是推送订阅,我们需要将这个对象发送给服务器,由服务器保存,用于后续推送消息。

有时候注册过了如果再重复注册会报错,可以通过pushManager.getSubscription()来获取已经注册的subscription对象。

pushManager.getSubscription().then((subscription) => {if (subscription) {// 这里 subscription 就是已经注册的 subscription 对象return;}pushManager.subscribe({userVisibleOnly: true,applicationServerKey: 'BHXrxJPYpQSwGMwcN-HprCaU_Po9POIUvqWFLFq9UUNHP5SNJKxk_Io59y8_twMTOuB5SbpbcPBwHFo2kBUj7vQ'}).then((subscription) => {console.log(subscription);});
}); 

发送推送消息

推送消息我们同样也是通过web-push来发送,它提供了一个sendNotification()方法,可以帮助我们发送推送消息。

这里我们通过node服务来写一个简单的推送消息的接口:

import express from 'express';
import webpush from 'web-push';

const vapidKeys = {"publicKey": "BHXrxJPYpQSwGMwcN-HprCaU_Po9POIUvqWFLFq9UUNHP5SNJKxk_Io59y8_twMTOuB5SbpbcPBwHFo2kBUj7vQ","privateKey": "Yhd4XF08Efh8HNF_8RDJ9VL6pF-Gos-3KOmgyMEUSf8"
}

// 这个是需要在 google cloud platform 中创建的项目id
webpush.setGCMAPIKey('<Your GCM API Key Here>');

// 联系邮箱填自己的邮箱
webpush.setVapidDetails('mailto:example@yourdomain.org',vapidKeys.publicKey,vapidKeys.privateKey
);

const app = express();

app.get('/push', (req, res) => {// 这里的 pushSubscription 就是上面注册成功后返回的 subscription 对象const pushSubscription = {"endpoint": "https://fcm.googleapis.com/fcm/send/cSAH1Q7Fa6s:APA91bEgYeKNXMSO1rcGAOPzt3L9fMhyjL-zSPV5JfiKwgqtbx_Q4de_8plEY_QViLnhfe6-0fUgdo7Z3Gqpml3zIBSfO6IISDYdF9kzL2h_dbZ_FE_YKbKOG70gMG_A74xwK1vsocCv","keys": {"p256dh": "BAqZaMLZn_rtYeR7WsBLqBWG7uMiOGRyCx2uhOqm0ZaJwDdQac-ubAyRRdLXJVZDOrNe-B3mCTy3g0vHCkeyYyo","auth": "fxDt8RtB92KHpQM7HetBUw"}};webpush.sendNotification(pushSubscription, 'Hello world').then(result => {res.send(result);})
})

app.listen(1701, async () => {console.log('服务启动成功:http://localhost:1701');
}) 

这里我们需要申请一个GCMApiKey,这个是用于验证推送消息的身份,可以在FCM申请。

上面是web-push的使用,现在通过firebase注册的推送消息已经更换了架构,不在使用web-push,而是使用firebase,详情可以参考这里;> firebase的使用在官网上有详细的文档,我这里只是演示订阅推送消息的过程,所以使用web-push开生成密钥对,然后通过web-push发送推送消息。

Service Worker监听推送消息

Service Worker监听推送消息,我们需要在Service Worker中注册push事件,然后在push事件中处理推送消息,也就是最开始提到的showNotification()方法。

// 注册 push 事件
self.addEventListener('push', function (event) {const data = event.data.json();self.registration.showNotification(data.title)
}); 

现在可以通过访问 http://localhost:1701/push 来发送推送消息了,然后在Service Worker中通过showNotification()方法来显示推送消息。

自此一整套的消息推送和接收的流程就走完了,但是为什么要订阅推送消息?为什么我自己的服务器还需要申请啥GCMApiKey

这就要说到消息推送的很多知识了,这里不宜过多的展开,有兴趣的可以自行了解。

消息推送的一些知识

我们这里需要了解的是订阅到服务器推送之间的过程倒是是什么样的,为什么我说推送消息会被墙,为什么我们推送消息会不成功?

这里我们直接看下面的流程图:

graph TD

A[订阅推送消息] --> B[FCM服务器]
B --> C1[验证不通过抛出异常]
B --> C[返回subscription对象]
C --> D[将subscription对象保存到服务器]
D --> E[服务器向FCM服务器发送推送消息]
E --> F[FCM服务器将消息直接推送给浏览器]
F --> G[浏览器接收到推送消息调用 push 回调]
F --> G1[浏览器不在线 推送消息会在浏览器上线时通过 notificationcallback 回调]
G --> H[执行 push 事件里面的回调函数] 

可以看到服务和浏览器之间是没有直接的通信的,而是通过FCM服务器来进行通信的;

FCM服务器是谷歌的服务器,所以我们不管是订阅还是推送消息都需要通过谷歌的服务器,这就是为什么我们推送消息会被墙的原因;

离线推送

PC不像移动端,你的网页或者浏览器不会一直开着,所以你的网页或者浏览器不在线的时候,你的推送消息是不会被推送到你的浏览器的,这个时候你的推送消息就会被FCM服务器保存起来,等你的网页或者浏览器上线的时候,FCM服务器会通过notificationcallback回调来推送消息到你的网页或者浏览器。

self.addEventListener('notificationcallback', function (event) {const data = event.data.json();self.registration.showNotification(data.title)
}); 

注意:这个回调是在离线的时候有推送消息,然后在你的网页上线的时候才会触发,如果你的网页一直在线,那么这个回调是不会触发的。

ok,到这里我们已经了解了推送消息的一些知识,我们继续下面的环节。

消息同步

上面讲到了push事件的监听,现在就还剩下sync事件的监听了,sync事件是在Service Worker中注册的,当我们调用navigator.serviceWorker.ready方法后,就可以通过registration.sync.register()方法来注册sync事件了。

// 注册 sync 事件
navigator.serviceWorker.ready.then((registration) => {return registration.sync.register('sync');
}); 

这里我们注册了一个sync事件,这个事件的名字叫sync,这个名字是自己定义的,可以随便定义,但是需要注意的是,这个名字是全局唯一的,如果你注册了一个sync事件,那么下次再注册的时候,就不能再注册一个sync事件了,否则会报错。

上面注册好了之后,就需要在Service Worker中监听这个事件了:

// 监听 sync 事件
self.addEventListener('sync', (event) => {if (event.tag === 'sync') {event.waitUntil(// 同步数据syncData());}
}); 

相对于push事件,sync事件的监听就简单多了,我们只需要判断一下事件的名字,然后执行对应的操作就可以了。

使用sync事件首选需要注册一个sync事件,通过registration.sync.register()方法来注册;

上面使用到navigator.serviceWorker.ready是用来确保Service Worker已经注册成功,它会返回一个不会失败的Promise对象,如果没有注册成功,那么就会一直处于pending状态。

Service Worker中监听sync事件,通过self.addEventListener('sync', callback)方法来监听,这个callback函数会接收一个event对象,通过event.tag来判断事件的名字,然后执行对应的操作。

sync的使用场景

看名字就知道,sync事件是用来同步数据的,那么我们什么时候需要同步数据呢?

sync的使用场景有很多,我下面列举几个:

  • 离线数据同步
  • 数据备份
  • 数据恢复
  • 数据同步

通常我们可以使用这个事件来通知Service Worker同步数据,或者更新缓存,继续拿我之前的百万数据的例子来说;

百万数据很大,在上一篇中我们将数据缓存下来了,但是如果数据发送了变化,那么我们就需要更新缓存,这个时候就可以使用sync事件来通知Service Worker更新缓存。

// main.js
// 请求后台查询数据是否有变化
fetch('/api/check').then((res) => {return res.json();
}).then((data) => {if (data.hasChange) {// 有变化,注册 sync 事件navigator.serviceWorker.ready.then((registration) => {return registration.sync.register('sync');});}
});

// service-worker.js
// 监听 sync 事件
self.addEventListener('sync', (event) => {if (event.tag === 'sync') {event.waitUntil(// 同步数据syncData());}
});

// 同步数据
function syncData() {// 请求后台获取数据return fetch('/getData').then((res) => {return res.json();}).then((data) => {// 更新缓存caches.open('my-caches').then((cache) => {cache.put('/getData', new Response(JSON.stringify(data)));});});
} 

上面的代码中,我们在main.js中请求后台查询数据是否有变化,如果有变化,那么就注册一个sync事件,然后在Service Worker中监听这个事件,当事件触发的时候,就会执行syncData方法,这个方法中会请求后台获取数据,然后更新缓存。

Service Worker更新

本文的最开始给了一段注释的代码,这段代码是用来更新Service Worker的,就是下面这段代码:

navigator.serviceWorker.getRegistrations().then((registrations) => {for (let registration of registrations) {registration.unregister()}
}); 

这段代码并不是用来更新Service Worker的,而是用来卸载Service Worker的,所以最开始我注释说明是开发阶段使用的,因为我们在开发阶段,经常会修改Service Worker的代码,如果不卸载Service Worker重装很有可能缓存没有清除,导致我们看到的效果和预期的不一样。

但是在生产环境中,我们肯定是不能轻易卸载Service Worker的,当然Service Worker也提供了更新的方法,我们可以通过Service Workerupdate方法来更新Service Worker

// 更新 Service Worker
navigator.serviceWorker.getRegistration().then((registration) => {registration.update();
}); 

上面的代码中,我们通过getRegistration方法获取Service Worker的注册对象,然后调用update方法来更新Service Worker

同样,是否需要更新Service Worker,也是需要我们自己来判断的,比如我们可以通过Service WorkerscriptURL属性来判断Service Worker的版本,如果版本不一致,那么就更新Service Worker

// 更新 Service Worker
navigator.serviceWorker.getRegistration().then((registration) => {if (registration.scriptURL !== '/service-worker.js?v=1.0.0') {registration.update();}
}); 

通常我们会将Service Worker的版本号放在scriptURL中,这样就可以通过scriptURL来判断Service Worker的版本了。

Service Worker缓存更新

上面说到了Service Worker的更新,我们的缓存也是需要更新的,比如我们的Service Worker更新了,那么我们的缓存也需要更新,这样才能保证我们正常的版本迭代。

缓存在上一章中,是使用了自定义的cacheName来进行缓存的,其实我们可以用版本号来进行缓存,和Service Worker保持一致,然后在更新的时候删除对应的缓存:

// 更新 Service Worker
navigator.serviceWorker.getRegistration().then((registration) => {if (registration.scriptURL !== '/service-worker.js?v=1.0.0') {registration.update();// 删除缓存caches.delete('v1.0.0');}
}); 

上面的代码中,我们在更新Service Worker的时候,也删除了缓存,这样就可以保证我们的缓存和Service Worker的版本一致了。

总结

本章几乎探索完了Service Worker的高频常用知识,以目前了解的Service Worker的知识来说,本章的内容已经足够满足大部分的需求了;

当然这只是一个起点,Service Worker的知识还有很多,而且并不是只围绕着Service Worker来讲,还有很多周边的知识;

例如本章讲到的showNotification方法,其实是Notification的API,push方法,其实是Push的API,还有很多;

本文讲的只是使用,并没有深入,光是使用这一篇文章就已经很长了,如果深入的话,那就更长了;

不多废话,记住这只是一个起点!!!

最后

整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享

部分文档展示:



文章篇幅有限,后面的内容就不一一展示了

有需要的小伙伴,可以点下方卡片免费领取

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/186073.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【分布式微服务】SpringBoot启动原理

一、SpringBoot启动类 SpringBootApplication(exclude DataSourceAutoConfiguration.class) public class PracticeApplication {public static void main(String[] args) {SpringApplication.run(PracticeApplication.class, args);} }二、源码解析 /*** Static helper that …

云原生安全方案介绍

方案介绍 安全狗云原生应用安全解决方案(CNAPP, Cloud-Native Application Protection Platform)采用主机安全Agent和安全容器相结合的技术&#xff0c;既落地了“安全左移”的概念&#xff0c;又能对云原生容器做全面保护&#xff0c;同时能灵活地跟容器编排体系相结合&#x…

map find/count源码分析

参考博客&#xff1a;c map find方法源码解析 众所周知&#xff0c;如果一个数据结构想作为map的key&#xff0c;则必须重载 operator < 否则编译将会报错。但是operator 则是不需要的。那么当查找某个key是否存在的时候&#xff0c;map内部是怎么实现的呢&#xff1f; 结…

1301738-40-8,Bis-maleimides-amine,双马来酰亚胺-氨基

一、基础产品数据&#xff08;Basic Product Data&#xff09;&#xff1a;CAS号&#xff1a;1301738-40-8中文名&#xff1a;三臂双马来酰亚胺-氨基&#xff0c;双马来酰亚胺-氨基&#xff0c;活性交联剂&#xff0c;异官能团三臂交联剂英文名&#xff1a;Bis-Maleimides amin…

【C语言进阶】柔性数组

目录一&#xff1a;柔性数组的特点二&#xff1a;柔性数组的使用三&#xff1a;模拟实现柔性数组在C99中&#xff0c;结构中的最后一个元素允许是未知大小的数组&#xff0c;这就叫做柔性数组成员 以下是柔性数组的两种写法&#xff1a; //写法一&#xff1a; struct S {int n…

Python笔记 · 鸭子类型 / Duck Typing

1. 问题的由来 我初次意识到鸭子类型在存在是在学习Sklearn时&#xff0c;在《Hands-On Machine Learing》一书的第二章&#xff0c;作者提供了一个自定义的Tansformer&#xff0c;使用自定义Transformer的好处在于&#xff1a;你既可以实现自己需要的数据处理逻辑&#xff0c…

【应用】SpringCloud -- Gateway

SpringCloud -- GatewayGateway 网关概述Gateway 的功能Gateway 核心概念Gateway 网关搭建Gateway 的配置及使用predicate 断言filter 过滤器GlobalFilter 全局过滤器Gateway 的几点问题过滤器执行顺序跨域问题Gateway 网关概述 Gateway 的功能 在微服务当中&#xff0c;有很…

PG15 pg_basebackup 代码解析

背景 先前 PG 版本 pg_basebackup 的代码较为复杂&#xff0c;pg_basebackup 在备份过程中做了很多事情&#xff0c;但这部分代码逻辑没有完全解耦&#xff0c;导致一个文件里包含了很多功能的逻辑&#xff0c;影响了代码的可读性和可修改性。 因此&#xff0c;PG 15 针对这部…

模电学习3. 差模、共模干扰与安规电容

模电学习3. 差模、共模干扰与安规电容一、简介1. 安规2. 电源安规标准二、差模干扰与共模干扰1. 共模干扰2. 差模干扰&#xff08;1&#xff09;定义&#xff08;2&#xff09;来源与特性3. 测量4. 消除电源线路中的差模干扰&#xff08;1&#xff09;差模电容5. 消除电源线路中…

TOOM加强网络舆情监控制定处置预案,抓好舆情监控管理?

舆情预案是指根据对未来舆情发展趋势的预测&#xff0c;制定预先准备的舆情处置方案&#xff0c;以应对可能出现的舆情危机。舆情预案包括舆情预警体系、舆情应对策略、应对措施等内容&#xff0c;旨在在舆情危机发生前进行有效的预防和准备&#xff0c;避免舆情危机扩大&#…

95. BERT预训练数据代码

为了预训练之前实现的BERT模型&#xff0c;我们需要以理想的格式生成数据集&#xff0c;以便于两个预训练任务&#xff1a;遮蔽语言模型和下一句预测。一方面&#xff0c;最初的BERT模型是在两个庞大的图书语料库和英语维基百科的合集上预训练的&#xff0c;但它很难吸引这本书…

Zookeeper 教程

Zookeeper 教程Zookeeper 概述分布式应用Zookeeper 架构图ZooKeeper当中的主从与主备&#xff1a;Zookeeper的特性分布式应用的优点分布式应用的挑战什么是Apache ZooKeeper&#xff1f;ZooKeeper的好处Zookeeper 基础ZooKeeper的架构层次命名空间Znode的类型Sessions&#xff…

九龙证券|外资放大招,400亿巨头狂飙!这只翻倍股却突然崩了

昨日超级“开门红”之后&#xff0c;今天上午港股又有多只重磅个股大幅涨超10%。 今日上午&#xff0c;港股整体窄幅震荡&#xff0c;上午收盘&#xff0c;恒生指数微跌0.05%&#xff0c;恒生科技指数涨0.22%。 职业板块方面&#xff0c;媒体、软件服务、电信等涨幅居前&#…

【BLE】ANCS(Apple Notification Center Service)

目录1. 前言1.1 名词解释1.2 ANCS概述2. ANCS的特征2.1 通知源2.2 控制点和数据源2.3 获取通知属性2.4 获取应用属性2.5 执行通知操作2.6 通知操作3. 更多详情参考1. 前言 1.1 名词解释 NP(Notification Provider)&#xff1a;消息提供者&#xff0c;指的是ANCS服务的生产者&…

【JavaSE】入门概述(1~41)

1.Java视频及配套资料下载指南 2.Java基础学习导读 Java语言的三个层面 基本语法&#xff1a;变量、运算符、流程控制、数组面向对象&#xff1a;把数据及对数据的操作方法放在一起&#xff0c;作为一个相互依存的整体——对象高级应用&#xff1a;多线程、集合、IO流、网络…

性能怪兽-Nginx常用配置指北

目录 安装 Nginx操作命令 https反向代理 负载均衡 轮询 加权轮询 最少连接 加权最少连接 IP Hash 普通 Hash 动静分离 资源压缩 缓冲区 缓存机制 解决跨域 防盗链设计 配置SLL证书 性能优化 打开长连接配置 开启零拷贝技术 开启无延迟或多包共发机制 调整W…

Python连接Liunx中mysql数据库-增删改查

上一篇文章已经讲解了如何连接liunx中的mysql数据库&#xff0c;如果没有连接好数据库的话&#xff0c;可以看这一篇文章 增删改查Mysql中查询操作1.创建游标2.定义一个sql的查询语句3.调用游标内的sql语句执行操作4.打印出查询结果5.完整代码6.指定查询Mysql中新增操作1.单条数…

jupyter 常用记录

安装windows环境下 运行cmd 然后&#xff1a;在命令提示窗口输入pip install jupyter,然后回车&#xff1b;完成后运行 jupyter notebook 前言 提起jupyter notebook&#xff0c;应该很多学习过Python的同学都不陌生。虽然用jupyter notebook的同学相对较少&#xff0c;但是提…

Java多级缓存是为了解决什么的?

前言 提到缓存&#xff0c;想必每一位软件工程师都不陌生&#xff0c;它是目前架构设计中提高性能最直接的方式。 缓存技术存在于应用场景的方方面面。从网站提高性能的角度分析&#xff0c;缓存可以放在浏览器&#xff0c;可以放在反向代理服务器&#xff0c;还可以放在应用…

使用JINJA2模板部署自定义文件(RH294)

在ansible中有许多模板可以用于修改现有的文件比如—— lineinfile和blockinfile但是&#xff01;我们还有一种更加便捷而且牛皮的方法为其构建模板在部署该文件是自动为受管主机自定义次模板配置文件而这些模板中非常有名的就是 JINJA2ansible将Jinja2模板系统用于模板文件 并…