【PWA学习】4. 使用 Push API 实现消息推送

news2024/11/29 5:49:47

引言

在接下来的内容里,我们会探究 PWA 中的另一个重要功能——消息推送与提醒(Push & Notification)。这个能力让我们可以从服务端向用户推送各类消息并引导用户触发相应交互

Web Push 效果

Push API 和 Notification API 其实是两个独立的技术,完全可以分开使用;不过Push API 和 Notification API相结合是一个常见的模式

浏览器是如何实现服务器消息 Push 的

Web Push 的整个流程相较之前的内容来说有些复杂。因此,在进入具体技术细节之前,我们需要先了解一下整个 Push 的基本流程与相关概念。

如果你对 Push 完全不了解,可能会认为,Push 是我们的服务端直接与浏览器进行交互,使用长连接、 WebSocket 或是其他技术手段来向客户端推送消息。然而,这里的 Web Push 并非如此,它其实是一个三方交互的过程。

在 Push 中登场的三个重要“角色”分别是:

  • 浏览器:就是我们的客户端
  • Push Service:专门的Push服务,你可以认为是一个第三方服务,目前chrome与firefox都有自己的Push Service Service。理论上只要浏览器支持,可以使用任意的Push Service
  • 后端服务:这里就是指我们自己的后端服务

下面就介绍一下这三者在 Web Push 中是如何交互

消息推送流程

下图来自Web Push协议草案,是 Web Push 的整个流程:

+-------+           +--------------+       +-------------+
    |  UA   |           | Push Service |       | Application |
    +-------+           +--------------+       |   Server    |
        |                      |               +-------------+
        |      Subscribe       |                      |
        |--------------------->|                      |
        |       Monitor        |                      |
        |<====================>|                      |
        |                      |                      |
        |          Distribute Push Resource           |
        |-------------------------------------------->|
        |                      |                      |
        :                      :                      :
        |                      |     Push Message     |
        |    Push Message      |<---------------------|
        |<---------------------|                      |
        |                      |                      |

该时序图表明了 Web Push 的各个步骤,我们可以将其分为订阅(subscribe)与推送(push)两部分来看

订阅 subscribe

  1. Ask Permission:这一步不在上图的流程中,这其实是浏览器中的策略。浏览器会询问用户是否允许通知,只有在用户允许后,才能进行后面的操作
  2. Subscribe:浏览器(客户端)需要向 Push Service 发起订阅(subscribe),订阅后会得到一个PushSubscription对象
  3. Monitor:订阅操作会和 Push Service 进行通信,生成相应的订阅信息,Push Service 会维护相应信息,并基于此保持与客户端的联系
  4. Distribute Push Resource:浏览器订阅完成后,会获取订阅的相关信息(存在于PushSubscription对象中),我们需要将这些信息发送到自己的服务端,在服务端进行保存
订阅过程

推送 Push Message

  1. 我们的服务端需要推送消息时,不直接和客户端交互,而是通过 Web Push 协议,将相关信息通知 Push Servic;
  2. Push Service 收到消息,通过校验后,基于其维护的客户端信息,将消息推送给订阅了的客户端
  3. 最后,客户端收到消息,完成整个推送过程
推送过程

什么是 Push Service

在上面的 Push 流程中,出现了一个比较少接触到的角色:Push Service。那么什么是 Push Service 呢?

我们来看官方解释:

A push service receives a network request, validates it and delivers a push message to the appropriate browser. If the browser is offline, the message is queued until the browser comes online.

译: 推送服务接收一个网络请求,对其进行验证,并将推送消息传递到适当的浏览器。如果浏览器离线,则消息将排队等待浏览器在线。

目前,不同的浏览器厂商使用了不同的 Push Service。例如,chrome 使用了 google 自家的 FCM(前身为GCM),firefox 也是使用自家的服务。那么我们是否需要写不同的代码来兼容不同的浏览器所使用的服务呢?答案是并不用。Push Service 遵循 Web Push Protocol,其规定了请求及其处理的各种细节,这就保证了,不同的 Push Service 也会具有标准的调用方式

这里再提一点:我们在上一节中说了 Push 的标准流程,其中第一步就是浏览器发起订阅,生成一个PushSubscription对象。Push Service 会为每个发起订阅的浏览器生成一个唯一的 URL,这样我们在服务端推送消息时,向这个 URL 进行推送后,Push Service 就会知道要通知哪个浏览器。而这个 URL 信息也在PushSubscription对象里,叫做endpoint

PushSubscription 对象

那么,如果我们知道了endpoint的值,是否就代表我们可以向客户端推送消息了呢?并非如此。下面会简单介绍一下 Web Push 中的安全策略

如何保证 Push 的安全性

在 Web Push 中,为了保证客户端只会收到其订阅的服务端推送的消息(其他的服务端即使在拿到endpoint也无法推送消息),需要对推送信息进行数字签名。该过程大致如下:

在 Web Push 中会有一对公钥与私钥。客户端持有公钥,而服务端持有私钥。客户端在订阅时,会将公钥发送给 Push Service,而 Push Service 会将该公钥与相应的endpoint维护起来。而当服务端要推送消息时,会使用私钥对发送的数据进行数字签名,并根据数字签名生成一个叫Authorization请求头。Push Service 收到请求后,根据endpoint取到公钥,对数字签名解密验证,如果信息相符则表明该请求是通过对应的私钥加密而成,也表明该请求来自浏览器所订阅的服务端。反之亦然

而公钥与私钥如何生成,会在下面的实例中讲解

如何使用 Push API 来推送向用户推送信息

为了使文章与代码更清晰,将Web Push分为这几个部分:

  • 浏览器发起订阅,并将订阅信息发送至后端;
  • 将订阅信息保存在服务端,以便今后推送使用;
  • 服务端推送消息,向Push Service发起请求;
  • 浏览器接收Push信息并处理。

友情提醒:由于 Chrome 所依赖的 Push Service——FCM 在国内不可访问,所以要正常运行 demo 中的代码需要“梯子”,或者可以选择 Firefox 来进行测试

浏览器(客户端)生成 subscription 信息

首先,我们需要使用PushManagersubscribe方法来在浏览器中进行订阅

在上一节中我们已经知道了如何注册 Service Worker。当我们注册完 Service Worker 后会得到一个Registration对象,通过调用Registration对象的registration.pushManager.subscribe()方法可以发起订阅

为了使代码更清晰,本篇 demo 在之前的基础上,先抽离出 Service Worker 的注册方法:

  • public/index.js
function registerServiceWorker() {
  if (!navigator.serviceWorker) {
    return Promise.reject('系统不支持 service worker')
  }

  return navigator.serviceWorker.register('./sw.js').then(function (reg) {
    registration = reg
  })
}

然后定义了 subscribeAndDistribute() 方法来发起订阅:

  • public/index.js
// 订阅推送并将订阅结果发送给后端
function subscribeAndDistribute(registration) {
  if (!window.PushManager) {
    return Promise.reject('系统不支持消息推送')
  }
  // 检查是否已经订阅过
  return registration.pushManager
    .getSubscription()
    .then(function (subscription) {
      // 如果已经订阅过,就不重新订阅了
      if (subscription) {
        distributePushResource(subscription)
      } else {
        return (
          // 订阅
          registration.pushManager
            .subscribe({
              userVisibleOnly: true,
              applicationServerKey: window.base64ToUint8Array(VAPIDPublicKey),
            })
            .then(function (subscription) {
              distributePushResource(subscription)
            })
        )
      }
    })
}

这里使用了registration.pushManager.subscribe()方法中的两个配置参数:userVisibleOnlyapplicationServerKey

  • userVisibleOnlyuserVisibleOnly表明该推送是否需要显性地展示给用户,即推送时是否会有消息提醒。如果没有消息提醒就表明是进行“静默”推送。在Chrome中,必须要将其设置为true,否则浏览器就会在控制台报错:

  • applicationServerKeyapplicationServerKey是一个客户端的公钥,VAPID定义了其规范,因此也可以称为 VAPID keys。该参数需要 Unit8Array 类型。因此定义了一个urlBase64ToUint8Array方法将 base64 的公钥字符串转为 Unit8Array。subscribe()也是一个 Promise 方法,在 then 中我们可以得到订阅的相关信息——一个PushSubscription对象。下图展示了这个对象中的一些信息。注意其中的endpoint,Push Service 会为每个客户端随机生成一个不同的值

    PushSubscription 信息

之后,我们再将PushSubscription信息发送到后端。这里定义了一个distributePushResource()方法,该方法就是一个普通的 XHR 请求,会向接口 post 订阅信息

  • public/index.js
// 将订阅信息传给后端服务器
function distributePushResource(subscription) {
  // 为了方便之后的推送,为每个客户端简单生成一个标识
  const body = {
    subscription,
    uniqueid: new Date().getTime(),
  }
  console.log('uniqueid', body.uniqueid)

  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest()
    xhr.timeout = 60000
    xhr.onreadystatechange = function () {
      var response = {}
      if (xhr.readyState === 4 && xhr.status === 200) {
        try {
          response = JSON.parse(xhr.responseText)
        } catch (e) {
          response = xhr.responseText
        }
        resolve(response)
      } else if (xhr.readyState === 4) {
        resolve()
      }
    }
    xhr.onabort = reject
    xhr.onerror = reject
    xhr.ontimeout = reject
    xhr.open('POST', '/subscription', true)
    xhr.setRequestHeader('Content-Type', 'application/json')
    xhr.send(JSON.stringify(body))
  })
}

当网站在完成推送订阅之后,Web Push API 也提供了相应的方法来取消订阅。前面提到推送订阅成功之后 PushManager.subscribe() 方法返回的 pushSubscription 对象上有一个 unsubscribe() 就是用来取消订阅的:

pushSubscription.unsubscribe().then(function () {
  console.log('取消订阅成功!')
})

在取消订阅之前,我们可以通过 PushManager.getSubscription() 方法来判断用户是否已经订阅,如果用户已经订阅过,那么该函数会返回 pushSubscription 对象,这样接下来再调用 unsubscribe() 方法最终取消订阅。如下所示:

registration.pushManager.getSubscription().then(function (pushSubscription) {
  if (!pushSubscription) {
    // 用户尚未订阅
    return
  }
  // 取消订阅
  return pushSubscription.unsubscribe()
})
.then(function () {
  console.log('取消订阅!')
})

最后,将这一系列方法组合在一起:

  • public/index.js
const VAPIDPublicKey =
  'BOEQSjdhorIf8M0XFNlwohK3sTzO9iJwvbYU-fuXRF0tvRpPPMGO6d_gJC_pUQwBT7wD8rKutpNTFHOHN3VqJ0A'
// 注册 service worker 并缓存 registration
let registration

// 注册 service worker
registerServiceWorker()
  // 申请桌面通知权限
  .then(function () {
    requestNotificationPermission()
  })
  // 订阅推送
  .then(function () {
    subscribeAndDistribute(registration)
  })
  .catch(function (err) {
    console.log(err)
  })

function registerServiceWorker() {
  if (!navigator.serviceWorker) {
    return Promise.reject('系统不支持 service worker')
  }

  return navigator.serviceWorker.register('./sw.js').then(function (reg) {
    registration = reg
  })
}

// 申请桌面通知权限
function requestNotificationPermission() {
  // 系统不支持桌面通知
  if (!window.Notification) {
    return Promise.reject('系统不支持桌面通知')
  }
  return Notification.requestPermission().then(function (permission) {
    if (permission === 'granted') {
      return Promise.resolve()
    }
    return Promise.reject('用户已禁止桌面通知权限')
  })
}

// 订阅推送并将订阅结果发送给后端
function subscribeAndDistribute(registration) {
  if (!window.PushManager) {
    return Promise.reject('系统不支持消息推送')
  }
  // 检查是否已经订阅过
  return registration.pushManager
    .getSubscription()
    .then(function (subscription) {
      // 如果已经订阅过,就不重新订阅了
      if (subscription) {
        distributePushResource(subscription)
      } else {
        return (
          // 订阅
          registration.pushManager
            .subscribe({
              userVisibleOnly: true,
              applicationServerKey: window.base64ToUint8Array(VAPIDPublicKey),
            })
            .then(function (subscription) {
              distributePushResource(subscription)
            })
        )
      }
    })
}

// 将订阅信息传给后端服务器
function distributePushResource(subscription) {
  // 为了方便之后的推送,为每个客户端简单生成一个标识
  const body = {
    subscription,
    uniqueid: new Date().getTime(),
  }
  console.log('uniqueid', body.uniqueid)

  return new Promise(function (resolve, reject) {
    var xhr = new XMLHttpRequest()
    xhr.timeout = 60000
    xhr.onreadystatechange = function () {
      var response = {}
      if (xhr.readyState === 4 && xhr.status === 200) {
        try {
          response = JSON.parse(xhr.responseText)
        } catch (e) {
          response = xhr.responseText
        }
        resolve(response)
      } else if (xhr.readyState === 4) {
        resolve()
      }
    }
    xhr.onabort = reject
    xhr.onerror = reject
    xhr.ontimeout = reject
    xhr.open('POST', '/subscription', true)
    xhr.setRequestHeader('Content-Type', 'application/json')
    xhr.send(JSON.stringify(body))
  })
}

注: 这里为了方便我们后面的推送,为每个客户端生成了一个唯一IDuniqueid,这里使用了时间戳生成简单的uniqueid

由于userVisibleOnlytrue,所以需要用户授权开启通知权限,因此我们会看到下面的提示框,选择“允许”即可。你可以在设置中进行通知的管理

显示通知弹窗

服务端存储客户端 subscription 信息

为了存储浏览器 post 来的订阅信息,服务端需要增加一个接口/subscription,同时添加中间件koa-body用于处理 body

  • routes/index.js
const router = require('koa-router')()
const { koaBody } = require('koa-body')

/**
 * @description: 提交 subscription 信息,并保存
 * @return {*}
 */
router.post('/subscription', koaBody(), async (ctx) => {
  let body = ctx.request.body
  await util.saveRecord(body)
  ctx.response.body = {
    success: true,
  }
})

接收到 subscription 信息后,需要在服务端进行保存,你可使用任何方式来保存它:mysql、redis、mongodb… 这里为了方便,我使用了nedb来进行简单的存储。nedb不需要部署安装,可以将数据存储在内存中,也可以持久化,nedb 的 api 和 mongodb 也比较类似

这里util.saveRecord()做了这些工作:首先,查询subscription信息是否存在,若已存在则只更新uniqueid;否则,直接进行存储

推送信息

在实际中,我们一般会给运营或产品同学提供一个推送配置后台。可以选择相应的客户端,填写推送信息,并发起推送。为了简单起见,我并没有写一个推送配置后台,而只提供了一个 post 接口/push来提交推送信息。后期我们完全可以开发相应的推送后台来调用该接口

  • routes/index.js
/**
 * @description: 消息推送API,可以在管理后台进行调用
 * @return {*}
 */
router.post('/push', koaBody(), async (ctx) => {
    const data = ctx.request.body
  let { uniqueid } = data
  let list = uniqueid ? await util.find({ uniqueid }) : await util.findAll()

  for (let i = 0; i < list.length; i++) {
    let subscription = list[i].subscription
    pushMessage(subscription, JSON.stringify(data))
  }

  ctx.response.body = {
    data: list,
  }
})

来看一下/push接口。

  1. 首先根据 post 的参数不同,我们可以通过uniqueid来查询某条订阅信息:util.find({uniqueid});也可以从数据库中查询出所有订阅信息:util.findAll()
  2. 然后通过pushMessage()方法向 Push Service 发送请求。根据第二节的介绍,我们知道,该请求需要符合Web Push协议。然而,Web Push协议的请求封装、加密处理相关操作非常繁琐。因此,Web Push为各种语言的开发者提供了一系列对应的库:Web Push Libaray,目前有NodeJS、PHP、Python、Java 等。把这些复杂而繁琐的操作交给它们可以让我们事半功倍
  3. 最后返回结果,这里返回了所有的订阅信息

web-push

安装

npm install web-push --save

密钥

公钥和私钥可以在线生成, 也可以通过下面方法用命令生成:

npm install web-push -g

web-push generate-vapid-keys

得到的结果如下所示:

=======================================

Public Key:
BO45dpS6296H9sSWdVcZsnsYeHXCXgYv-9jEUcyFrNfUvmvWF_c5iniytzHU_pEP9mE50xcUKbJrqh-hmMbvLZs

Private Key:
qXsFHan2sLkH0RFHGkNIkVgiUmox3uVYKwEAphUo7II

=======================================

转码

正如在订阅推送中提到的,subscribe 方法通过 applicationServerKey 传入所需要的公钥。一般来说得到的公钥一般都是 base64 编码后的字符串,需要将其转换成 Uint8Array 格式才能作为 subscribe 的参数传入。下面给出一个 base64 转 Uint8Array 的函数实现:

function base64ToUint8Array (base64String) {
  let padding = '='.repeat((4 - base64String.length % 4) % 4)
  let base64 = (base64String + padding)
    .replace(/\-/g, '+')
    .replace(/_/g, '/')
  let rawData = atob(base64)
  let outputArray = new Uint8Array(rawData.length)

  for (let i = 0; i < rawData.length; i++) {
    outputArray[i] = rawData.charCodeAt(i)
  }
  return outputArray
}

使用

const webpush = require('web-push')

// VAPID
const VAPIDKeys = {
    publicKey: 'BOEQSjdhorIf8M0XFNlwohK3sTzO9iJwvbYU-fuXRF0tvRpPPMGO6d_gJC_pUQwBT7wD8rKutpNTFHOHN3VqJ0A',
    privateKey: 'TVe_nJlciDOn130gFyFYP8UiGxxWd3QdH6C5axXpSgM'
}

// 设置 web-push 的 VAPID 值
webpush.setVapidDetails(
  'mailto:shenxh0928@gmail.com', // 联系邮箱
  VAPIDKeys.publicKey,
  VAPIDKeys.privateKey,
)

设置完成后即可使用webpush.sendNotification()方法向 Push Service 发起请求。

最后我们来看下pushMessage()方法的细节:

/**
 * 向push service推送信息
 * @param {*} subscription
 * @param {*} data
 */
function pushMessage(subscription, data = {}) {
  webpush
    .sendNotification(subscription, data, options)
    .then((res) => {
      console.log('push service的相应数据:', JSON.stringify(res))
      return
    })
    .catch((err) => {
      // 判断状态码,440和410表示失效
      if (err.statusCode === 410 || err.statusCode === 404) {
        return util.remove(subscription)
      } else {
        console.log('失败', err)
      }
    })
}

webpush.sendNotification为我们封装了请求的处理细节。状态码401和404表示该 subscription 已经无效,可以从数据库中删除

Service Worker 监听 Push 消息

调用webpush.sendNotification()后,我们就已经把消息发送至 Push Service了;而 Push Service 会将我们的消息推送至浏览器

要想在浏览器中获取推送信息,只需在 Service Worker 中监听push的事件即可:

  • public/sw.js
// 监听 push 事件
self.addEventListener('push', function (e) {
  var data = e.data

  if (e.data) {
    data = data.json()
    console.log('push 的数据为:', data)
  } else {
    console.log('没有 push 任何数据')
  }
})

Push Service可以在设备离线时,帮你维护推送消息。当浏览器设备重新联网时,就会收到该推送

兼容性

又到了查看兼容性的时间了。对于Push API,目前 Safari 团队并没有明确表态计划支持

Push API 兼容性

其实比兼容性更大的一个问题是,Chrome 所依赖的 FCM 服务在国内是无法访问的,而 Firefox 的服务在国内可以正常使用。这也是为什么在代码中会有这一项设置:

const options = {
  proxy: 'http://34.82.107.67' // 使用FCM(Chrome)需要配置代理
}

注: 免费代理获取方法: 戳这里

虽然由于 google 服务被屏蔽,导致国内 Push 功能无法在 chrome 上使用,但是作为一个重要的技术点,Web Push 还是非常值得我们了解与学习的

本章分支: push

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

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

相关文章

很好用的URL工具类

&#x1f4e2; &#x1f4e2; &#x1f4e2; &#x1f4e3; &#x1f4e3; &#x1f4e3;哈喽&#xff01;大家好&#xff0c;我是「奇点」&#xff0c;江湖人称 singularity。刚工作几年&#xff0c;想和大家一同进步 &#x1f91d; &#x1f91d;一位上进心十足的【Java ToB端…

【C++】deque双端队列

目录deque的原理介绍deque的优点和缺陷deque的原理介绍 1.deque(双端队列)&#xff1a;是一种双开口的“连续”空间的数据结构&#xff0c;双开口的含义是&#xff1a;在头尾两端都可以进行插入和删除操作&#xff0c;且时间复杂度为O(1)。 需要注意的是deque并不是真正连续的…

制作一个微信小程序步骤_分享个人微信小程序开发步骤

微信小程序的功能不断提高&#xff0c;以及用户对小程序的使用的增加&#xff0c;使得新一批的流量融入小程序中&#xff0c;越开越多的企业开始开发小程序&#xff0c;想要从中分一碗羹&#xff0c;今天内容就从如何制作一个微信小程序说起&#xff0c;希望对你有帮助。微信小…

Vue鼠标移入移出事件(冒泡问题)

一、定义1、mouseenter&#xff1a;在鼠标光标从元素外部首次移动到元素范围之内时触发&#xff0c;这个事件不冒泡。2、mouseleave&#xff1a;在位于元素上方的鼠标光标移动到元素范围之外时触发&#xff0c;这个事件不冒泡。3、mouseover&#xff1a;在鼠标指针位于一个元素…

Flink学习31:Table API SQL之注册表

table API tableAPI & flink SQL flink SQL是最上层的封装&#xff0c;封装了流处理的DataStream API 和 批处理的DataSet API。 但是DataStream API 、DataSet API、table API、flink SQL 这几层并没有分到不同层中&#xff0c;所以应用程序可以同时使用这几层。 Table A…

合合信息——用智能文字识别技术赋能古彝文原籍数字化

文章目录1. 背景介绍&#xff1a;古彝文是什么&#xff1f;为什么要保护它&#xff1f;如何保护它&#xff1f;2. 传统方法保护古彝文&#xff1a;原籍难获、翻译困难2.1. 古彝文原籍的破损与古法保存2.2 古彝文原籍的保护与翻译2.2.1 获取古彝文原籍2.2.2 修复古彝文原籍2.2.3…

ansible作业一

配置ansible学习环境实现以下要求 1.控制主机和受控主机通过root用户通过免密验证方式远程控住受控主机实施对应&#xff08;普通命令&#xff0c;特权命令&#xff09;任务 2.控制主机连接受控主机通过普通用户以免密验证远程控住受控主机实施指定&#xff08;普通命令&#x…

Makefile快速入门

介绍 这里以一个例子来演示利用Makfile进行多文件编译 一共有四个源程序&#xff1a;main.cpp&#xff0c;printhello.cpp&#xff0c;factorial.cpp&#xff0c;functions.h 首先是main.cpp内容 #include<iostream> #include"functionals.h"using namespa…

Apache与Nginx虚拟机的三种访问+非简单请求+跨域知识点整理

Apache 在D:\project\web\index.html中写入 <h1>welcome useing apache!</h1>基于ip访问 打开phpstudy_pro\Extensions\Apache2.4.39\conf\extra\httpd-vhosts.conf写入 <VirtualHost 192.168.1.4:80>ServerAdmin 88888888163.com #管理员邮箱DocumentRoo…

【BP靶场portswigger-服务端7】访问控制漏洞和权限提升-11个实验(全)

前言&#xff1a; 介绍&#xff1a; 博主&#xff1a;网络安全领域狂热爱好者&#xff08;承诺在CSDN永久无偿分享文章&#xff09;。 殊荣&#xff1a;CSDN网络安全领域优质创作者&#xff0c;2022年双十一业务安全保卫战-某厂第一名&#xff0c;某厂特邀数字业务安全研究员&…

【手写 Vue2.x 源码】第十五篇 - 生成 ast 语法树 - 构造树形结构

一&#xff0c;前言 上篇&#xff0c;主要介绍了生成 ast 语法树 - 模板解析部分 使用正则对 html 模板进行解析和处理&#xff0c;匹配到模板中的标签和属性 本篇&#xff0c;生成 ast 语法树 - 构造树形结构 二&#xff0c;构建树形结构 1&#xff0c;需要描述什么 前面…

文本相似度

传统方法 基于TF-IDF、BM25、Jaccord、SimHash、LDA等算法抽取两个文本的词汇、主题等层面的特征&#xff0c;然后使用机器学习模型&#xff08;LR, xgboost&#xff09;训练分类模型优点&#xff1a;可解释性较好缺点&#xff1a;依赖人工寻找特征&#xff0c;泛化能力一般&a…

linux有用技巧:使用ntfs-3g挂载ntfs设备

1.几种文件系统的比较 (1)在linux系统中支持以下文件系统&#xff1a; Ext2 第二扩展文件系统&#xff08;简称 ext2&#xff09;很多年前就已经成为 GNU/Linux 默认的文件系统了。ext2 取代了扩展文件系统(这是 “第二代” 的前身)。它纠正了它前身的一些错误并突破了…

【工具篇】41 # 常用可视化工具集整理(完结)

说明 【跟月影学可视化】学习笔记。 可视化场景主体需求 主体需求示例绘制基本图形根据数据绘制一些二维、三维的几何图形&#xff0c;它们不一定是完整的图表&#xff0c;通常是具有灵活性和视觉冲击力的小图形&#xff0c;例如粒子效果绘制基础图表通常是指绘制常见的饼图…

让开发人员偷懒的正则表达式

正则表达式是一种基于特殊模式符号系统的文本处理系统。简而言之&#xff0c;它为程序员提供了轻松处理和验证字符串的能力。它代表了DRY&#xff08;Dont Repeat Yourself&#xff09;原则的实现&#xff0c;在几乎所有支持的语言中&#xff0c;正则表达式模式根本不会改变形式…

【信管7.2】质量保证与质量控制

质量保证与质量控制项目质量管理的两个核心过程就是实施质量保证和控制质量。关于它们两个的区别我们在上一课已经讲了&#xff0c;实施质量保证其实保证的是过程&#xff0c;就是我们的开发过程是不是遵循了质量计划&#xff0c;也就是说&#xff0c;这是保证过程有质量的一个…

视频录制软件有哪些?4款录制视频软件,免费下载

对于不了解录屏的用户&#xff0c;肯定会有这些视频是如何制作出来的疑惑&#xff1f;其实录制视频一件很容易的事情&#xff0c;只需要找到一个可以在录视频的软件就可以了。哪有什么录制视频软件可以录制的呢&#xff1f;下面小编给大家分享4款就可以录制视频的软件&#xff…

ORB-SLAM3算法和代码学习—跟踪恒速运动模型TrackWithMotionModel()

0总述 跟踪运动模型核心思想&#xff1a;假设在极短时间内&#xff0c;相机的运动相同。设相邻时刻的三帧图像分别为k-2帧&#xff0c;k-1帧&#xff0c;k帧&#xff0c;则认为k-2帧到k-1帧相机的运动T_delta1和k-1帧到k帧相机的运动T_delta2相等&#xff0c;如下图所示。当相…

K8S概述及用途

K8S概述 1.K8S说明 K8S(Kubernetes) 是一个可移植的、可扩展的开源平台&#xff0c;用于管理容器化的工作负载和服务&#xff0c;可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快速增长的生态系统。Kubernetes 的服务、支持和工具广泛可用。 Kubernetes 这个名字源…

海量数据存储面临的问题

海量数据存储面临的问题海量数据存储面临的问题成本高性能低可扩展性差如何实现分布式文件存储如何支撑高效率的计算分析如何解决海量数据存储的问题如何解决海量数据文件查询便捷问题如何解决大文件传输效率慢的问题如何解决硬件故障数据丢失问题如何解决用户查询视角统一规整…