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

article/2025/9/26 18:10:41

引言

在接下来的内容里,我们会探究 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 = 60000xhr.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 = rejectxhr.onerror = rejectxhr.ontimeout = rejectxhr.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 = 60000xhr.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 = rejectxhr.onerror = rejectxhr.ontimeout = rejectxhr.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.bodyawait 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.bodylet { uniqueid } = datalet list = uniqueid ? await util.find({ uniqueid }) : await util.findAll()for (let i = 0; i < list.length; i++) {let subscription = list[i].subscriptionpushMessage(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 -gweb-push generate-vapid-keys

得到的结果如下所示:

=======================================Public Key:
BO45dpS6296H9sSWdVcZsnsYeHXCXgYv-9jEUcyFrNfUvmvWF_c5iniytzHU_pEP9mE50xcUKbJrqh-hmMbvLZsPrivate 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.dataif (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://chatgpt.dhexx.cn/article/UIkkyUfw.shtml

相关文章

TWAMP 协议

使用标准协议测量IP网络性能始终是一项挑战。IP发明者曾作为TCP/IP协议套件的一部分提供了一些工具,如Internet控制消息协议(ICMP)Ping、Traceroute和用户数据报协议(UDP)Echo。但是,这些工具并不是为执行总体性能测试而准备的,而是设计用于简单排查IP网络故障。因此,企…

HTAP技术

传统业务采取隔天隔天将数据抽取到OLAP的方式&#xff0c;带来了两个问题 1&#xff0c; 报表往往需要隔天才能出来&#xff0c;无法满足业务需要&#xff1b; 2&#xff0c;带来了多副本的问题&#xff1b; OLTP里面至少三个副本&#xff1b; OLAP里面又至少3个副本&#xf…

【区块链 | Uniswap】3.剖析DeFi交易产品之Uniswap:V2下篇

前言 上篇我们主要讲了 UniswapV2 整体分为了哪些项目&#xff0c;并重点讲解了 uniswap-v2-core 的核心代码实现&#xff1b;中篇主要对 uniswap-v2-periphery 的路由合约实现进行了剖析&#xff1b;现在剩下 V2 系列的最后一篇&#xff0c;我会介绍剩下的一些内容&#xff0…

量化交易中VWAP/TWAP算法的基本原理和简单源码实现(C++和python)

参考文献&#xff1a; https://en.wikipedia.org/wiki/Time-weighted_average_price https://en.wikipedia.org/wiki/Volume-weighted_average_price http://blog.sina.com.cn/s/blog_163a2b9700102wdy0.html https://www.douban.com/note/214362575 算法交易其实主要是用在…

用高频交易的方式对TWAP算法优化(TWAP算法、算法实现、高频交易、冰山算法)

用高频交易的方式对TWAP算法进行优化。 以下内容主要基于执行算法的讨论。 什么是TWAP算法&#xff08;时间加权平均价格&#xff09;&#xff1f; TWAP交易时间加权平均价格Time Weighted Average Price 模型是把一个母单的数量平均地分配到一个交易时段上。该模型将交易时…

算法交易简介以及TWAP、VWAP算法原理

算法交易视频&#xff1a;算法交易视频 1&#xff0c;交易成本&#xff1a; 交易成本分成两类&#xff0c;一类是显性成本&#xff0c;包括佣金&#xff08;包括券商佣金&#xff08;券商收取&#xff09;&#xff0c;交易经手费&#xff08;交易所收取&#xff0c;千分之0.0…

时间加权平均价格算法(TWAP)和成交量平均算法(VWAP)在量化回测的应用

为什么要引入TWAP和 VWAP&#xff1f; 为了评估策略的资金容量&#xff0c;我们对M.trade模块里买入点和卖出点这两个参数进行了更丰富的扩展&#xff0c;支持了策略能够按更丰富的算法交易价格&#xff08;WAP&#xff09;进行撮合。 如果资金是10万的话&#xff0c;那么在开…

平均价格算法:TWAP vs. VWAP

时间加权平均价格 (TWAP) 和成交量加权平均价格 (VWAP) 算法应用不同的方法来计算资产价格&#xff0c;这是所有去中心化金融 (DeFi) 原语的组成部分。 在本文中&#xff0c;我们介绍了 TWAP 和 VWAP 算法之间的差异&#xff0c;解释了它们如何在区块链环境中为资产定价&#…

json对象中的数组怎么转化为json字符串

注意&#xff1a;去掉数组下标&#xff0c;才有效&#xff1f; JSON是一种数据交换格式&#xff0c;与XML数据格式相比更加方便使用&#xff0c;互联网中的URL请求接口大部分都是已JSON数据格式进行交互&#xff0c;对接数据解析也是非常方便容易&#xff0c;在字符串与JSON对…

json对象转对象数组

对象的两种取值方式 let obj {name: wan}; console.log(obj.name); //wan这是最普通的一种方式&#xff0c;还有一种方式我们用的不太多&#xff0c;就是使用[]包住属性名取值 let obj {name: wan}; console.log(obj[name]); //wan将对象转化为数组 let obj {未完成:5, 已…

JSON对象/数组与JSON字符串之间的相互转换

文章目录 前言JSON介绍如何判断JS数据类型JSON数组转化为JSON字符串JSON对象转化为JSON字符串JSON字符串转化为JSON数组/对象注意点 前言 这里先介绍一个个人觉得很好用的谷歌浏览器的功能&#xff1a;snippet 就是类似收藏夹&#xff0c;平常你可能会用到的调试有关的东西都可…

json转数组(json数组对象)

哪位知道json格式怎么转换为word&#xff1f;哪位知道json格式 使用office工具进行转换 jquery如何读取并显示JSON数组 1、新建一个html文件&#xff0c;命名为test。html&#xff0c;用于讲解ajax怎么获取json数据并输出。 2、在test。html中&#xff0c;使用script标签加载j…

将数组转换为JSON数据

如何将数组转换为JSON数据&#xff1f;下面本篇就来给大家介绍一下将数组转换为JSON对象的方法&#xff0c;希望对大家有所帮助。 方法一&#xff1a;使用Object.assign() Object.assign()方法将枚举的所有属性的值从源对象&#xff08;一个或多个&#xff09;复制到目标对象 …

js中json对象转换为array

今天遇到了一个将json对象格式转换为js数组的问题&#xff0c;网上down了一下,发现只是对json进行了一些操作。在此写了一个小小的代码&#xff0c;以供有需要的人使用。 开门见山&#xff0c;先声明一个json对象数组&#xff0c;在此说一下&#xff0c;js中数组使用 " [ …

java json对象和json数组对象的处理

在开发过程中&#xff0c;经常需要和别的系统交换数据&#xff0c;数据交换的格式有XML、JSON等&#xff0c;JSON作为一个轻量级的数据格式比xml效率要高&#xff0c;XML需要很多的标签&#xff0c;这无疑占据了网络流量&#xff0c;JSON在这方面则做的很好&#xff0c;下面先看…

数组转JSON json对象 json字符串

入参格式JSON对象与JSON字符串的区别 1、Object.assign() Object.assign() 方法将所有可枚举&#xff08;Object.propertyIsEnumerable() 返回 true&#xff09;的自有&#xff08;Object.hasOwnProperty() 返回 true&#xff09;属性从一个或多个源对象复制到目标对象&#…

js 数组转json,json转数组

//数组转json串 var arr [1,2,3, { a : 1 } ]; JSON.stringify( arr );//json字符串转数组 var jsonStr [1,2,3,{"a":1}]; JSON.parse( jsonStr );

Java利用fastjson解析复杂嵌套json字符串、json数组;json字符串转Java对象,json数组转list数组

文章目录 前言一、什么是JSON对象&#xff1f;二、什么是json字符串&#xff1f;二、什么是JSON数组&#xff1f;三、复杂、嵌套的json字符串四、json字符串转换4.1 简单json字符串转换为java对象4.2 简单json字符串数组转换为list数组4.3 复杂嵌套json字符串数组转换为Java对象…

将嵌套的json对象转化为json数组(python列表推导式)

需求说明 想将一个json文件录入MongoDB&#xff0c;如下图所示。 但是原文件是嵌套json对象的形式&#xff0c;如果直接导入MongoDB&#xff0c;会整体变成一个Document&#xff1a; 注&#xff1a;MongoDB中的Document相当于SQL中Row的概念。 而我希望一个Document对应一个…

Oracle 基础总结:日期函数专题

Oracle 基础总结&#xff1a;日期函数专题 日期函数:1、SYSDATE2、日期函数&#xff1a;(1&#xff09; MONTHS_BETWEEN&#xff08;日期1&#xff0c;日期2&#xff09;&#xff1a;返回两个日期相差的月数。(2&#xff09; ADD_MONTHS&#xff08;日期&#xff0c;数值&#…