你可能需要的多文档页面交互方案

前言

欢迎关注同名公众号《熊的猫》,文章会同步更新!

在日常工作中,面对不同的需求场景,你可能会遇到需要进行多文档页面间交互的实现,例如在 A 页面跳转到 B 页面进行某些操作后,A 页面需要针对该操作做出一定的反馈等等,这个看似简单的功能,却也需要根据不同场景选择不同的方案。

445C9191.jpg

这里所说的场景实际上可分为两个大方向:同源策略文档加载方式,那么本篇文章就来探讨一下这两个方面。

同源策略 & 文档加载方式

在正式开始之前,我们还是先简单聊一下同源策略和页面加载方式,如果你已经足够了解了,可以选择跳过阅读。

同源策略

基本概念

所谓的 同源策略 实际上是 浏览器 的一个重要的 安全策略,主要是用于限制 一个源 的文档 或者 其加载的脚本 是否可以与 另一个源 的资源进行交互。

注意】这里的目标是浏览器,也就是只有浏览器有同源策略的限制,例如服务端就不存在什么同源策略,这里的浏览器包括 桌面端浏览器移动端浏览器微信内置浏览器虚拟浏览器(虚拟环境中运行的网络浏览器) 等。

所谓的 就是我们常说的 协议、主机名(域名)、端口,所以所谓的 同源 也就是指两个 URL 的 协议、主机名(域名)、端口 等信息要完全匹配。

主要作用

同源策略 可以用来阻隔恶意文档,减少可能被攻击的媒介,下面还是通过一个 CSRF 例子讲解一下没有同源限制会发生什么。

CSRF 攻击

假设你在 A 网站上进行了登录并成功登入网站后,你发现 A 网站上出现了一个广告弹窗(写着:拒绝 huang,拒绝 du,拒绝 pingpangqiu),于是放纵不羁爱自由的你(为了验证真理)点开了它,发现这个网站居然不讲武德,啥也不是…

4488455F.gif

表明平静如水,背地里实则已经悄悄向 A 站点服务器 发送了请求操作,并且身份验证信息用的是你刚刚登录的认证信息(由于没有同源限制 cookies 会被自动携带在目标请求中),但服务端并不知道这是个假冒者,于是允许了本次操作,结果就是…

文档加载方式

因为这里是说多页面交互,所以前提是至少有一个页面 A 存在,那么基于 A 页面来讲有以下几种方式去加载 B 页面文档:

  • window.location.href
  • <a href="xx" target="xx">
  • window.open
  • iframe

这一部分这里先简单提及,更详细的内容放到最后作为扩展去讲,也许你会奇怪怎么没有 history.pushStatelocation.hash (如 Vue Router、React Router 中的使用),因为它们算属于在页面加载之后的路由导航,看起来虽然是页面切换了,但是切换的是文档的内容,不是整个文档,这一点还是不一样的。

同源策略下的多文档交互

Web Storage

sessionStorage & localStorage

由于多文档的方式并不适合使用 Vuex/Pinia/React Redux 等全局状态管理器,因此 Web Storage 这种应该是我们最先能想到的方式了,而 Web Storage 实际上只包含以下两种:

  • sessionStorage
    • 为每一个给定的源(given origin)维持一个独立的存储区域,该存储区域在页面 会话期间 可用,即只要浏览器处于打开状态,包括页面重新加载和恢复
  • localStorage
    • 为每一个给定的源(given origin)维持一个独立的存储区域,但是在浏览器关闭,然后重新打开后数据仍然存在,即其存储的数据是 持久化的

有些人会把 IndexedDB 也当做 Web Storage 的一种,这在规范定义上是不够准确的.

它们最基本的用法这里就不多说了,总结起来就是:在 B 页面往 Web Storage 中存入数据 X ,在 A 页面中读取数据 X 然后决定需要做什么。

这里我们可以借助 document 文档对象的 visibilitychange 事件来监听当前标签页面是否处于 可见状态,然后再决定是不是要做某些反馈操作。

核心代码:

// A 页面document.addEventListener('visibilitychange', function () {if (document.visibilityState === 'visible') {// do something ...}
})

演示效果如下:

9.gif

值得注意的是,sessionStorage 在不同标签页之间的数据是不能同步,但如果 A 和 B 两个页面属于 同一浏览上下文组 可以实现初始化同步(实际算是拷贝值),后续变化不再同步。

storage 事件

当存储区域(localStorage | sessionStorage)被修改时,将会触发 storage 事件,这是 MDN 上的解释但实际是:

  • 如果当前页面的 localStorage 值被修改,只会触发其他页面的 storage 事件,不会触发本页面的 storage 事件
  • window.onstorage 事件只对 localStorage 的修改有效,sessionStorage 的修改不能触发
  • localStorage 的值必须发生变化,如果设置成相同的值则不会触发

9.gif

window.onstorage 事件配合 localStorage 很完美,但是唯独对 sessionStorage 无效,目前没有发现一个很好且详细的解释。

Cookies & IndexdeDB

这两种和上述的 Web Storage 的实现方式一致,但它们又不属于一类,因此在这里还是额外提出来讲,不过它们可都是有同源策略的限制的。

既然核心方案一致,这里就不多说了,来看看它们的一些区别,便于更好的进行选择:

  • sessionStorage
    • 会话级存储,最多能够存储 5MB 左右,不同浏览器限制不同
    • 不同标签页之间的数据不能同步,但如果 A 和 B 两个页面属于 同一浏览上下文组 可以实现初始化同步(实际算是拷贝值),后续变化不再同步
    • 不支持 结构化存储,只能以 字符串形式 进行存储
  • localStorage
    • 持久级存储,最多能够存储 5MB 左右,不同浏览器限制不同
    • 只要在 同源 的情况下,无论哪个页面操作数据都可以一直保持同步到其他页面
    • 不支持 结构化存储,只能以 字符串形式 进行存储
  • Cookie
    • 默认是 会话级存储,若想实现 持久存储 可以设置 Expires 的值,存储大小约 4KB 左右,不同浏览器限制不同
    • 只要在 同源 的情况下,无论哪个页面操作数据都可以一直保持同步到其他页面
    • 不支持 结构化存储,只能以 字符串形式 进行存储
  • IndexedDB
    • 持久存储,是一种事务型数据库系统(即非关系型),存储大小理论上没有限制,由用户的磁盘空间和操作系统来决定
    • 只要在 同源 的情况下,无论哪个页面操作数据都可以一直保持同步到其他页面
    • 支持 结构化存储,包括 文件/二进制大型对象(blobs)

同一浏览上下文组 可理解为:假设在 A 页面中以 window.open<a href="x" target="_blank">x</a> 方式 打开 B 页面,并且 A 和 B 是 同源 的,那么此时 A 和 B 就属于 同一浏览上下文组

SharedWorker — 共享 Worker

SharedWorker 接口代表一种特定类型的 worker,不同于普通的 Web Worker,它可以从 几个浏览上下文中 访问,例如 几个窗口iframe其他 worker

那么 SharedWorker 的 Shared 指的是什么?

从普通的 Web Worker 的使用来看:

  • 主线程要实例化 worker 实例:const worker = new Worker('work.js');
  • 主线程调用 worker 实例的 postMessage() 方法与 worker 线程发送消息,通过 onmessage 方法用来接收 worker 线程响应的结果
  • worker 线程(即 'work.js')中也会通过 postMessage() 方法 和 onmessage 方法向主线程做相同的事情

从上述流程看没有什么大问题,但是如果是不同文档去加载执行 const worker = new Worker('work.js'); 就会生成一个新的 worker 实例,而 SharedWorker 区别于 普通 Worker 就在这里,如果不同的文档加载并执行 const sharedWorker = new SharedWorker('work.js');,那么除了第一个文档会真正创建 sharedWorker 实例外,其他以相同方式去加载 work.js 的文档就会直接 复用 第一个文档创建的 sharedWorker 实例。

效果演示

9.gif

核心代码

>>>>>>>>>>>>>>>>>> pubilc/worker.js <<<<<<<<<<<<<<
// 保存多个 port 对象
let ports = []// 每个页面进行连接时,就会执行一次
self.onconnect = (e) => {// 获取当前 port 对象const port = e.ports[0]// 监听消息port.onmessage = ({ data }) => {switch (data.type) {case 'init': // 初始化页面信息ports.push({port,pageId: data.pageId,})port.postMessage({from: 'init',data: '当前线程 port 信息初始化已完成',})breakcase 'send': // 单播 || 广播for (const target of ports) {if(target.port === port) continuetarget.port.postMessage({from: target.pageId,data: data.data,})}breakcase 'close':port.close()ports = ports.filter(v => data.pageId !== v.pageId)break}}
}
>>>>>>>>>>>>>>>>>> pubilc/worker.js <<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>> initWorker.ts <<<<<<<<<<<<<<
import { v4 as uuidv4 } from 'uuid'export default (store) => {const pageId = uuidv4()const sharedWorker = new SharedWorker('/worker.js', 'testShare')store.sharedWorker = sharedWorker// 初始化页面信息sharedWorker.port.postMessage({pageId,type: 'init'})// 接收信息sharedWorker.port.onmessage = ({ data }) => {if (data.from === 'init') {console.log('初始化完成', data)return}store.commit('setShareData', data)}// 页面关闭window.onbeforeunload = (e) => {e = e || window.eventif (e) {e.returnValue = '关闭提示'}// 清除操作sharedWorker.port.postMessage({ type: 'close', pageId })return '关闭提示'}
}
>>>>>>>>>>>>>>>>>> initWorker.js <<<<<<<<<<<<<<>>>>>>>>>>>>>>>>>> store/indext.js <<<<<<<<<<<<<<
import { createStore } from 'vuex'
import initWorker from '../initWorker'const store: any = createStore({state: {shareData: {}},getters: {},mutations: {setShareData (state, payload) {state.shareData = payloadconsole.log('收到的消息:', payload)}},actions: {send (state, data) {store.sharedWorker.port.postMessage({type: 'send',data})console.log('发送的消息:', data)}},modules: {}
})// 初始化 worker
initWorker(store)export default store
>>>>>>>>>>>>>>>>>> store/indext.js <<<<<<<<<<<<<<

BroadcastChannel

BroadcastChannel 接口代理了一个命名频道,可以让指定 origin 下的任意 浏览上下文 来订阅它,并允许 同源 的不同浏览器 窗口、Tab 页、frame/iframe 下的不同文档之间相互通信,通过触发一个 message 事件,消息可以 广播 到所有监听了该频道的 BroadcastChannel 对象。

效果演示

9.gif

核心代码

// A.html
<body><h1>A 页面</h1><a href="/b" target="_blank">打开 B 页面</a><br /><button onclick="send()">发送消息给 B 页面</button><h3>收到 B 页面的消息:<small id="small"></small></h3><script>const bc = new BroadcastChannel('test_broadcast_hannel')// 向 B 页面发送消息function send() {console.log('A 页面已发送消息')bc.postMessage('你好呀!')}// 监听来着 A 页面的消息bc.onmessage = ({ data }) => {document.querySelector('#small').innerHTML = event.data}</script></body>// B.html
<body><h1>B 页面</h1><button onclick="send()">发送消息给 B 页面</button><h3>收到 A 页面的消息:<small id="small"></small></h3><script>const bc = new BroadcastChannel('test_broadcast_hannel')// 向 A 页面发送消息function send() {console.log('B 页面已发送消息')bc.postMessage('还不错呦~')}// 监听来着 A 页面的消息bc.onmessage = ({ data }) => {document.querySelector('#small').innerHTML = event.data}</script></body>

HTTP 长轮询

HTTP 长轮询 相信大家应该非常的熟悉了,也许你 过去/现在 正在做的 扫码登录 就是用的长轮询。

由于 HTTP1.1 协议并不支持服务端主动向客户端发送数据消息,那么基于这种 请求-响应 模型,如果我们需要服务端的消息数据,就必须先向服务端发送对应的查询请求,因此只要每隔一段时间向服务器发起查询请求,在根据响应结果决定是继续下一步操作,还是继续发起查询。

核心很像 Web Storage 方案,只不过中间者不同:

  • Web Storage 的中间者是 浏览器,一个页面 存/改 数据,其他页面读取再执行后续操作
  • 长轮询 的中间者是 服务器,一个页面提交请求把目标数据提交到服务端,其他页面通过轮询的方式去读取数据再决定后续操作

由于这种方案比较常见,这里就不再额外演示。

6397E0B3.png

非同源的多文档交互

window.postMessage

通常对于两个不同页面的脚本,只有当执行它们的页面具有:

  • 相同协议(通常为 https
  • 相同端口号443https 的默认值)
  • 相同主机 (两个页面的 Document.domain设置为相同的值)

时,这两个脚本才能相互通信。

window.postMessage() 方法可以 安全 地实现 跨源通信,这个方法提供了一种 受控机制 来规避此限制,本质就是自己注册监听事件,自己派发事件。

window.postMessage() 允许 一个窗口 可以获得对 另一个窗口 的引用(比如 targetWindow = window.opener)的方式,然后在窗口上调用 targetWindow.postMessage() 方法分发一个 MessageEvent 消息。

语法如下,详细解释可见 MDN

targetWindow.postMessage(message, targetOrigin, [transfer]);

63719F00.jpg

window.open() 和 window.postMessage()

9.gif

核心代码

// A.html
<body><h1>A 页面</h1><button onclick="openAction()">打开 B 页面</button><button onclick="send()">发送消息给 B 页面</button><script>let targetWin = nullconst targetOrigin = 'http://127.0.0.1:8082/'// 打开 B 页面function openAction() {targetWin = window.open(targetOrigin)}// 向 B 页面发送消息function send() {if (!targetWin) returnconsole.log('A 页面已发送消息')targetWin.postMessage('你好呀!', targetOrigin)}// 监听来着 B 页面的消息window.onmessage = (event) => {console.log('收到 B 页面的消息:', event.data)}</script></body>// B.html<body><h1>B 页面</h1><button onclick="send()">发送消息给 A 页面</button><script>const targetWin = window.openerconst targetOrigin = 'http://127.0.0.1:8081/'// 监听来着 A 页面的消息window.onmessage = (event) => {console.log("收到 A 页面的消息:", event.data)}// 向 B 页面发送消息function send() {if (!targetWin) returnconsole.log('B 页面已发送消息')targetWin.postMessage('还不错哟~', targetOrigin)}</script></body>

iframe 和 window.postMessage()

眼前的限制

<iframe> 加载的方式有些限制,只能父页面向子页面发送消息,子页面不能向父页面发送消息,本质原因是在父页面中我们可以通过 document.querySelector('#iframe').contentWindow 的方式获取到子页面 window 对象的引用,但是子页面却不能像 window.open() 的方式通过 window.opener 的方式获取父页面 window 对象的引用。

原本想通过 postMessage 将父页面的 window 的代理对象传递过去,但抛出如下异常:

企业微信截图_16812109326512.png

主要原因是 postMessage 是不允许将 Window、Element 等对象进行复制传递,即使可以传递到了子页面中也是无法使用的,因为能传递过去说明你用了深克隆,但深克隆之后已经和原来的父页面无关了。

window.parent 属性

以上思考是在没完全没有想到 window.parent 时的方向,也感谢评论区掘友的提醒,完全可以使用这个 window.parent 化繁为简来获取父页面的 window 对象引用:

  • 如果一个窗口没有父窗口,则它的 parent 属性为 自身的引用
  • 如果当前窗口是一个 <iframe><object><frame> 的加载的内容,那么它的父窗口就是 <iframe><object><frame> 所在的那个窗口

9.gif

核心代码

 // A.html<body><h1>A 页面</h1><button onclick="send()">发送消息给 B 页面</button><h3>收到 B 页面的消息:<small id="small"></small></h3><iframeid="subwin"height="200"src="http://127.0.0.1:8082/"onload="load()"></iframe><script>let targetWin = nullconst targetOrigin = 'http://127.0.0.1:8082/'// B 页面加载完成function load() {// 获取子页面 window 对象的引用let subwin = document.querySelector('#subwin')targetWin = subwin.contentWindow}// 向 B 页面发送消息function send() {if (!targetWin) returnconsole.log('A 页面已发送消息')targetWin.postMessage('你好呀!', targetOrigin)}// 监听来着 A 页面的消息window.onmessage = ({ data }) => {document.querySelector('#small').innerHTML = event.data}</script></body>// B.html
<body><h1>B 页面</h1><button onclick="send()">发送消息给 B 页面</button><h3>收到 A 页面的消息:<small id="small"></small></h3><script>const targetWin = window.parentconst targetOrigin = 'http://127.0.0.1:8081/'// 向 A 页面发送消息function send() {if (targetWin === window) returnconsole.log('B 页面已发送消息')targetWin.postMessage('还不错呦~', targetOrigin)}// 监听来着 A 页面的消息window.onmessage = ({ data }) => {document.querySelector('#small').innerHTML = event.data}</script></body>

websocket

早期 HTTP(超文本传输协议)主要目的就是传输超文本,因为当时网络上绝大多数的资源都是纯文本,许多通信协议也都使用纯文本,因此 HTTP 在设计上不可避免地受到了时代的限制,即 HTTP 没有完全的利用 TCP 协议的 全双工通信 能力,这就是为什么 HTTP 是 半双工通信 的原因。

由于 HTTP 存在早期设计上的限制,但随着互联网的不断发展,越来越需要这种 全双工通信 的功能,因此需要一种新的基于 TCP 实现 全双工通信 的协议,而这个协议就是 WebSocket

具体使用这里不再单独介绍,如果你想了解更多,可以查看往期文章《HTTP,WebSocket 和 聊天室》.

63721876.gif

不过这里还是简单介绍一下,实现的核心就是 不同的页面同一个 websocket 服务 建立连接,多个页面间的通信在 websocket 服务 中进行转发,即页面发送消息到 websocket 服务 根据标识进行 单博广播 的形式下发到其他指定页面

image.png

不同文档加载方式

前面提到的不同的文档加载方式如下:

  • window.location.href
  • <a href="x" target="x">
  • window.open
  • <iframe>

上面已经列举了最常见的方案,跨源方案最全能,这是毋庸置疑的,关于不同文档加载方式也在某些层面上与上述方案挂钩,下面主要讲一些不同文档加载方式的异同点。

window.location.href 和 <a href="x" target="_self">

最常的用法就是通过 window.location.href = x 将当前的文档的 url 进行替换,但其实它是有一些规则的:

  • 有效 url
  • hash 形式
  • 其他形式

x = 有效 url 时,当前文档的内容会被新的 url 指向的内容替换:

9.gif

x = hash 形式 时,会将当前文档的 hash 部分直接替换为 x 指向的内容:

9.gif

x = 其他形式 时,会将 x 的内容作为当前文档 url子路径 进行替换:

9.gif

以上三种形式与 <a href="x" target="_self"> 的表现一致。

window.open 和 <a href="x" target="_blank">

window.open(x)<a href="x" target="_blank"> 的方式都会新打开一个标签页,然后去加载 x 指向的资源,当然其中 x 的加载形式同上。

window.open() 的缺点

浏览器出于安全的考虑,会拦截掉 非用户操作 打开的新页面,也就是指如果我们想在某个异步操作之后自动通过 window.open(x) 的形式打开新页面就会失败,例如:

fetch(url,option).then(res=>{    window.open('http://www.test.com') // 打开失败
})setTimeout(() => {window.open('http://www.test.com') // 打开失败
}, 1000)

image.png

解决办法

  • 将 window.open() 方法放在用户事件中
    • 如在异步操作结束后弹窗提供按钮,让用户手动点击
  • 直接提供 <a> 标签的形式进行跳转
    • 不要妄想通过自动创建 a 标签,然后再通过 a.click() 的方式实现跳转,你能想到浏览器安全限制中也能考虑到
  • window.open() 配合 window.location.href
    • 如下的 clickHandle 本质还是需要用在 用户事件 中,直接自动执行该函数还是会失效,因为毕竟不是由用户动作产生的结果
      const clickHandle = () => {const newWin = window.open('about:blank')ajax().then(res => {newWin.location.href = 'http://www.baidu.com'}).catch(() => {newWin.close()})
      }
      

<iframe>

<iframe> 能够将另一个 HTML 页面嵌入到 当前页面 中,每个嵌入的 浏览上下文 都有自己的 会话历史记录DOM 树

包含嵌入内容的浏览上下文称为 父级浏览上下文顶级浏览上下文(没有父级)通常是由 Window 对象表示的浏览器窗口。

多余的东西在这也不展开了,上面我们使用过的 contentWindow 属性只在 <iframe> 元素上存在,它返回的是当前 iframe 元素HTMLIFrameElement) 所加载文档的 Window 对象的引用。

contentWindow 属性是 可读属性,它所指向的 Window 对象可以去访问这个 iframe 的文档和它内部的 DOM

9.gif

最后

欢迎关注同名公众号《熊的猫》,文章会同步更新!

以上就是本文的全部内容了,纵观文中原本各个看似零散的知识点,在一个需求场景下都被联系起来了,所以有些东西确实学了不一定立刻就会用到,但是真的到需要用到的时候你会发现很多知识点其实都是联系在一起的,并且它们的表现或原理何其相似。

希望本文对你有所帮助!!!

63F21AE8.gif

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

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

相关文章

成本决定未来——AIGC 下半场,高成本阻碍发展,我们该怎么办?

你好&#xff0c;我是三桥君 你最近有没有觉得工作中用到的那些 AI 工具好像越来越便宜了呢&#xff1f;这可不是偶然哦。 今天&#xff0c;三桥君就来聊聊为啥 AIGC 的下半场成本这么重要&#xff1f; 你想想看&#xff0c;咱平时工作已经够累了&#xff0c;要是再加上用那些贵…

算法Day-2

27. 移除元素 给你一个数组 nums 和一个值 val&#xff0c;你需要 原地 移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。 假设 nums 中不等于 val 的元素数量为 k&#xff0c;要通过此题&#xff0c;您需要执行以下操作&am…

zotero下载、安装、翻译和显示无法安装插件“%S”的解决办法

文章目录 zotero下载和安装和翻译还有插件英文翻译软件遇到的问题 zotero下载和安装和翻译还有插件 Zotero从入门到精通第一期–如何省心省力翻译科研文献&#xff0c;这里面主要是使用小牛翻译的过程&#xff0c;输入产品密匙需要加入个人的账号密码进行sign in 英文翻译软件…

动态规划17:123. 买卖股票的最佳时机 III

动态规划解题步骤&#xff1a; 1.确定状态表示&#xff1a;dp[i]是什么 2.确定状态转移方程&#xff1a;dp[i]等于什么 3.初始化&#xff1a;确保状态转移方程不越界 4.确定填表顺序&#xff1a;根据状态转移方程即可确定填表顺序 5.确定返回值 题目链接&#xff1a;123.…

递归神经网络(RNN)简介

递归神经网络简介 在本文中,我们将介绍神经网络的一种新的变体,即递归神经网络,也称为 (RNN),当数据是连续的时,如时间序列数据和文本数据,它比简单的神经网络效果更好。 什么是递归神经网络 (RNN)? 循环神经网络 (RNN) 是一种神经网络,其中上一步的输出作为当前…

Quarto ppt模板制作与Rstudio git连接

本篇记录下当前ppt演示中比较流行的quarto document使用情况以及Rstudio与git相连接的一些实操。 1 Quarto ppt模板制作 1.1 Quarto简介&#xff08;来自Kimi&#xff09; Quarto 是一个由 RStudio 的母公司 Posit 团队开发的开源科学和技术出版系统&#xff0c;它建立在 Pan…

计算机网络——传输层服务

传输层会给段加上目标ip和目标端口号 应用层去识别报文的开始和结束

upload-labs靶场Pass-01

upload-labs靶场Pass-01 分析 查看提示&#xff0c;提示如下 查看源码 function checkFile() {var file document.getElementsByName(upload_file)[0].value;if (file null || file "") {alert("请选择要上传的文件!");return false;}//定义允许上传…

Java-类与对象-下篇

关于类与对象&#xff0c;内容较多&#xff0c;我们分为两篇进行讲解&#xff1a; &#x1f4da; Java-类与对象-上篇&#xff1a;————<传送门:Java-类与对象-上篇-CSDN博客> &#x1f4d5; 面向对象的概念 &#x1f4d5; 类的定义格式 &#x1f4d5; 类的使用 …

ubuntu 安装nginx

sudo apt-get update sudo apt-get install nginx sudo nginx -vsudo systemctl status nginx sudo systemctl start nginx sudo systemctl stop nginx sudo systemctl restart nginx#浏览器输入&#xff1a;http://192.168.31.181/#查看文件结构 cd /etc/nginx sudo cp nginx.…

Java 类和对象详解(下)

个人主页&#xff1a;鲤鱼王打挺-CSDN博客 目录 &#x1f497;前言&#xff1a; &#x1f4af;一.static关键字 1. 为什么要使用static 2. static 修饰成员变量&#xff1a; 3. static 修饰成员方法&#xff1a; ​编辑 4. 静态代码块 5.静态导入包 &#x1f4af;二.…

Wi-Fi安全性入门(基于ESP-IDF-v4.4)

主要参考资料&#xff1a; Wi-Fi 安全性: https://docs.espressif.com/projects/esp-idf/zh_CN/release-v4.4/esp32/api-guides/wifi-security.html 目录 1.ESP32 Wi-Fi 安全功能1.1 受保护的管理帧 (PMF)1.2 第三代 Wi-Fi 访问保护 (WPA3-Personal) 1.ESP32 Wi-Fi 安全功能 支…

linux系统之jar启动脚本

编辑linux启动脚本 执行 vi run_blog 按i 进入编辑&#xff0c;复制以下代码&#xff0c;并根据当前环境修改三个参数。以下是详细完整脚本代码&#xff1a; #!/bin/bash# 配置部分 JAR_PATH"/path/to/your/app.jar" # 替换为你的 JAR 文件的实际路径 L…

Gin框架操作指南07:路由与中间件

官方文档地址&#xff08;中文&#xff09;&#xff1a;https://gin-gonic.com/zh-cn/docs/ 注&#xff1a;本教程采用工作区机制&#xff0c;所以一个项目下载了Gin框架&#xff0c;其余项目就无需重复下载&#xff0c;想了解的读者可阅读第一节&#xff1a;Gin操作指南&#…

【JVM】内存模型

文章目录 内存模型的基本概念案例 程序计数器栈Java虚拟机栈局部变量表栈帧中局部变量表的实际状态栈帧中存放的数据有哪些 操作数栈帧数据 本地方法栈 堆堆空间是如何进行管理的? 方法区静态变量存储 直接内存直接内存的作用 内存模型的基本概念 在前面的学习中,我们知道了字…

Java 8 Stream API:从基础到高级,掌握流处理的艺术

一、Stream&#xff08;流&#xff09;基本介绍 Java 8 API 添加了一个新的抽象称为Stream&#xff08;流&#xff09;&#xff0c;可以让你以一种声明的方式处理数据&#xff0c;这种风格将要处理的元素集合看做一种流&#xff0c;元素流在管道中传输&#xff0c;并在管道中间…

云黑系统全解无后门 +搭建教程

这套系统呢是玖逸之前南逸写的一套云黑系统&#xff0c;功能带有卡密生成和添加黑名单等&#xff0c;源码放在我的网盘里已经两年之久&#xff0c;由于玖逸现在已经跑路了所以现在发出来分享给大家&#xff0c;需要的可以自己拿去而开&#xff0c;反正功能也不是很多具体的自己…

电脑视频剪辑大比拼,谁更胜一筹?

随着短视频的火爆&#xff0c;越来越多的人开始尝试自己动手制作视频&#xff0c;无论是记录生活点滴还是创作个性短片&#xff0c;一款好用的视频剪辑软件是必不可少的。今天&#xff0c;我们就从短视频运营的角度&#xff0c;来聊聊几款热门的电脑视频剪辑软件&#xff0c;看…

docker配置加速器

阿里云 控制台》容器镜像服务》镜像工具》镜像加速器 复制地址&#xff1a;https://ywtoq7bz.mirror.aliyuncs.com 到&#xff1a;etc/docker下&#xff1a;vi daemon.json 格式&#xff1a; { "registry-mirrors": ["加速器地址"] } 注&#xff1…

JavaScript:闭包、防抖与节流

一&#xff0c;闭包 1&#xff0c;什么是闭包 闭包是指一个函数和其周围的词法环境(lexical environment)的组合。 换句话说&#xff0c;闭包允许一个函数访问并操作函数外部的变量。 闭包的核心特性: 函数内部可以访问外部函数的变量即使外部函数已经返回&#xff0c;内部…