Spring Boot整合Minio实现文件上传
Spring Boot整合Minio后,前端的文件上传有两种方式:
-
文件上传到后端,由后端保存到Minio
-
这种方式好处是完全由后端集中管理,可以很好的做到、身份验证、权限控制、文件与处理等,并且可以做一些额外的业务逻辑,比如生成缩略图、提取元数据等。
-
缺点也很明显:
-
延迟时间高了,本来花费上传一次文件的时间,现在多了后端保存到Minio的时间
-
后端资源占用,后端本来可以只处理业务请求,现在还要负责文件流,增加了性能压力
-
单点故障,Minio即便做了集群,但是如果后端服务器故障,也会导致Minio不可用
-
所以,实际上我们不会把文件传到后端,而是直接传给Minio,其实这也符合OSS服务的使用方式。
-
文件向后端申请上传凭证,然后直接上传到Minio
-
为了避免Minio被攻击,我们需要结合后端,让后端生成并返回一个有时效的上传凭证,前端拿着这个凭证才能去上传,通过这种方式,我们可以做到一定程度的权限控制,本文要分享的就是这种方式。
环境准备
- 部署好的Minio环境:http://mylocalhost:9001
Spring Boot整合Minio
简单过一下整合方式把。
先引入Minio依赖
# pom.xml
<dependency> <groupId>io.minio</group Id>
<artifactId>minio</artifact Id>
<version>7.1.0</version>
</dependency>
然后定义配置信息
# application.yml
minio:
endpoint: http://mylocalhost:9001
accessKey: minio
secretKey: minio123
bucket: demo
定义一个属性类
@Component
@ConfigurationProperties(prefix = "minio")
public class MinioProperties { /** * 对象存储服务的URL */ private String endpoint; /** * Access key就像用户ID,可以唯一标识你的账户 */ private String accessKey;/** * Secret key是你账户的密码*/private String secretKey; /*** 默认文件桶*/ private String bucket; ...
}
定义Minio配置类
@Configuration
public class MinioConfig { @Beanpublic MinioClient minioClient(MinioProperties properties){ try { MinioClient.Builder builder = MinioClient.builder(); builder.endpoint(properties.getEndpoint());if (StringUtils.hasLength(properties.getAccessKey()) && StringUtils.hasLength(properties.getSecretKey())) { builder.credentials(properties.getAccessKey(),properties.getSecretKey()); } return builder.build(); } catch (Exception e){ return null;}}
}
现在启动服务即可。
上传凭证
写一个接口,返回上传凭证:
@RequestMapping(value = "/presign", method = {RequestMethod.POST})
public Map<String, String> presign(@RequestBody PresignParam presignParam) {//如果前端不指定桶,那么给一个默认的if (StringUtils.isEmpty(presignParam.getBucket())) {presignParam.setBucket("demo"); } // 前端不指定文件名称,就给一个UUID if (StringUtils.isEmpty(presignParam.getFilename())) { presignParam.setFilename(UUID.randomUUID().toString());} // 如果想要以子目录的方式保存,就在前面加上斜杠来表示 // presignParam.setFilename("/2023/" + presignParam.getFilename());// 设置凭证过期时间 ZonedDateTime expirationDate = ZonedDateTime.now().plusMinutes(10); // 创建一个凭证 PostPolicy policy = new PostPolicy(presignParam.getBucket(), presignParam.getFilename(), expirationDate); // 限制文件大小,单位是字节byte,也就是说可以设置如:只允许10M以内的文件上传 // policy.setContentRange(1, 10 * 1024); // 限制上传文件请求的ContentType // policy.setContentType("image/png");try {// 生成凭证并返回final Map<String, String> map = minioClient.presignedPostPolicy(policy);for (Map.Entry<String, String> entry : map.entrySet()) { System.out.println(entry.getKey() + " = " + entry.getValue()); } return map; } catch (MinioException | InvalidKeyException | IOException | NoSuchAlgorithmException e) { e.printStackTrace();}return null;
}
上面的示例代码可以知道,我们还可以加一些权限认证,以判断用户是否有以下权限:
-
上传权限
-
可上传的文件大小
-
可上传的文件类型
请求参数类:
public class PresignParam { // 桶名private String bucket; // 文件名 private String filename; ...
}
这个接口的返回结果是:
bucket: demo
x-amz-date: 20230831T042351Z
x-amz-signature: 79cc2ae0baee274d1d47cb29bdd5e99127059033503c2a02f904f0478a73ecac
key: 寂寞的季节.mp4
x-amz-algorithm: AWS4-HMAC-SHA256
x-amz-credential: minio/20230831/us-east-1/s3/aws4_request
policy: eyJleHBpcmF0aW9uIjoiMjAyMy0wOC0zMVQwNDozMzo1MS42MzZaIiwiY29uZGl0aW9ucyI6W1siZXEiLCIkYnVja2V0IiwiZGVtbyJdLFsiZXEiLCIka2V5Iiwi5a+C5a+e55qE5a2j6IqCLm1wNCJdLFsiZXEiLCIkeC1hbXotYWxnb3JpdGhtIiwiQVdTNC1ITUFDLVNIQTI1NiJdLFsiZXEiLCIkeC1hbXotY3JlZGVudGlhbCIsIm1pbmlvLzIwMjMwODMxL3VzLWVhc3QtMS9zMy9hd3M0X3JlcXVlc3QiXSxbImVxIiwiJHgtYW16LWRhdGUiLCIyMDIzMDgzMVQwNDIzNTFaIl1dfQ==
-
bucket:表示目标桶
-
x-amz-date:时间戳
-
x-amz-signature:签名
-
key:文件名
-
x-amz-algorithm:签名算法
-
x-amz-credential:认证授权
-
policy:凭证token
前端收到后,将该凭证连同文件流一并上传到Minio服务器:
uploadFile(file, policy) { console.log("准备上传文件:") console.log("file:" + file) console.log("policy:" + policy) var formData = new FormData() formData.append('file', file) formData.append('key', policy['key']) formData.append('x-amz-algorithm', policy['x-amz-algorithm'])formData.append('x-amz-credential', policy['x-amz-credential']) formData.append('x-amz-signature', policy['x-amz-signature']) formData.append('x-amz-date', policy['x-amz-date']) formData.append('policy', policy['policy'])return new Promise(((resolve, reject) => {$.ajax({method: 'POST',url: 'http://mylocalhost:9001/' + policy['bucket'],data: formData, dataType: 'json', contentType: false, // 必须设置为 false,不设置 contentType,让浏览器自动设置processData: false, // 必须设置为 false,不对 FormData 进行序列化处理 // async: false, // 设置同步,方便等下做分片上传xhr: function xhr() {//获取原生的xhr对象 var xhr = $.ajaxSettings.xhr(); if (xhr.upload) { //添加 progress 事件监听 xhr.upload.addEventListener('progress', function (e) {//e.loaded 已上传文件字节数 //e.total 文件总字节数var percentage = parseInt(e.loaded / e.total * 100) vm.uploadResult = percentage + "%" + ":" + policy['key']}, false); } return xhr; }, success: function (result) { vm.uploadResult = '文件上传成功:' + policy['key'] resolve(result) }, error: function (e) { reject()} }) }))
}
这样就完成了获取上传凭证并上传文件。
分片上传、秒传、断点续传
分片上传
分片上传可以用在大文件上传上,一个100M的文件可以分成10份,每份10M,一共传输10次,这有以下好处:
-
Minio做了集群,用Nginx转发,那么分片上传可以降低单台Minio服务器的性能压力
-
多线程上传可以加快上传效率
秒传
现在说说秒传,我们上传一个文件之前,可以用工具生成MD5字符串,就好像这样:
3cc1f3c3c2d1a29ecf60ffad4de278c7
然后拼接上文件名:
3cc1f3c3c2d1a29ecf60ffad4de278c7_寂寞的季节.mp4
这时候去向后端申请上传凭证的时候,后端可以先去看看文件是否已存在,如果文件已存在,就不用生成凭证了,直接告诉前端该文件已经上传完毕,由此实现文件秒传。
这样的好处是:
-
降低Minio服务器压力
-
响应秒回,用户体验提高
断点续传
结合分片上传和秒传的原理,我们可以来做到断点续传。
场景:当我们要上传一个大文件的时候,进度到一半了,这时候网络掉线导致上传失败,网络恢复后又要重新上传,这就很崩溃。
处理方式:大文件也可以分成一个个小文件来上传,这样即便上传到一半网络掉线,恢复上传的时候可以跳过前一半已上传的部分,接着上传后面一半。
文件合并
当我们分片上传后,后端还需要提供接口,来将所有分片数据合并:
@GetMapping("/compose")public void merge() {List<ComposeSource> sources = new ArrayList<>();// 分片数据放到另一个桶里面:slice sources.add(ComposeSource.builder() .bucket("slice") .object("0寂寞的季节.mp4").build()); sources.add(ComposeSource.builder().bucket("slice") .object("1寂寞的季节.mp4").build());sources.add(ComposeSource.builder().bucket("slice").object("2寂寞的季节.mp4") .build());final ComposeObjectArgs args = ComposeObjectArgs.builder() .bucket("demo") .object("寂寞的季节.mp4").sources(sources).build(); try { minioClient.composeObject(args);} catch (MinioException | InvalidKeyException | IOException | NoSuchAlgorithmException e) {e.printStackTrace(); } }
上面的示例很简单,因为只做演示说明。
前端需要传的参数是:
-
分片桶:slice
-
分片数据数组: 0寂寞的季节.mp4 1寂寞的季节.mp4 2寂寞的季节.mp4
-
目标桶:demo
然后调用composeObject函数完成合并。
前端示例代码分享
上面就是关于实战经验分享的全部了,因为需要前端配置来使用,所以这里给出我这篇文章的前端示例,很简单的单页面(技术栈就别吐槽了):
<!DOCTYPE html><html lang="en"> <head> <meta charset="UTF-8"><title>Title</title> <script src="https://cdn.bootcdn.net/ajax/libs/vue/2.7.14/vue.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.4/jquery.js"></script> <script src="https://cdn.bootcdn.net/ajax/libs/spark-md5/3.0.2/spark-md5.min.js"></script>
</head>
<body>
<div id="app"> <h1>{{title}}</h1>
<br> <form @submit.prevent="getPolicyForm"><label> 桶名 <input type="text" v-model="policyParams.bucket"></label>
<br><label> 文件名 <input type="text" v-model="policyParams.filename"> </label>
<br> <button type="submit">获取上传凭证</button>
<br> <div v-for="(val, key) in policy" :key="key">{{ key }}: <span>{{ val }}</span></div></form>
<br> <form @submit.prevent="uploadFileForm" v-show="policy != null"><label> 文件 <input type="file" @change="fileChange"></label>
<br> <br><button type="submit" v-show="file != null">上传文件</button>
</form> ---
<br> <div v-show="file != null"> <button @click="sliceEvent">测试文件分片上传</button> | <button @click="sliceComposeEvent">分片文件合并</button> </div>
<br><br> <br> <p>{{uploadResult}}</p>
<ul> <!-- <li v-for="item in sliceUploadResult">{{ item }}</li>--> <li v-for="(item, index) in sliceUploadResult" :key="index">{{ item }}</li> </ul> <br> </div> <script> var vm = new Vue({el: "#app", data() { return { title: "Minio测试" // 请求凭证参数 , policyParams: { bucket: null , filename: null } // 请求到的凭证 , policy: null // 待上传文件 , file: null// 上传文件参数 , uploadParams: { file: null } // 分片上传参数 , sliceParams: { bucket: "" , filename: "" , file: null } , slicePolicys: [] , sliceCount: 0// 上传结果回调 , uploadResult: null // 分片上传结果回调 , sliceUploadResult: null };}, methods: { getPolicyForm() {this.policyParams.bucket = "demo" this.policyParams.filename = "寂寞的季节.mp4" this.requestPolicy(this.policyParams)}, requestPolicy(params) { return new Promise(((resolve, reject) => { $.ajax({ type: "POST", url: "http://localhost:8888/presign", contentType: "application/json", data: JSON.stringify(params),// async: false, success: function (result) { console.log(result) vm.policy = result; resolve(result) }, error: function (e) { reject() }});}))}, fileChange(event) { const file = event.target.files[0] this.file = file }, uploadFileForm() { this.uploadFile(this.file, this.policy) }, uploadFile(file, policy) { console.log("准备上传文件:") console.log("file:" + file) console.log("policy:" + policy) var formData = new FormData() formData.append('file', file) formData.append('key', policy['key']) formData.append('x-amz-algorithm', policy['x-amz-algorithm']) formData.append('x-amz-credential', policy['x-amz-credential']) formData.append('x-amz-signature', policy['x-amz-signature'])formData.append('x-amz-date', policy['x-amz-date']) formData.append('policy', policy['policy']) return newPromise(((resolve, reject) => { $.ajax({ method: 'POST', url: 'http://mylocalhost:9001/' + policy['bucket'], data: formData, dataType: 'json', contentType: false, // 必须设置为 false,不设置 contentType,让浏览器自动设置 processData: false,// 必须设置为 false,不对 FormData 进行序列化处理 // async: false, // 设置同步,方便等下做分片上传 xhr: function xhr() { //获取原生的xhr对象 var xhr = $.ajaxSettings.xhr(); if (xhr.upload) { //添加 progress 事件监听 xhr.upload.addEventListener('progress', function (e) {//e.loaded 已上传文件字节数 //e.total 文件总字节数 var percentage = parseInt(e.loaded / e.total * 100) vm.uploadResult = percentage + "%" + ":" + policy['key'] }, false);} return xhr; }, success: function (result) { vm.uploadResult = '文件上传成功:' + policy['key'] resolve(result) }, error: function (e) { reject() } })}))
}, sliceEvent() { // 获取文件 var file = this.file// 设置分片大小:5MB var chunkSize = 5 * 1024 * 1024 // 计算总共有多少个分片 var totalChunk = Math.ceil(file.size / chunkSize)// 数组存放所有分片 var chunks = [] // 遍历所有分片 for (var i = 0; i < totalChunk; i++) { // 利用slice获取分片 var start = i * chunkSize var end = Math.min(file.size, start + chunkSize) var blob = file.slice(start, end)// 添加分片到数组 chunks.push(blob) } console.log(totalChunk) this.sliceUploadResult = Array(totalChunk).fill(0) for (let i = 0; i < chunks.length; i++) { var file = chunks[i]; this.calculateMD5(file) .then((md5) => { console.log(md5);// 输出计算出的 MD5 值 }) .catch((error) => { console.error(error); // 处理错误 }); } return// 创建序号 var index = 0; // 循环上传分片 while (index < totalChunk) { console.log('------------------------------') params = { "bucket": "slice", "filename": index + "寂寞的季节.mp4" } var policyPromise = this.requestPolicy(params); (function (index) { var file = chunks[index] policyPromise.then(function (result) { var filename = result['key'] console.log('准备上传文件:', filename, ',序号为:', index) vm.uploadFile(file, result).then(function (result) { console.log('上传完成:' + filename) vm.sliceUploadResult[index] = ('分片文件上传成功:' + filename) }) }) })(index) index++ } }, sliceComposeEvent() { var parmas = {} $.ajax({ method: 'POST', url: 'http://localhost:8888/compose', data: formData, dataType: 'json', contentType: false, // 必须设置为 false,不设置 contentType,让浏览器自动设置 processData: false, // 必须设置为 false,不对 FormData 进行序列化处理 // async: false, // 设置同步,方便等下做分片上传 xhr: function xhr() { //获取原生的xhr对象 var xhr = $.ajaxSettings.xhr(); if (xhr.upload) { //添加 progress 事件监听 xhr.upload.addEventListener('progress', function (e) { //e.loaded 已上传文件字节数 //e.total 文件总字节数 var percentage = parseInt(e.loaded / e.total * 100) vm.uploadResult = percentage + "%" + ":" + policy['key'] }, false);} return xhr; },success: function (result) { vm.uploadResult = '文件上传成功:' + policy['key'] resolve(result) },error: function (e) { reject() }
})
}, calculateMD5(file) { return new Promise((resolve, reject) => { const reader = new FileReader(); // 读取文件内容 reader.readAsArrayBuffer(file); reader.onload = () => { const spark = new SparkMD5.ArrayBuffer(); spark.append(reader.result); // 将文件内容添加到 MD5 计算器中 const md5 = spark.end();// 计算 MD5 值 resolve(md5); }; reader.onerror = (error) => { reject(error); }; });}
}, mounted() {
}, created() {
},
});
</script>
</body>
</html>
相关文章:
Spring Boot整合Minio实现文件上传
Spring Boot整合Minio后,前端的文件上传有两种方式: 文件上传到后端,由后端保存到Minio 这种方式好处是完全由后端集中管理,可以很好的做到、身份验证、权限控制、文件与处理等,并且可以做一些额外的业务逻辑…...
GitHub - riscv-software-src/riscv-isa-sim: Spike, a RISC-V ISA Simulator
GitHub - riscv-software-src/riscv-isa-sim: Spike, a RISC-V ISA Simulator 操作手册 $ apt-get install device-tree-compiler libboost-regex-dev libboost-system-dev $ mkdir build $ cd build $ ../configure --prefix$RISCV $ make $ [sudo] make install 具体安装 …...
ceph文件系统
ceph文件系统: 使用设备:4台机器 高度可扩展,分布式的存储文件系统,旨在提供高性能,高可靠性和高可用的对象存储,块存储,文件系统的存储 使用分布式的算法保证数据的高可用和一致性 ceph的架…...
模型创新、论文复现、科研辅导、论文代码定制
建模先锋团队长期致力于为用户提供优质的代码定制服务。团队提供全网最低价格的服务,同时保证高性价比和高质量的代码交付,为您提供个性化定制的服务。 以下是定制服务范围: 通过深度学习和信号处理技术,我们能够针对不同行业和场…...
【flink-cdc】flink-cdc 3版本debug启动pipeline任务,mysql-doris
官方文档 github仓库地址 Flink cdc debug调试动态变更表结构 经过测试使用,在启动任务配置Modify classpath添加jar的方式,容易出错classNotFoundException等等。 一、build project flink-cdc版本:3.2.1 mvn clean package "-Dma…...
mybatisX插件的使用,以及打包成配置
装mybatisX插件; idea连接数据库; 点击mybatisx-generator,设置自己装mybatisX插件; idea连接数据库; 点击mybatisx-generator,设置自己要的包和类; 如果要把自己的配置设置成一个自定义模板&a…...
pip下载包出现SSLError
报错: ERROR: Could not install packages due to an OSError: HTTPSConnectionPool(host‘files.pythonhosted.org’, port443): Max retries exceeded with url: /packages/8a/c2/ae7227e4b089c6a8210920db9d5ac59186b0a84eb1e6d96b9218916cdaf1/taming_transform…...
Linux下查看文件和文件夹占用空间大小
使用Linux命令,查看文件磁盘所占的空间大小,下面可以通过以下命令进行操作 df 可以查看一级文件夹大小、使用比例、档案系统及其挂入点,但对文件束手无策du 查看文件和文件夹的磁盘使用空间 在使用中,一般是df命令和du命令一起联…...
【论文+源码】基于Spring和Spring MVC的汉服文化宣传网站
为了实现一个基于Spring和Spring MVC的汉服文化宣传网站,我们需要创建一个简单的Web应用程序来展示汉服文化和相关信息。这个系统将包括以下几个部分: 数据库表设计:定义文章、用户和评论的相关表。实体类:表示数据库中的数据。DAO层接口及MyBatis映射文件:用于与数据库交…...
C++语言的学习路线
C语言的学习路线 C是一门复杂而强大的编程语言,由于其高性能和灵活性,受到了许多开发者和企业的青睐。无论是系统软件、嵌入式系统还是游戏开发,C都有非常广泛的应用。要掌握C这门语言,需要合理制定学习路线,并结合实…...
【OpenCV】使用Python和OpenCV实现火焰检测
1、 项目源码和结构(转) https://github.com/mushfiq1998/fire-detection-python-opencv 2、 运行环境 # 安装playsound:用于播放报警声音 pip install playsound # 安装opencv-python:cv2用于图像和视频处理,特别是…...
【ArcGISPro/GeoScenePro】解决常见的空间参考和投影问题
修复空间参考缺失的图像 数据 https://arcgis.com/sharing/rest/content/items/535efce0e3a04c8790ed7cc7ea96d02d/data 查看属性坐标 查看属性范围 范围值并不是零或接近于零。 这意味着栅格具有范围,因此其已正确进行...
Ruby语言的语法
Ruby语言的语法之美 Ruby是一种动态、开放源代码的编程语言,由日本的松本行弘(Yukihiro Matsumoto)于1995年首次发布。Ruby语言以其简洁、优雅和易于阅读的语法而闻名。它不仅适合初学者,还被广泛应用于Web开发、数据分析和其他领…...
概述(讲讲python基本语法和第三方库)
我是北子,这是我自己写的python教程,主要是记录自己的学习成果方便自己日后复习, 我先学了C/C,所以这套教程中可能会将很多概念和C/C去对比,所以该教程大概不适合零基础的人。 it seems that python nowadays 只在人工…...
程序包org.springframework.boot不存在
springBoot项目启动报错 程序包org.springframework.boot不存在 1、检查依赖 首先检查pom文件判断依赖是否存在 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId><version>2.4.5…...
.NET Core FluentAPI
目录 约定配置 主要规则 两种配置方式 Data Annotation Fluent API Fluent API配置 Fluent API众多方法 选择 约定配置 主要规则 表名采用DbContext中的对应的DbSet的属性名。数据表列的名字采用实体类属性的名字,列的数据类型采用和实体类属性类型最兼容…...
浙江省自然资源厅:基于“浙里办”的自然资源移动政务服务创新实践——“浙里自然资源”
摘 要:本文基于浙江省自然资源移动政务服务的创新实践,设计和实现“浙里自然资源”应用,依托浙江省省域空间治理数字化平台特有的架构基础,在提升功能性和可用性、加强运营力度、丰富服务内容等方面采取了管理举措和技术创新。通…...
【Astro】如何在Astro上借助Cloudflare D1和Drizzle ORM打造全栈应用?一文带你搞定!
如何在Astro上借助Cloudflare D1和Drizzle ORM打造全栈应用?一文带你搞定! 文章目录 如何在Astro上借助Cloudflare D1和Drizzle ORM打造全栈应用?一文带你搞定!前言一、Astro简介与优势二、Cloudflare D1简介三、Drizzle ORM简介四…...
【Axios使用手册】如何使用axios向后端发送请求并进行数据交互
axios 是一个基于 Promise 的 HTTP 客户端,用于浏览器和 Node.js。它支持请求和响应拦截、取消请求、自动转换 JSON 数据等功能,非常适合在现代 JavaScript 应用中进行网络请求。以下是对 axios 的详细讲解,包括安装、基本用法、高级功能等。…...
边缘计算应用十大领域
边缘计算解决了互联网的网速问题,作为实现边缘计算的基础,那边缘计算是5G与产业互联网、物联网时代的重要技术支撑,也正迎来广阔的增长空间。那么现在我们生活中有哪些领域正在使用边缘计算呢?今天我们来盘点一下我们身边正在使用…...
CSS 学习之 padding 与图形绘制
padding 属性和 background-clip 属性配合,可以在有限的标签下实现一些 CSS 图形绘制效果,我这里举两个小例子,重在展示可行性。 例 1:不使用伪元素,仅一层标签实现大队长的“三道杠”分类图标效果。此效果在移动端比较常见&…...
熔断器模式如何进入半开状态的
熔断器模式在进入打开状态并经过一段冷却时间后,会自动进入半开状态。这个过程是熔断器模式自我恢复机制的一部分,旨在测试下游服务是否已经恢复正常,从而决定是否重新允许请求通过。 1. 进入打开状态: • 当服务调用失败次数达到…...
数据结构:双向循环链表
双向循环链表(Doubly Circular Linked List) 双向循环链表是双向链表的一种变体,其特点是链表的头节点和尾节点相连,形成一个闭环。这种结构允许在链表中进行无缝的双向遍历,并且由于循环特性,可以从任何节…...
宝安湾区之光附近的钓鱼点
工作日的午休我经常在公司附近骑行,有时候也会骑行到宝安的湾区之光。但是我最感兴趣的除了湾区之光摩天轮,还有雷打不动的快乐钓鱼佬。 上图红框区域的河岸每天都会出现零零散散的快乐钓鱼佬,他们好像都有自己的钓鱼窝点。我发现来这里钓鱼也…...
【计算机网络】什么是AC和AP?
在现代的无线网络中,AC(Access Controller,接入控制器)和AP(Access Point,无线接入点)是两个至关重要的设备,它们在网络的管理、连接和优化中扮演着重要角色。理解它们的功能和区别&…...
python 词法分析
词法分析(Lexical Analysis)是编译器的第一步,它的任务是将源代码文本分割成一系列有意义的单元(称为“词法单元”或“Token”)。这些词法单元通常包括关键字、标识符、常量、运算符、分隔符等。 import re# 定义词法单…...
JUC--CAS原理(以Atomic报下类的实现来了解CAS的原理)
以Atomic来了解CAS的原理 六、无锁6.1CAS(Compare-And-Swap)原理6.2CAS与synchronized6.3Atomic(原子类)原理分析 6.4ABA问题6.4unsafe 六、无锁 6.1CAS(Compare-And-Swap)原理 CAS原理:CAS是…...
对比显式启用-u_printf_float和-u_scanf_float前后的代码内存体量实验
本文的嵌入式编译器基于GCC for ARM,构建文件基于Makefile。 main.c不编写任何代码,保证实验的其他变量统一。源文件main.c: 优化等级固定为 -Og : syscalls.c 系统调用库函数文件参考:基于GCC for ARM交叉编译工具链…...
嵌入式 Linux LED 驱动开发实验
一、Linux 下 LED 灯驱动原理 a)地址映射 在编写驱动之前,我们需要先简单了解一下 MMU 这个神器, MMU 全称叫做 Memory Manage Unit,也就是内存管理单元。在老版本的 Linux 中要求处理器必须有 MMU,但是现在 Linux 内核已经支持无 MMU 的处理器了。 MMU 主要完成的功能如…...
qml PathView详解
1、概述 PathView 是 Qt Quick 中一个非常强大的视图组件,它基于一个 Path 来展示视图项(如 Item、Rectangle 等)。PathView 可以让你按照定义的路径动态地显示多个元素,并且支持动画、滑动等功能。这个视图控件的最大特点是能够…...
Spring源码分析之事件机制——观察者模式(一)
目录 事件基类定义 事件监听器接口 事件发布者接口及实现 事件广播器实现 小小总结 Spring源码分析之事件机制——观察者模式(一)-CSDN博客 Spring源码分析之事件机制——观察者模式(二)-CSDN博客 Spring源码分析之事件机制…...
安卓14无法安装应用解决历程
客户手机基本情况: 安卓14,对应的 targetSdkVersion 34 前天遇到了安卓14适配问题,客户发来的截图是这样的 描述:无法安装我们公司的B应用。 型号:三星google美版 解决步骤: 1、寻找其他安卓14手机测试…...
BGP(Border Gateway Protocol)路由收集器
全球 BGP(边界网关协议)路由收集器的分布情况以及相关数据。以下是主要的信息解读: 地图标记: 每个绿色点代表一个路由收集器的位置。路由收集器分布在全球不同的地区,覆盖了五大区域: ARIN(美…...
Vue.js与其他框架有哪些兼容性?
Vue.js的兼容性主要体现在几个方面,包括浏览器支持、运行环境适应性、与其他库和框架的集成能力等。以下是更详细的解释: 浏览器兼容性 现代浏览器:Vue.js广泛支持所有主流的现代浏览器,如Google Chrome, Firefox, Safari, Edge…...
深度解析与实践:HTTP 协议
一、引言 HTTP(HyperText Transfer Protocol,超文本传输协议)是 Web 应用程序、API、微服务以及几乎所有互联网通信的核心协议。虽然它是我们日常使用的基础技术,但要深刻理解其高效使用、优化以及如何避免性能瓶颈,我…...
MyBatis 配置文件全解析
一、MyBatis 配置文件为何至关重要? 在 Java 后端开发领域,MyBatis 作为一款广受欢迎的持久层框架,极大地简化了数据库操作。而 MyBatis 配置文件,恰似整个框架的 “神经中枢”,掌控着其运行的方方面面,对…...
redis Redis内存缓存过期机制
起因:随着项目的进一步推广,数据量的增大,直接访问mysql数据库获取数据所使用的时间越来越长,为解决当前主要矛盾,决定引入redis非关系型数据库作为缓存层,使得数据并不能直接命中数据库,减少访…...
游戏关卡设计的常用模式
游戏关卡分为很多种,但常用的有固定套路,分为若干种类型。 关卡是主角与怪物、敌方战斗的场所,包括装饰物、通道。 单人游戏的关卡较小,偏线性; 联机/MMO的关卡较大,通道多,自由度高…...
计算机网络常见面试题及解答
以下是计算机网络中常见的面试题及解答,按主题分类: --- ## **一、基础概念** ### **1. OSI 七层模型和 TCP/IP 模型的区别是什么?** **答:** - **OSI 七层模型:** - 应用层、表示层、会话层、传输层、网络层、数…...
SUB输入5V升压充电16.8V芯片HU5912
HU5912芯片,作为航誉微电子有限公司推出的一款高性能升压充电管理IC,自其面世以来,便以其出色的性能和广泛的应用领域,受到了业界的高度关注和赞誉。本文将详细介绍HU5912芯片的技术特点、应用优势、市场定位以及其在各类电子设备…...
基于Informer网络实现电力负荷时序预测——cross validation交叉验证与Hyperopt超参数调优
前言 系列专栏:【深度学习:算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域,讨论了各种复杂的深度神经网络思想,如卷积神经网络、循环神经网络、生成对…...
linux ubantu重启桌面
在 Ubuntu 系统中,重启桌面环境通常有几种方法,具体取决于你所使用的桌面环境(如 GNOME、KDE 等)。下面是几种常用的重启桌面的方法: 重启 GNOME 桌面环境 如果你使用的是 GNOME 桌面环境(Ubuntu 默认桌面…...
C++Primer const限定符
欢迎阅读我的 【CPrimer】专栏 专栏简介:本专栏主要面向C初学者,解释C的一些基本概念和基础语言特性,涉及C标准库的用法,面向对象特性,泛型特性高级用法。通过使用标准库中定义的抽象设施,使你更加适应高级…...
【机器学习】机器学习的基本分类-自监督学习(Self-supervised Learning)
自监督学习是一种机器学习方法,介于监督学习和无监督学习之间。它通过数据本身生成标签,创建训练任务,从而学习数据的表征,而不需要人工标注的标签。这种方法在减少标注数据依赖、提高模型通用性等方面具有重要意义。 自监督学习的…...
python基础案例
#一个年份如果能被4整除但不能被 100整除,或能被 400整除,那么这个年份就是闰年。 year int(input(请输入年份:)) if (year %40 and year %100!0) or year %4000:print("这个年份就是闰年") else:print("这个年份不是闰…...
ARP(地址解析协议)攻击;TCP SYN Flood(SYN洪流)攻击
ARP(地址解析协议)攻击 是一种网络攻击类型,攻击者利用ARP协议的缺陷对目标网络实施攻击。ARP协议用于在局域网(LAN)中将IP地址解析为MAC地址,它是无认证机制的,这为攻击者提供了可利用的机会。…...
基于大数据爬虫+Python+数据可视化大屏的慧游数据爬虫与推荐分析系统(源码+论文+PPT+部署文档教程等)
博主介绍:**CSDN毕设辅导第一人、**全网粉丝50W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和学生毕业项目实战,高校老师/讲师/同行前辈交流 **技术范围:**S…...
Linux系统安装es详细教程
一、下载es及插件 从下面的网址进行对应es版本的下载https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.15.2-linux-x86_64.tar.gz ,想要不同版本的es只需更换对应的版本号即可。 插件下载地址(ik分词器、pinyin等)es…...
分布式搜索引擎之elasticsearch基本使用3
分布式搜索引擎之elasticsearch基本使用3 1.部署单点es 1.1.创建网络 因为我们还需要部署kibana容器,因此需要让es和kibana容器互联。这里先创建一个网络: docker network create es-net1.2.加载镜像 这里我们采用elasticsearch的7.12.1版本的镜像&…...
进程间通信-----信号
进程间通信-----信号 信号:进程间异步通知的机制。是一种在操作系统中用于进程间通信和控制的机制。它可以用于多种场景,例如进程间通信、异常处理、线程同步等。常见的信号有SIGINT(中断信号)、SIGTERM(终止信号&…...