java实现局域网内视频投屏播放(二)爬虫

代码链接

视频播放原理

大多视频网站使用的是m3u8,m3u8其实不是一个真正的视频文件,而是一个视频播放列表(playlist)。它是一种文本文件,里面记录了一系列的视频片段(segment)的网络地址。这些视频片段通常是ts格式的,也就是传输流(transport stream)格式。ts格式的视频片段可以很快地在网络上传输和播放,而不需要等待整个文件下载完毕。这样就可以实现流媒体(streaming media)的效果,也就是边下边播。

m3u8是苹果公司提出的一种流媒体协议,叫做HTTP Live Streaming(HLS)。HLS的目的是为了解决在不同网络环境下,如何提供更好的视频观看体验的问题。
HLS的原理是把一个完整的视频切分成很多小的视频片段,并且为每个片段提供不同的码率(bitrate)和分辨率(resolution)的选项。这样,当用户观看视频时,可以根据自己的网络状况和设备性能,自动或者手动地选择合适的视频片段进行播放。这样就可以避免卡顿、缓冲、画质模糊等问题,提高用户满意度。

爬虫实现原理

所以对于爬虫来说,需要获取m3u8文件,再根据m3u8里面记录的ts链接列表去下载ts文件(视频片段)。将这些ts保存到本地磁盘中。

视频解析器接口

public interface VideoResolver {/*** 是否支持解析** @param url 地址* @return boolean*/boolean support(String url);/*** 获取m3u8文件** @param url 地址* @return Result*/M3U8BO getM3U8(String url);/*** 获取ts地址列表** @param m3u8BO m3u8内容* @return Result*/TsListBO getTsList(M3U8BO m3u8BO);/*** 获取解密ts的方法** @param head   m3u8文件头* @param encKey 加密的key* @return UnaryOperator*/default UnaryOperator<byte[]> getDecodeTsFunction(String head, byte[] encKey) {return null;}
}

通用视频解析器抽象类

一般来说m3u8的链接保存在播放页面里面。可以通过查看源代码找到,只要写一个正则表达式就能取到链接,然后通过该链接获取m3u8内容和ts下载链接等内容,所以将这些公共的代码封装到一个一个抽象类中,并新增两个抽象方法,获取解析m3u8的正则模式和获取解析视频名的正则模式。

@Slf4j
public abstract class CommonVideoResolver implements VideoResolver {private static final Pattern tsContentPat = Pattern.compile("(#EXTINF.*?)\n(.*?\\.ts)");public static final Pattern encKeyPat = Pattern.compile("#EXT-X-KEY.*?URI=\"(.*?)\"[^\n]*");private static final Pattern m3u8ContentPat = Pattern.compile("([\\s\\S]*?)(#EXTINF[\\s\\S]*\\.ts)([\\s\\S]*)");/*** 获取解析m3u8的正则模式** @return Pattern*/protected abstract Pattern getM3U8UrlPat();/*** 获取解析视频名的正则模式** @return Pattern*/protected abstract Pattern getM3U8NamePat();@Overridepublic M3U8BO getM3U8(String url) {HttpRespBO respBO = HttpUtil.httpGet10(url);Assert.isTrue(respBO, "获取M3U8文件失败", () ->log.error("xcVideoService getM3U8 fail url:{}", url));String res = respBO.getUTF8Body();Matcher matcher = getM3U8UrlPat().matcher(res);Assert.isTrue(matcher.find(), "未获取到M3U8地址");String m3u8Url = matcher.group(1);HttpRespBO m3u8Resp = HttpUtil.httpGet10(m3u8Url);Assert.isTrue(m3u8Resp, "获取M3U8列表文件失败", () ->log.error("xcVideoService getM3U8 list fail m3u8Url:{},m3u8Resp:{}", m3u8Url, m3u8Resp));String m3u8Content = m3u8Resp.getUTF8Body();m3u8Content = HLSUtil.getMaxBandwidthM3U8(m3u8Url, m3u8Content);Matcher m3u8NameMatcher = getM3U8NamePat().matcher(res);String title = Optional.of(m3u8NameMatcher).filter(Matcher::find).map(m -> m.group(1)).orElse(null);String id = UUID.randomUUID().toString().replace("-", "");return new M3U8BO(id, title, m3u8Content, url, m3u8Url);}@Overridepublic TsListBO getTsList(M3U8BO m3u8BO) {String videoId = m3u8BO.getId();String m3u8Url = m3u8BO.getM3u8Url();String m3u8Content = m3u8BO.getContent();Matcher m3u8Matcher = m3u8ContentPat.matcher(m3u8Content);Assert.isTrue(m3u8Matcher.find(), "m3u8内容解析失败");String head = m3u8Matcher.group(1);StringBuilder newHead = new StringBuilder(head);TsEncBO tsEncBO = buildTsEnc(videoId, newHead, m3u8Url);List<TsBO> tsList = new ArrayList<>();String domain = NetUtil.resolveRootUrl(m3u8Url);Matcher tsMatcher = tsContentPat.matcher(m3u8Matcher.group(2));while (tsMatcher.find()) {String url = tsMatcher.group(2);tsList.add(new TsBO(composeUrl(domain, url), tsMatcher.group(1)));}TsListBO tsListBO = new TsListBO();tsListBO.setTsList(tsList);tsListBO.setTsEncBO(tsEncBO);tsListBO.setHead(newHead.toString());tsListBO.setEnd(m3u8Matcher.group(3));return tsListBO;}private String composeUrl(String domain, String url) {if (url.startsWith("http")) {return url;}return url.startsWith("/") ? domain + url : domain + "/" + url;}private TsEncBO buildTsEnc(String videoId, StringBuilder newHead, String m3u8Url) {TsEncBO tsEncBO = null;String head = newHead.toString();Matcher encKeyMatcher = encKeyPat.matcher(head);if (encKeyMatcher.find()) {String originEncKeyUrl = encKeyMatcher.group(1);String encKeyUrl;if (originEncKeyUrl.startsWith("http")) {encKeyUrl = originEncKeyUrl;} else if (originEncKeyUrl.startsWith("/")) {encKeyUrl = NetUtil.resolveRootUrl(m3u8Url) + originEncKeyUrl;} else {encKeyUrl = m3u8Url.substring(0, m3u8Url.lastIndexOf("/") + 1) + originEncKeyUrl;}HttpRespBO encKeyResp = HttpUtil.httpGet10(encKeyUrl);Assert.isTrue(encKeyResp, "获取ts文件密钥失败", () ->log.error("getEncKey fail encKeyUrl:{},encKeyResp:{}", encKeyUrl, encKeyResp));newHead.setLength(0);newHead.append(head.replace(originEncKeyUrl, "/video/enc/key/" + videoId));byte[] encKey = encKeyResp.getBody();tsEncBO = new TsEncBO();tsEncBO.setEncKey(encKey);tsEncBO.setEncKeyUrl(encKeyUrl);tsEncBO.setOriginEncKeyUrl(originEncKeyUrl);}return tsEncBO;}
}

如果想新增一个网站的解析,只需要继承这个抽象类,提供两个正则表达式即可。当然对于前后端分离的网站,就不能使用这个抽象类了,需要自己根据m3u8的接口新增一个公用的抽象类(等遇到了再实现),获取m3u8和ts的内容了(实现VideoResolver接口)

@Slf4j
@Service("ccVideoResolver")
public class CCVideoResolver extends CommonVideoResolver {private static final Pattern m3u8NamePat = Pattern.compile("<title>(.*?)</title>");private static final Pattern m3u8UrlPat = Pattern.compile("player_data=\\{.*?\"url\":\"(.*?)\"");@Overridepublic boolean support(String url) {return url != null && url.contains("www.nxyjjt.com");}@Overrideprotected Pattern getM3U8UrlPat() {return m3u8UrlPat;}@Overrideprotected Pattern getM3U8NamePat() {return m3u8NamePat;}
}

ts文件解密

有的ts文件是加密的,播放的时候需要根据m3u8上的加密方式和加密的key去解密ts文件,如m3u8文件中有一行为 #EXT-X-KEY:METHOD=AES-128,URI="key.key",IV=0x864267cc19f34ec1066e016e0da856ee。对于这种情况我们有两种处理方案

  • 将ts文件解密后再保存到本地,这种保存到本地的ts文件可以直接播放,将这些ts文件直接拼接成一个大的ts文件后,就是完整的视频。并且需要将要提供的m3u8文件中的 #EXT-X-KEY:METHOD=xxx 这一行删掉,代表这个视频没有进行过加密。
  • 不解密直接保存,这种ts文件不能直接播放。并将加密的key也保存下来,需要将要提供的m3u8文件中的 #EXT-X-KEY:METHOD=xxx 这一行URI="xxx“中的地址改为自己服务获取密钥的地址,让投屏设置在投屏时候根据这个链接获取密钥,在播放过程中解密ts文件

第一种方式下载相对慢一些但是播放很快,因为下载需要解密,播放无需解密。第二种方式则相反。注意这个慢是相对的,因为在局域网内投屏,最耗时的步骤已经解决,无论哪一种方式都不会卡

可以看到VideoResolver接口中提供了一个getDecodeTsFunction方法,返回一个解密方法UnaryOperator,默认是返回null即不解密,如果想解密的话,可以根据m3u8里的解密方式重写getDecodeTsFunction方法

@Slf4j
@Service("xcVideoResolver")
public class XCVideoResolver extends CommonVideoResolver {private static final Pattern m3u8UrlPat = Pattern.compile("\"url\":\"(.*?)\"");private static final Pattern m3u8NamePat = Pattern.compile("<title>(.*?)</title>");private static final Pattern encKeyPat = Pattern.compile("#EXT-X-KEY:METHOD=(.*?),.*?IV=(.*?)\n");@Overridepublic boolean support(String url) {return url != null && (url.contains("www.huidongxie.com") || url.contains("www.wszwz.net"));}@Overrideprotected Pattern getM3U8UrlPat() {return m3u8UrlPat;}@Overrideprotected Pattern getM3U8NamePat() {return m3u8NamePat;}@Overridepublic UnaryOperator<byte[]> getDecodeTsFunction(String head, byte[] encKey) {Matcher encKeyMatcher = encKeyPat.matcher(head);if (encKeyMatcher.find()) {String method = encKeyMatcher.group(1);return method.contains("aes") || method.contains("AES") ?encByte -> AESUtil.decode(encByte, encKey, "0000000000000000") : null;}return null;}
}

通用视频下载执行器

当获取完ts的下载列表后,就需要将ts下载下来并且保存到本地。整个过程如下

  1. 如果ts被加密但是没有解密方法,就将密钥文件保存到本地
  2. 将ts地址列表依次提交到线程池中执行下载任务
  3. ts下载完成后,如果有解密方法就执行解密,然后保存到本地,并且将下载ts的信息放到SynchronousQueue中
  4. 主线程创建一个文件info.txt,写入视频名、来源、进度,始终打开着该文件,然后从SynchronousQueue不断的获取下载完成的ts信息,并更新info.txt中的进度,直到100%
  5. 最后将本地m3u8文件(定义了如何从本地服务获取ts文件)保存到本地(video.base.path配置中执行的路径,可以直接修改application.yml或者执行jar包时候指定属性java -jar -Dvideo.base.path=D:/xx xxx.jar )

解密的视频:

​​​​​​​

不解密的视频:

@Slf4j
@Service("commonVideoActuator")
public class CommonVideoActuator implements VideoActuator {@Value("${video.base.path}")private String videoBasePath;@Resource(name = "downloadTSPool")private ExecutorService downloadTSPool;public static final Pattern encKeyPat = Pattern.compile("#EXT-X-KEY.*?URI=\"(.*?)\"[^\n]*");@Overridepublic Result<String> downloadAndSaveTS(String url, VideoResolver videoResolver) {try {M3U8BO m3u8BO = videoResolver.getM3U8(url);TsListBO tsListBO = videoResolver.getTsList(m3u8BO);List<TsBO> tsList = tsListBO.getTsList();Assert.isNotEmpty(tsList, "ts地址列表为空");String fileId = m3u8BO.getId();String basePath = videoBasePath + "/" + fileId;Assert.isTrue(FileUtil.deleteFolder(basePath), basePath + "删除失败");Files.createDirectories(Paths.get(basePath + "/ts"));String m3u8Content = m3u8BO.getContent();Path originM3U8Path = Paths.get(basePath + "/origin.m3u8");Files.write(originM3U8Path, m3u8Content.getBytes(), StandardOpenOption.CREATE_NEW);UnaryOperator<byte[]> decodeTsFunction = getDecodeTsFunction(basePath, tsListBO, videoResolver);SynchronousQueue<LocalTsBO> synchronousQueue = new SynchronousQueue<>();batchSubmitTsTask(basePath, tsList, decodeTsFunction, synchronousQueue);List<LocalTsBO> successTsList = getFutureAndSaveInfo(basePath, m3u8BO, tsList.size(), synchronousQueue);Path localM3U8Path = Paths.get(basePath + "/local.m3u8");String newM3U8Content = buildLocalM3U8Content(tsListBO, fileId, successTsList);Files.write(localM3U8Path, newM3U8Content.getBytes(), StandardOpenOption.CREATE_NEW);return Result.success(fileId);} catch (ViewException e) {throw e;} catch (Exception e) {log.error("downloadAndSaveTS fail url:{}", url, e);return Result.fail("下载保存视频发生错误");}}private UnaryOperator<byte[]> getDecodeTsFunction(String basePath, TsListBO tsListBO, VideoResolver videoResolver) throws IOException {TsEncBO tsEncBO = tsListBO.getTsEncBO();if (tsEncBO != null) {String head = tsListBO.getHead();byte[] encKey = tsEncBO.getEncKey();UnaryOperator<byte[]> decodeTsFunction = videoResolver.getDecodeTsFunction(head, encKey);if (decodeTsFunction != null) {tsListBO.setHead(head.replaceAll(encKeyPat.toString(), ""));return decodeTsFunction;} else {Path encKeyPath = Paths.get(basePath + "/enc.key");Files.write(encKeyPath, encKey, StandardOpenOption.CREATE_NEW);}}return UnaryOperator.identity();}private void batchSubmitTsTask(String basePath, List<TsBO> tsList, UnaryOperator<byte[]> decodeFun, SynchronousQueue<LocalTsBO> synchronousQueue) {IntStream.range(0, tsList.size()).forEach(i -> {TsBO tsBO = tsList.get(i);String localTsName = i + ".ts";LocalTsBO localTsBO = new LocalTsBO();localTsBO.setIndex(i);localTsBO.setTsUrl(tsBO.getUrl());localTsBO.setExtInf(tsBO.getExtInf());localTsBO.setLocalTsName(localTsName);localTsBO.setLocalTsPath(basePath + "/ts/" + localTsName);downloadTSPool.submit(() -> doDownloadAndSaveTS(localTsBO, decodeFun, synchronousQueue));});}private List<LocalTsBO> getFutureAndSaveInfo(String basePath, M3U8BO m3u8BO, int allSize, SynchronousQueue<LocalTsBO> synchronousQueue) throws IOException {try (RandomAccessFile rf = new RandomAccessFile(basePath + "/info.txt", "rw")) {String head = "文件名:" + m3u8BO.getName() + "\n";head += "来源:" + m3u8BO.getSourceUrl() + "\n";head += "进度:";rf.write(head.getBytes(StandardCharsets.UTF_8));int preRateByteNum = 0;StringBuilder failTsContent = new StringBuilder();List<LocalTsBO> successTsList = new ArrayList<>(allSize);for (int i = 0; i < allSize; i++) {LocalTsBO localTsBO = synchronousQueue.take();rf.seek(rf.getFilePointer() - preRateByteNum);String rate = String.format("%.2f", (i + 1) * 100.0 / allSize) + "%";byte[] rateByte = rate.getBytes(StandardCharsets.UTF_8);rf.write(rateByte);preRateByteNum = rateByte.length;HandlerUtil.branchHandler(localTsBO.isTaskSuccess(), () -> successTsList.add(localTsBO), () ->failTsContent.append(localTsBO.getLocalTsName()).append("->").append(localTsBO.getTsUrl()).append("\n"));}rf.write(("\n异常ts文件:\n" + failTsContent).getBytes(StandardCharsets.UTF_8));successTsList.sort(Comparator.comparing(LocalTsBO::getIndex));return successTsList;} catch (InterruptedException e) {Thread.currentThread().interrupt();log.error("getFutureAndSaveInfo fail m3u8BO:{}", m3u8BO, e);throw new IllegalStateException("线程中断,下载任务停止");}}private String buildLocalM3U8Content(TsListBO tsListBO, String fileId, List<LocalTsBO> successLocalTs) {StringBuilder newM3U8Content = new StringBuilder(tsListBO.getHead());successLocalTs.forEach(localTsBO -> {String extInf = localTsBO.getExtInf();String localTsName = localTsBO.getLocalTsName();newM3U8Content.append(extInf).append("\n").append("/video/ts/").append(fileId).append("/").append(localTsName).append("\n");});newM3U8Content.append(tsListBO.getEnd());return newM3U8Content.toString();}private void doDownloadAndSaveTS(LocalTsBO localTsBO, UnaryOperator<byte[]> decodeFunction, SynchronousQueue<LocalTsBO> synchronousQueue) {String tsUrl = localTsBO.getTsUrl();String localTsPath = localTsBO.getLocalTsPath();try {HttpRespBO respBO = HttpUtil.httpGet10(tsUrl);if (respBO == null) {log.error("downloadTS fail localTsBO:{}", localTsBO);return;}try (RandomAccessFile ts = new RandomAccessFile(localTsPath, "rw")) {ts.write(decodeFunction.apply(respBO.getBody()));localTsBO.setTaskSuccess(true);}} catch (IOException e) {log.error("save ts fail,localTsBO:{}", localTsBO, e);} finally {putSynchronousQueue(synchronousQueue, localTsBO);}}private void putSynchronousQueue(SynchronousQueue<LocalTsBO> synchronousQueue, LocalTsBO localTsBO) {try {synchronousQueue.put(localTsBO);} catch (InterruptedException e) {Thread.currentThread().interrupt();log.error("doDownloadAndSaveTS put synchronousQueue fail,localTsBO:{}", localTsBO, e);}}@Dataprivate static class LocalTsBO {/*** ts的位置*/private int index;/*** ts的地址*/private String tsUrl;/*** ts时长*/private String extInf;/*** 本地ts名称*/private String localTsName;/*** 本地ts路径*/private String localTsPath;/*** 任务是否成功*/private boolean taskSuccess;}
}

视频播放能力

最后提供本地视频的播放能力,也就是提供三个http接口

  1. 根据视频id获取本地m3u8文件
  2. 根据视频id和ts文件名获取本地ts字节数组(这个接口就是上面m3u8里面的ts链接)
  3. 根据视频id获取本地密钥数组(这个接口就是上面m3u8里面的密钥链接)

至此随便找一个能播放m3u8的软件就可以播放我们本地的视频了,如Safari浏览器、QuickTime Player等,直接在Safari浏览器或者播放器中输入m3u8文件的网址,就可以开始观看视频了

@Slf4j
@RestController
@RequestMapping("/video")
public class VideoController {@Autowiredprivate VideoService videoService;@GetMapping(value = "/m3u8/{videoId}", produces = "application/vnd.apple.mpegurl")public byte[] getM3U8(@PathVariable String videoId) {return returnFileTemplate(videoId + "/local.m3u8");}@GetMapping(value = "/ts/{videoId}/{tsName}", produces = "video/mp2t")public byte[] getTs(@PathVariable String videoId, @PathVariable String tsName) {return returnFileTemplate(videoId + "/ts/" + tsName);}@GetMapping(value = "/enc/key/{videoId}", produces = "application/octet-stream")public byte[] getEncKey(@PathVariable String videoId) {return returnFileTemplate(videoId + "/enc.key");}
}
    private byte[] returnFileTemplate(String relativePath) {Result<byte[]> result = videoService.getFileByte(relativePath);return Optional.of(result).filter(Result::isSuccess).map(Result::getData).orElseGet(() -> JSON.toJSONBytes(result));}@Overridepublic Result<byte[]> getFileByte(String relativePath) {String filePath = videoBasePath + "/" + relativePath;try (RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "r")) {byte[] buffer = new byte[(int) randomAccessFile.length()];randomAccessFile.read(buffer);return Result.success(buffer);} catch (Exception e) {log.error("getTs fail,filePath:{}", filePath, e);return Result.fail("获取文件失败");}}

本质上就是先从本地服务里获取m3u8文件,根据里面定义的ts链接去本地服务获取ts数据,如果加密了,再获取密钥进行解密

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

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

相关文章

原来定时发朋友圈设置这么简单?看完我也会了

目前微信作为最大的社交平台之一&#xff0c;吸引了众多使用者。你是否听过有些朋友感叹这么多微信号&#xff0c;需要每天手动发布朋友圈&#xff0c;任务很繁琐呢&#xff1f;是否希望可以事先设置好定时发送的功能&#xff0c;让朋友圈自动更新&#xff0c;省去手动发送的麻…

初探栈溢出(下)

0x04 漏洞利用 作为脚本小子&#xff0c;先跑一下写好了的exploit脚本。 打开HackSysEVDExploit.sln文件&#xff0c;直接在vs2019上编译即可。 将生成的HackSysEVDExploit.exe拷贝至win7&#xff0c;执行如下命令 直接可以获取system权限。 那么只跑一下脚本肯定不行&#…

世微 锂电池保护IC DW01 充电器检测过充保护SOT23-6

一、 描述 DW01A 是一个锂电池保护电路&#xff0c;为避免锂电池因过充电、过放电、电流过大导致电池寿命缩短或电池被损坏而设计的。它具有高精确度的电压检测与时间延迟电路。 二、 主要特点 工作电流低 过充检测 4.3V&#xff0c;过充释放 4.05V&#xff1b; 过放检测 2.4…

黑马头条--day01.环境搭建

一.前言 该项目学习自黑马程序员&#xff0c;由我整理如下&#xff0c;版权归黑马程序员所有 二.环境搭建 1.数据库 第一天&#xff0c;先创建如下库和表: sql文件如下: CREATE DATABASE IF NOT EXISTS leadnews_user DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_…

LeetCode(62)删除排序链表中的重复元素 II【链表】【中等】

目录 1.题目2.答案3.提交结果截图 链接&#xff1a; 删除排序链表中的重复元素 II 1.题目 给定一个已排序的链表的头 head &#xff0c; 删除原始链表中所有重复数字的节点&#xff0c;只留下不同的数字 。返回 已排序的链表 。 示例 1&#xff1a; 输入&#xff1a;head [1…

【JavaEE】锁的策略

作者主页&#xff1a;paper jie_博客 本文作者&#xff1a;大家好&#xff0c;我是paper jie&#xff0c;感谢你阅读本文&#xff0c;欢迎一建三连哦。 本文于《JavaEE》专栏&#xff0c;本专栏是针对于大学生&#xff0c;编程小白精心打造的。笔者用重金(时间和精力)打造&…

20.Java程序设计-基于SSM框架的安卓掌上校园生活系统的设计与实现

摘要&#xff1a; 随着移动互联网技术的快速发展&#xff0c;校园生活信息化成为提高学校管理效率、方便学生生活的关键。本研究以基于SSM&#xff08;Spring Spring MVC MyBatis&#xff09;框架的技术体系为基础&#xff0c;致力于设计与实现一款功能强大、高效稳定的安卓…

现代雷达车载应用——第2章 汽车雷达系统原理 2.6节 雷达设计考虑

经典著作&#xff0c;值得一读&#xff0c;英文原版下载链接【免费】ModernRadarforAutomotiveApplications资源-CSDN文库。 2.6 雷达设计考虑 上述部分给出了汽车雷达基本原理的简要概述。在雷达系统的设计中&#xff0c;有几个方面是必不可少的&#xff0c;它们决定了雷达系…

VUE学习三、前端项目部署

1.前端项目打包 执行命令 npm run build:prod正常命令结束 , 会在前端项目里面出现dist文件夹 2.nginx下载安装 nginx下载 : http://nginx.org/en/download.html Windows 下载版本 Mainline version&#xff1a;Mainline 是 Nginx 目前主力在做的版本&#xff0c;可以说…

《使用ThinkPHP6开发项目》 - ThinkPHP6使用使用中间件验证登录Token

https://blog.csdn.net/centaury32/article/details/134997438 按照https://blog.csdn.net/centaury32/article/details/134999029的方法验证登录Token&#xff0c;那么每一步都需要写同样一段代码&#xff0c;这里可以结合中间件进行验证 一、创建中间件&#xff1a;php thi…

QT----第三天,Visio stdio自定义封装控件,鼠标事件,定时器,事件分发器过滤器,绘图事件

目录 第三天1 自定义控件封装2 QT鼠标事件3 定时器4 event事件分发器5 事件过滤器6 绘图事件Qpainter 源码&#xff1a;CPP学习代码 第三天 1 自定义控件封装 新建一个QT widgetclass&#xff0c;同时生成ui,h,cpp文件 在smallWidget.ui里添加上你想要的控件并调试大小 回到…

ISSUE的基本概念

ISSUE:将符合一定条件的指令从发射队列&#xff08;IssueQueue)中选出来&#xff0c;并送到FU中执行的过程; ISSUE QUEUE也称之为reservation station, 其按照一定的规则&#xff0c;选择那些源操作数都已经准备好的指令&#xff0c;将其送到FU中执行&#xff0c;这个过程称为…

11.jvm第三方工具使用实践

目录 概述GCEasy官网jvm内存占用情况关键性能指标堆内存与元空间优化 MAT安装MAT相关概念说明内存泄漏与内存溢出shallow heap及retained heapoutgoing references与incoming referencesDominator Tree GCViewerArthas下载安装与启动jdk8jdk 11jdk11自定义boot jarjdk17 常用命…

LVS负载均衡集群和NAT模式部署

目录 一、群集的类型及含义 二、LVS的三种工作模式 一、根据群集所针对的目标差异&#xff0c;可分为三种类型 二、LVS的负载调度算法 三、ipvsadm 工具选项 四、NAT模式 LVS负载均衡群集部署 1.共享服务器配置&#xff1a; 2.节点服务器1配置 3.节点服务器2 4.配置负…

spring 笔记八 SpringMVC异常处理和SpringMVC拦截器

文章目录 SpringMVC拦截器拦截器&#xff08;interceptor&#xff09;的作用拦截器和过滤器区别拦截器是快速入门拦截器方法说明 SpringMVC拦截器拦截器&#xff08;interceptor&#xff09;的作用拦截器和过滤器区别拦截器是快速入门拦截器方法说明 SpringMVC异常处理异常处理…

网络服务IP属地发生变化的原因有哪些?

近期&#xff0c;许多用户发现自己的网络服务IP属地发生了变化。原本固定的IP地址不再是静态的&#xff0c;而是发生了变动。这一现象引起了广大用户的关注和疑惑&#xff0c;对网络服务的使用和信息安全产生了影响。为了解决用户的疑虑&#xff0c;我们对此现象进行了深入探究…

java.lang.UnsupportedOperationException

一、背景 记录一次小坑… 最近在写一个关于Excel导出的小需求&#xff0c;由于系统都有一些工具类&#xff0c;还有原来已经做好的导出&#xff0c;直接拿过来改了改就用了&#xff0c;没想到直接报错&#xff0c;尴尬。 还是那句话&#xff0c;别人都能用&#xff0c;我复制…

国产浪潮服务器:风扇免手动调节脚本

简介&#xff1a;浪潮集团&#xff0c;是中国本土顶尖的大型IT企业之一&#xff0c;中国领先的云计算、大数据服务商。浪潮集团旗下拥有浪潮信息、浪潮软件、浪潮国际&#xff0c;业务涵盖云计算、大数据、工业互联网等新一代信息技术产业领域&#xff0c;为全球120多个国家和地…

【人工智能】模糊推理

模糊推理:交叉的隶属函数&#xff0c;方法如模糊规则中推理方法一致&#xff0c;step3中的每一个格子都有所应的隶属关系函数 引言&#xff1a;“生活中的模糊关系”模糊集合模糊集合的定义模糊集合的表示法模糊集合表示法示例 隶属函数 模糊规则模糊计算的流程 引言&#xff1…

RT-DETR 目标过线计数

使用 Ultralytics RT-DETR 进行目标计数 🚀 实际应用场景 物流水产养殖使用 Ultralytics RT-DETR 进行传送带包裹计数使用 Ultralytics RT-DETR 在海中进行鱼类计数请使用最新代码(2023年12月8日后),旧版本不支持! 示例 “目标计数示例” 目标计数 from ultralytics