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

IOS safari 播放 mp4 遇到的坎儿

起因

事情的起因是调试 IOS 手机下播放服务器接口返回的 mp4 文件流失败。对于没调试过移动端和 Safari 的我来说着实费了些功夫,网上和AI也没有讲明白。好在最终大概理清楚了,在这里整理出来供有缘人参考。

问题

因为直接用 IOS 手机的浏览器打开页面去播放也能复现,所以问题出现在 IOS上的 Safari 浏览器上。

问题主要分两类:

  • 一、视频能播放,但是不主动设置 poster,就不显示默认的 poster
  • 二、视频不能播放

首先文件是没有问题的,是主流浏览器都支持的视频编码格式为 H.264(AVC).mp4 文件,由文件格式导致的播放失败问题,网上很多,这里就不赘述了。

问题复现和解决

因为其他浏览器可以正常播放,所以用来对比差异时,我直接在PC端(Edge)查看信息,而 Safari 是在手机上访问项目页面,以及局域网下访问本地运行的 web 服务页面。

用到的工具如下:

  • Live Server 用来运行本地 web 页面
  • node + nodemon + Express 用来运行文件服务,模拟后端接口,nodemon 方便实时同步运行修改内容
  • video.js 播放视频的插件,直接用 CDN。
  • vConsole 移动端查看控制台,直接用 CDN

编写简单 demo

排查问题的时候我是一点一点将实现方式还原到最原始的方式:

  1. 后端接口 + video.js
  2. 后端接口 + video标签
  3. 本地运行的接口 + video标签
  4. 本地文件 + video标签

所以这里再梳理,就可以反向从最简单的方式排查,在本地写个用本地视频文件 + video标签的demo,为了后续排查方便,将事件日志也打印出来。

视频资源我用的 https://vjs.zencdn.net/v/oceans.mp4 下载到本地,不过它的开头是黑屏淡入的,为了方便区分 “不显示poster”,我将开头的几秒黑屏裁剪掉了。
也可以用安卓手机录制一个,默认就是支持播放的 mp4 文件。(PS:别用QQ录屏,编码不对)

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script><script>var vConsole = new window.VConsole()</script><style>#video {width: 480px;height: 25vh;max-width: 100%;/* 背景色用于在视频不显示poster时查看占位 */background: pink;border: 4px solid pink;}</style></head><body><div class="video-box"><video id="video" src="oceans.mp4" controls preload="auto" playsinline="true" muted></video></div><div id="log"><h2>日志:方便手机上的 Safari 查看</h2><div class="log__content"></div></div><script>// 打印日志function log(msg) {// 页面上打印日志const logContentEl = document.querySelector('.log__content')const pEl = document.createElement('p')pEl.textContent = msglogContentEl.appendChild(pEl)// 控制台打印日志console.log(msg)}const video = document.getElementById('video')// 主要关心的事件const eventNames = [{ name: 'abort', desc: '当音频/视频的加载已放弃时触发' },{ name: 'canplay', desc: '当浏览器可以开始播放音频/视频时触发' },{ name: 'canplaythrough', desc: '当浏览器预计能够在不因缓冲而停顿的情况下持续播放指定的音频/视频时触发' },{ name: 'durationchange', desc: '当音频/视频的时长已更改时触发' },{ name: 'error', desc: '当在音频/视频加载期间发生错误时触发' },{ name: 'loadeddata', desc: '当浏览器已加载音频/视频的当前帧时触发' },{ name: 'loadedmetadata', desc: '当浏览器已加载音频/视频的元数据时触发' },{ name: 'loadstart', desc: '当浏览器开始查找音频/视频时触发' },{ name: 'play', desc: '当音频/视频已开始或不再暂停时触发' },{ name: 'playing', desc: '当音频/视频在因缓冲而暂停或停止后已就绪时触发' },{ name: 'progress', desc: '当浏览器正在下载音频/视频时触发' },{ name: 'timeupdate', desc: '当音频/视频的播放位置发生改变时触发' },{ name: 'waiting', desc: '当视频由于需要缓冲下一帧而停止,等待时触发' }]// 注册video事件监听器eventNames.forEach(v => {video.addEventListener(v.name, () => {// 打印日志log(`【readyState: ${video.readyState}${v.name}: ${v.desc}`)})})</script></body>
</html>

preload 默认是 metadata,和 auto 的日志结果一样。为了排除一些可能性,我将其设置了 auto
playsinline="true" 防止 IOS 播放视频时自动打开全屏。

问题1 不显示预览图

demo 页面加载后发现,Safari 浏览器没有显示视频的预览图:

在这里插入图片描述
Edge 显示了预览图:

在这里插入图片描述
通过日志发现,Safari 加载完元数据后(loadedmetadata)就不会继续加载了。

我们知道,视频的预览图就是 video 标签的 poster 属性,当 poster 有值时就会显示指定的图片,当 poster 没有值时,浏览器就会自动处理,而不同浏览器的处理方式也不一样,可能会有这几种情况:

  1. 如果配置了预加载,显示加载后的第一帧,或第二、三桢。
  2. 什么都不显示,或显示默认的播放器背景样式

从日志可以看到,Safari 只加载了元数据,并没有加载画面。查看官方文档,得到了解答:

在这里插入图片描述
再看 poster 的说明:
在这里插入图片描述
既然如此,那只能提供一个 poster 来解决了。

问题2 视频不能播放

问题复现

播放 demo 示例里的视频,是可以正常播放的。

将播放方式改成 video.js + 本地文件,播放是正常的。代码如下:

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1.0" /><link href="https://unpkg.com/video.js@7.10.2/dist/video-js.min.css" rel="stylesheet" /><script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script><script>var vConsole = new window.VConsole()</script><style>#video {width: 480px;max-width: 100%;height: 25vh;/* 背景色用于在视频不显示poster时查看占位 */background: pink;border: 4px solid pink;}</style></head><body><video id="video" class="video-js" controls preload="auto" playsinline="true" muted><source src="oceans.mp4" type="video/mp4" /></video><div id="log"><h3>日志:方便手机上的 Safari 查看</h3><div class="log__content"></div></div><script src="https://unpkg.com/video.js@7.10.2/dist/video.min.js"></script><script>// 打印日志function log(msg) {// 页面上打印日志const logContentEl = document.querySelector('.log__content')const pEl = document.createElement('p')pEl.textContent = msglogContentEl.appendChild(pEl)// 控制台打印日志console.log(msg)}// 主要关心的 videojs 事件const eventNames = [{ name: 'abort', desc: '当音频/视频的加载已放弃时触发' },{ name: 'canplay', desc: '当浏览器可以开始播放音频/视频时触发' },{ name: 'canplaythrough', desc: '当浏览器预计能够在不因缓冲而停顿的情况下持续播放指定的音频/视频时触发' },{ name: 'durationchange', desc: '当音频/视频的时长已更改时触发' },{ name: 'error', desc: '当在音频/视频加载期间发生错误时触发' },{ name: 'loadeddata', desc: '当浏览器已加载音频/视频的当前帧时触发' },{ name: 'loadedmetadata', desc: '当浏览器已加载音频/视频的元数据时触发' },{ name: 'loadstart', desc: '当浏览器开始查找音频/视频时触发' },{ name: 'play', desc: '当音频/视频已开始或不再暂停时触发' },{ name: 'playing', desc: '当音频/视频在因缓冲而暂停或停止后已就绪时触发' },{ name: 'progress', desc: '当浏览器正在下载音频/视频时触发' },{ name: 'timeupdate', desc: '当音频/视频的播放位置发生改变时触发' },{ name: 'waiting', desc: '当视频由于需要缓冲下一帧而停止,等待时触发' }]const player = videojs('video')player.ready(() => {// 注册video事件监听器eventNames.forEach(v => {player.on(v.name, () => {// 打印日志log(`【readyState: ${player.readyState()}${v.name}: ${v.desc}`)})})})</script></body>
</html>

然后换成后端接口地址,Edge 可以播放,但是 Safari 就失败了。

video 标签方式:
在这里插入图片描述
video.js 方式:报错 The media could not be loaded, either because the server or network failed or because the format is not supported.
在这里插入图片描述

我肯定不是服务器网络故障,也不是文件格式不支持,所以原因只能是接口了。

本地模拟接口

为了不麻烦后端同事,我只能在本地搭建一个服务器,模拟项目接口。

搭建服务

创建 app.js 文件

const express = require('express')
const fs = require('fs')
const path = require('path')const app = express()app.get('/', (req, res) => {res.send('Stupid IOS')
})app.listen(3000, () => {const ip = 192.169.3.7 // 我的局域网ipconsole.log(`server is running on http://${ip}:3000`)
})
# 安装依赖
npm i express
# 已安装 nodemon,所以直接使用
nodemon app.js

访问 http://192.169.3.7:3000/。把 oceans.mp4 视频文件放到 app.js 同目录下,后面就编写接口就行。

方式1 使用 express 封装好的方法返回文件流
// 方式1: 使用封装好的方法返回文件流
app.get('/type1/:file', (req, res) => {const fileName = req.params.fileconst filePath = path.join(__dirname, fileName)res.sendFile(filePath)
})

文件地址:http://192.169.3.7:3000/type1/oceans.mp4

video标签和 video.js 播放都正常。

方式2 手动读取全部文件流并返回

同样是接口,本地模拟的正常,项目接口不能播放,那就继续更细致的模拟,手动读取文件流,设置相同的响应头。

查看项目接口的响应头,排除一些范围,只模拟有可能影响的:

// 方式2: 手动读取全部文件流并返回
app.get('/type2/:file', (req, res) => {const fileName = req.params.fileconst filePath = path.join(__dirname, fileName)// 打印请求头console.log(req.headers)fs.stat(filePath, (err, stats) => {if (err) {return res.status(404).send('file not found')}// 设置响应头res.set({'Accept-Ranges': 'bytes', // 支持 Range 请求'Cache-Control': 'no-cache, no-store', // 不缓存'Content-Type': 'video/mp4;charset=UTF-8','Content-Range': `bytes 0-${stats.size - 1}/${stats.size}`,'Content-Length': stats.size,'Content-Disposition': 'attachment;filename="oceans.mp4"',ETag: `"${stats.ino.toString()}-${stats.size.toString()}-${Date.now().toString()}"`,'Last-Modified': stats.mtime.toUTCString(),'Pragma': 'no-cache'})const stream = fs.createReadStream(filePath)stream.pipe(res)})
})

文件地址:http://192.169.3.7:3000/type2/oceans.mp4

问题依旧存在,于是查看官方文档,找到一段说明:
在这里插入图片描述

Range 请求就是范围请求或分块传输,客户端通过请求头 Range 指定当前请求想要获取的数据子节范围,服务器根据这个范围,读取文件流,并将读取的内容返回给客户端。
通常,服务器会返回 206 状态码,表示范围请求的响应结果。并且需要在响应头中包含 Content-Range 字段,指明实际返回的数据范围,以及整个资源的总大小。

可是我加了支持 range 请求的响应头啊:'Accept-Ranges': 'bytes'

继续看文档,下面介绍了如何确认服务器是否支持 range 请求:
在这里插入图片描述
大概意思就是主动发送一个指定范围 100 bytes 的请求,看返回的数据是100 bytes,那就是支持,如果返回了整个文件,那就是不支持。

查看之前服务器中打印的请求头,Range 请求头的值:

  • Edge 是 0-,表示获取整个资源
  • Safari 是 0-1:表示获取位置01 的子节的资源,注意可不是从开头到第1个子节,这个范围的请求数是2个子节。

因为我每次都返回的完整的文件流,没有按照 Safari 的要求范围处理,所以属于不支持。

看来仅仅配置响应头是不行的,还要正确处理请求头中的指定范围。

方式3 手动读取指定范围的文件流并返回-分块传输

原来 Safari 会先发送一个获取范围为 bytes=0-1 的请求,以测试服务器是否支持 range 请求。

于是我手动改了下响应头,去掉那些没有影响的,还是返回整个文件流:

    // 设置响应头res.set({'Accept-Ranges': 'bytes', // 支持 Range 请求'Cache-Control': 'no-cache, no-store', // 不缓存'Content-Type': 'video/mp4;charset=UTF-8','Content-Range': `bytes 0-1/${stats.size}`,'Content-Length': stats.size,})

看来仅仅伪造响应头还是不行,还要返回正确大小的文件流,那就编写分块传输的接口:

// 方式3: 手动读取指定范围的文件流并返回-分块传输
app.get('/type3/:file', (req, res) => {const fileName = req.params.fileconst filePath = path.join(__dirname, fileName)// 查看请求头的范围console.log(req.headers.range)fs.stat(filePath, (err, stats) => {if (err) {return res.status(404).send('file not found')}// 设置响应头res.set({'Accept-Ranges': 'bytes', // 支持 Range 请求'Cache-Control': 'no-cache, no-store', // 不缓存'Content-Type': 'video/mp4'})const range = req.headers.rangelet partslet start = 0let end = stats.size - 1if (range) {parts = range.replace(/bytes=/, '').split('-')start = parseInt(parts[0], 10)end = parts[1] ? parseInt(parts[1], 10) : stats.size - 1}if (start >= stats.size || end > stats.size) {// 如果Range请求超出文件大小,返回416状态码return res.status(416).end()}if (start >= 0 && end >= 0) {// 处理 Range 请求res.set({'Content-Range': `bytes ${start}-${end}/${stats.size}`,'Content-Length': end - start + 1})// 部分内容状态码,返回200浏览器也能正常处理res.status(206)}const stream = fs.createReadStream(filePath, { start, end })stream.pipe(res)})
})

终于视频可以正常播放了,video.js 也可以播放。

Safari 的校验还挺严格,如果服务器正确处理了这个2子节的请求,Safari 就会开始正式发送正常范围的请求。

服务器的日志可以体现出来:

bytes=0-1
bytes=0-51883958
bytes=196608-51883958
bytes=458752-51883958

最终查看了后端代码,果然接口没有处理 range 请求,在修改逻辑后,功能终于正常。

继续伪造 range 接口

为了搞清楚 Safari 的校验到底有多严格,我再次尝试模拟了一下第一次请求的响应:

	const range = req.headers.rangelet partslet start = 0let end = stats.size - 1// 增加逻辑 start-----------------------------------------if (range === 'bytes=0-1') {// 设置响应头res.set({'Accept-Ranges': 'bytes', // 支持 Range 请求'Cache-Control': 'no-cache, no-store', // 不缓存'Content-Type': 'video/mp4;charset=UTF-8','Content-Range': `bytes 3-3/${stats.size}`, // 随便写个范围'Content-Length': 2})const stream = fs.createReadStream(filePath, { start:100, end:200 }) // 随便获取个范围,但不能少于2stream.pipe(res)return res.status(206) // 随便返回个状态码}// 增加逻辑 end-----------------------------------------if (range) {parts = range.replace(/bytes=/, '').split('-')start = parseInt(parts[0], 10)end = parts[1] ? parseInt(parts[1], 10) : stats.size - 1}

再次提醒,bytes=0-1 表示请求的位置是01的子节,是2个子节,而不是 1-0=1 的子节数量。

不断测试下,发现只要满足这几个要求,Safari 就认为接口支持 range 请求:

  1. Content-Length 要正确。
  2. Content-Range 的范围要合理。
  3. 返回了不少于 Range 请求头要求大小的文件流数据。

而下面这几点,不影响 Safari 的校验结果:

  1. Content-Range 的范围和 Range 的指定范围不一样
  2. 读取的数据范围和 Range 的指定范围不一样
  3. 返回任意状态码

总结

  1. IOS 上的 Safari 不支持 video 预加载(preload),浏览器不会自动提取帧画面作为默认的 poster 预览图
  2. Safari 上使用 video 播放视频,必须支持并正确处理 Range 范围请求,浏览器会先发送 bytes=0-1 范围的请求来测试服务器是否支持 Range 请求,如果校验成功,就会继续发送正常范围的 Range 请求。否则不再请求资源。

相关文章:

IOS safari 播放 mp4 遇到的坎儿

起因 事情的起因是调试 IOS 手机下播放服务器接口返回的 mp4 文件流失败。对于没调试过移动端和 Safari 的我来说着实费了些功夫&#xff0c;网上和AI也没有讲明白。好在最终大概理清楚了&#xff0c;在这里整理出来供有缘人参考。 问题 因为直接用 IOS 手机的浏览器打开页面…...

plsql :用户system通过sysdba连接数据库--报错ora-01031

一、winR cmd通过命令窗口登录sys用户 sql sys/[password]//localhost:1521/[service_name] as sysdba二、输入用户名:sys as sysdba 三、输入密码:自己设的 四、执行grant sysdba to system; 再去PL/SQL连接就可以了...

LabVIEW条件配置对话框

条件配置对话框&#xff08;Configure Condition Dialog Box&#xff09; 要求&#xff1a;Base Development System 当右键单击**条件禁用结构&#xff08;Conditional Disable Structure&#xff09;**并选择以下选项时&#xff0c;会显示此对话框&#xff1a; Add Subdiagr…...

PyAudio库基本知识详解——为自制PCM音频播放器做准备

前言 结合前段时间我们做的音频编解码器&#xff0c;这样我们就可以将获取到的ADPCM数据&#xff0c;转换成PCM数据&#xff0c;然后播放出来&#xff0c;得到一个完整的音频数据&#xff0c;因此&#xff0c;接下来几篇文章中&#xff0c;我们想做一个播放PCM格式的音频播放器…...

Git如何添加子仓库

背景 项目中经常使用别人维护的模块&#xff0c;在git中使用子模块的功能能够大大提高开发效率。 使用子模块后&#xff0c;不必负责子模块的维护&#xff0c;只需要在必要的时候同步更新子模块即可。 本文主要讲解子模块相关的基础命令&#xff0c;详细使用请参考main page…...

001__VMware软件和ubuntu系统安装(镜像)

[ 基本难度系数 ]:★☆☆☆☆ 一、Vmware软件和Ubuntu系统说明&#xff1a; a、Vmware软件的说明&#xff1a; 官网&#xff1a; 历史版本&#xff1a; 如何下载&#xff1f; b、Ubuntu系统的说明&#xff1a; 4、linux系统的其他版本&#xff1a;红旗(redhat)、dibian、cent…...

在国产电脑上运行PDFSAM软件使用pdf分割合并交替混合处理pdf文档

软件下载地址: https://sourceforge.net/projects/pdfsam/files/ 需要注意事项&#xff0c;系统需要java环境&#xff0c;确认系统有java环境&#xff0c;根据软件版本需求安装对应的java运行环境。 下载pdfsam-4.3.4-linux.tar.gz安装包&#xff0c;解压&#xff0c;将runt…...

STM32完全学习——FATFS0.15移植SD卡

一、下载FATFS源码 大家都知道使用CubMAX可以很快的将&#xff0c;FATFS文件管理系统移植到单片机上&#xff0c;但是别的芯片没有这么好用的工具&#xff0c;就需要自己从官网下载源码进行移植。我们首先解决SD卡的驱动问题&#xff0c;然后再移植FATFS文件管理系统。 二、SD…...

Linux -- 生产消费模型之环形队列、信号量

目录 前言 环形队列 如何用环形队列实现生产消费模型&#xff1f; 信号量 sem_t sem_init&#xff08;初始化信号量&#xff09; sem_destroy&#xff08;销毁信号量&#xff09; 什么是PV操作&#xff1f; sem_wait&#xff08;P操作&#xff0c;减少信号量&#xff…...

Ashy的考研游记

文章目录 摘要12.1112.2012.21 DAY1&#xff08;政治/英语&#xff09;政治英语 12.22 DAY2&#xff08;数学/专业课&#xff09;数学专业课 结束估分 摘要 在24年的12月里&#xff0c;Ashy完成了他的考研冲刺&#xff0c;顺利的结束了他本年度的考研之旅。 在十二月里&#…...

MySQL线上事故:使用`WHERE`条件`!=xxx`无法查询到NULL数据

前言 在一次 MySQL 的线上查询操作中&#xff0c;因为 ! 的特性导致未能正确查询到为 NULL 的数据&#xff0c;险些引发严重后果。本文将详细解析 NULL 在 SQL 中的行为&#xff0c;如何避免类似问题&#xff0c;并提供实际操作建议。 1. 为什么NULL会查询不到&#xff1f; 在…...

vue3学习笔记(11)-组件通信

1.props 父传子 子传夫 父传子 接收用defineProps([]) 空字符串也是假 2.自定义事件 $event:事件对象 ref定义的数据在模板里面引用的时候可以不用.value 3.子传父 宏函数 触发事件 声明事件 defineEmits() 挂载之后3s钟触发 4.命名 肉串命名 5.任意组件通信 mitt pubs…...

【PDF物流单据提取明细】批量PDF提取多个区域内容导出表格或用区域内容对文件改名,批量提取PDF物流单据单号及明细导出表格并改名的技术难点及小节

相关阅读及下载&#xff1a; PDF电子物流单据&#xff1a; 批量PDF提取多个区域局部内容重命名PDF或者将PDF多个局部内容导出表格&#xff0c;具体使用步骤教程和实际应用场景的说明演示https://mp.weixin.qq.com/s/uCvqHAzKglfr40YPO_SyNg?token720634989&langzh_CN扫描…...

张量与数据类型

Pytorch最基本的操作对象——张量&#xff08;tensor&#xff09;&#xff0c;张量是Pytorch中重要的数据结构&#xff0c;可认为是一个高维数组。一般的&#xff0c;标量&#xff08;scalar&#xff09;是只有大小没有方向的量&#xff0c;如1、2、3等&#xff1b;向量&#x…...

torchvision.utils.make_grid 解释下

torchvision.utils.make_grid 是 PyTorch 中 torchvision 库提供的一个实用函数,用于将多个图像拼接成一个网格,方便进行可视化。 主要功能 make_grid 将一批图片组织成一个网格形式,输出一个单一的张量,便于使用可视化工具(如 Matplotlib)查看图像。 参数解释 torchvi…...

Android原生Widget使用步骤

需要创建三个XML文件以及一个Class文件 三个XML文件分别是 Widget布局文件 <?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_p…...

实验八 指针2

7-1 利用指针返回多个函数值 分数 30 全屏浏览 切换布局 作者 陈晓梅 单位 广东外语外贸大学 读入n个整数&#xff0c;调用max_min()函数求这n个数中的最大值和最小值。 输入格式: 输入有两行&#xff1a; 第一行是n值&#xff1b; 第二行是n个数。 输出格式: 输出最大…...

1 数据库(下):多表设计 、多表查询 + SQL中的with查询语法(MySQL8.0以后版本才支持这种新语法)+ 数据库优化(索引优化)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、多表设计1 多表设计-概述2 三种多表关系一对多&#xff08;多对一&#xff09;&#xff08;1&#xff09;无外键约束&#xff08;逻辑外键&#xff09;&…...

什么是.net framework,什么是.net core,什么是.net5~8,版本对应关系

我不知道有多少人和我一样&#xff0c;没学习过.netCore&#xff0c;想要学习&#xff0c;但是版本号太多就蒙了&#xff0c;不知道学什么了&#xff0c;这里解释下各个版本的关系 我们一般开始学习微软的时候&#xff0c;都是开始学习的.netframework&#xff0c;常用的就是4…...

vulhub-wordpress靶场

一.主题上传漏洞 来到靶场点击主题选择add new 这里有一个上传主题的地方 我们可以去网上找到wordpress主题下载一个 wordpress模板 网页设计模板 免费 免费下载 - 爱给网 下载完成后对我们有用的东西只有这一个目录&#xff0c;把它拖出来 点开moban目录后&#xff0c;创建…...

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…...

Docker 离线安装指南

参考文章 1、确认操作系统类型及内核版本 Docker依赖于Linux内核的一些特性&#xff0c;不同版本的Docker对内核版本有不同要求。例如&#xff0c;Docker 17.06及之后的版本通常需要Linux内核3.10及以上版本&#xff0c;Docker17.09及更高版本对应Linux内核4.9.x及更高版本。…...

Vue记事本应用实现教程

文章目录 1. 项目介绍2. 开发环境准备3. 设计应用界面4. 创建Vue实例和数据模型5. 实现记事本功能5.1 添加新记事项5.2 删除记事项5.3 清空所有记事 6. 添加样式7. 功能扩展&#xff1a;显示创建时间8. 功能扩展&#xff1a;记事项搜索9. 完整代码10. Vue知识点解析10.1 数据绑…...

最新SpringBoot+SpringCloud+Nacos微服务框架分享

文章目录 前言一、服务规划二、架构核心1.cloud的pom2.gateway的异常handler3.gateway的filter4、admin的pom5、admin的登录核心 三、code-helper分享总结 前言 最近有个活蛮赶的&#xff0c;根据Excel列的需求预估的工时直接打骨折&#xff0c;不要问我为什么&#xff0c;主要…...

C# 类和继承(抽象类)

抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

2025盘古石杯决赛【手机取证】

前言 第三届盘古石杯国际电子数据取证大赛决赛 最后一题没有解出来&#xff0c;实在找不到&#xff0c;希望有大佬教一下我。 还有就会议时间&#xff0c;我感觉不是图片时间&#xff0c;因为在电脑看到是其他时间用老会议系统开的会。 手机取证 1、分析鸿蒙手机检材&#x…...

WEB3全栈开发——面试专业技能点P2智能合约开发(Solidity)

一、Solidity合约开发 下面是 Solidity 合约开发 的概念、代码示例及讲解&#xff0c;适合用作学习或写简历项目背景说明。 &#x1f9e0; 一、概念简介&#xff1a;Solidity 合约开发 Solidity 是一种专门为 以太坊&#xff08;Ethereum&#xff09;平台编写智能合约的高级编…...

SpringCloudGateway 自定义局部过滤器

场景&#xff1a; 将所有请求转化为同一路径请求&#xff08;方便穿网配置&#xff09;在请求头内标识原来路径&#xff0c;然后在将请求分发给不同服务 AllToOneGatewayFilterFactory import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; impor…...

用docker来安装部署freeswitch记录

今天刚才测试一个callcenter的项目&#xff0c;所以尝试安装freeswitch 1、使用轩辕镜像 - 中国开发者首选的专业 Docker 镜像加速服务平台 编辑下面/etc/docker/daemon.json文件为 {"registry-mirrors": ["https://docker.xuanyuan.me"] }同时可以进入轩…...

【学习笔记】深入理解Java虚拟机学习笔记——第4章 虚拟机性能监控,故障处理工具

第2章 虚拟机性能监控&#xff0c;故障处理工具 4.1 概述 略 4.2 基础故障处理工具 4.2.1 jps:虚拟机进程状况工具 命令&#xff1a;jps [options] [hostid] 功能&#xff1a;本地虚拟机进程显示进程ID&#xff08;与ps相同&#xff09;&#xff0c;可同时显示主类&#x…...