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

文件上传 分片上传

分片上传则是将一个大文件分割成多个小块分别上传,最后再由服务器合并成完整的文件。这种做法的好处是可以并行处理多个小文件,提高上传效率;同时,如果某一部分上传失败,只需要重传这一部分,不影响其他部分。

初步实现

后端代码

/*** 分片上传** @param file 上传的文件* @param start 文件开始上传的位置* @param fileName 文件名称* @return  上传结果*/
@PostMapping("/fragmentUpload")
@ResponseBody
public AjaxResult fragmentUpload(@RequestParam("file") MultipartFile file, @RequestParam("start") long start, @RequestParam("fileName") String fileName) {try {// 检查上传目录是否存在,如果不存在则创建File directory = new File(uploadPath);if (!directory.exists()) {directory.mkdirs();}// 设置上传文件的目标路径File targetFile = new File(uploadPath +File.separator+ fileName);// 创建 RandomAccessFile 对象以便进行文件的随机读写操作RandomAccessFile randomAccessFile = new RandomAccessFile(targetFile, "rw");// 获取 RandomAccessFile 对应的 FileChannelFileChannel channel = randomAccessFile.getChannel();// 设置文件通道的位置,即从哪里开始写入文件内容channel.position(start);// 从 MultipartFile 对象的资源通道中读取文件内容,并写入到指定位置channel.transferFrom(file.getResource().readableChannel(), start, file.getSize());// 关闭文件通道和 RandomAccessFile 对象channel.close();randomAccessFile.close();// 返回上传成功的响应return AjaxResult.success("上传成功");} catch (Exception e) {// 捕获异常并返回上传失败的响应return AjaxResult.error("上传失败");}
}/*** 检测文件是否存在* 如果文件存在,则返回已经存在的文件大小。* 如果文件不存在,则返回 0,表示前端从头开始上传该文件。* @param filename* @return*/
@GetMapping("/checkFile")
@ResponseBody
public AjaxResult checkFile(@RequestParam("filename") String filename) {File file = new File(uploadPath+File.separator + filename);if (file.exists()) {return AjaxResult.success(file.length());} else {return AjaxResult.success(0L);}
}

前端

var prefix = ctx + "/kuroshiro/file-upload";// 每次上传大小
const chunkSize = 1 * 1024 * 1024;/*** 开始上传*/
function startUpload(type) {const fileInput = document.getElementById('fileInput');const file = fileInput.files[0];if (!file) {alert("请选择文件");return;}if(type == 1){checkFile(filename).then(start => {uploadFile(file, start,Math.min(start + chunkSize, file.size));})}
}/*** 检查是否上传过* @param filename* @returns {Promise<unknown>}*/
function checkFile(filename) {return $fetch(prefix+`/checkFile?filename=${filename}`);
}/*** 开始分片上传* @param file 文件* @param start 开始位置* @param end 结束位置*/
function uploadFile(file, start,end) {if(start < end){const chunk = file.slice(start, end);const formData = new FormData();formData.append('file', chunk);formData.append('start', start);formData.append('fileName', file.name);$fetch(prefix+'/fragmentUpload', {method: 'POST',body: formData}).then(response => {console.log(`分片 ${start} - ${end} 上传成功`);// 递归调用uploadFile(file,end,Math.min(end + chunkSize, file.size))})}}function $fetch(url,requestInit){return new Promise((resolve, reject) => {fetch(url,requestInit).then(response => {if (!response.ok) {throw new Error('请求失败');}return response.json();}).then(data => {if (data.code === 0) {resolve(data.data);} else {console.error(data.msg);reject(data.msg)}}).catch(error => {console.error(error);reject(error)});});}

以上虽然实现的分片上传,但是它是某种意义上来说还是与整体上传差不多,它是一段一段的上传,某段上传失败后,后续的就不会再继续上传;不过比起整体上传来说,它会保存之前上传的内容,下一个上传时,从之前上传的位置接着上传。不用整体上传。下面进行优化。

优化

首先,之前的分片上传,后端是直接写入了一个文件中了,所以只能顺序的上传写入,虽然可以保存上传出错之前的内容,但是整体上看来是速度也不行。
优化逻辑:把分片按顺序单独保存下来,等到所有分片都上传成功后,把所有分片合并成文件。这样上传的时候就不用等着上一个上传成功才上传下一个了。

后端代码

/**
* 分片上传* @param file 文件* @param chunkIndex 分片下标*/
@PostMapping("/uploadChunk")
@ResponseBody
public AjaxResult uploadChunk(@RequestParam("file") MultipartFile file, @RequestParam("chunkIndex") int chunkIndex,@RequestParam("fileName") String fileName) {String uploadDirectory = chunkUploadPath+File.separator+fileName;File directory = new File(uploadDirectory);if (!directory.exists()||directory.isFile()) {directory.mkdirs();}String filePath = uploadDirectory + File.separator + fileName+ "_" + chunkIndex;try (OutputStream os = new FileOutputStream(filePath)) {os.write(file.getBytes());return AjaxResult.success("分片"+(chunkIndex+1)+"上传成功");}catch (Exception e){// 保存失败后如果文件建立了就删除,下次上传时重新保存,避免文件内容错误File chunkFile = new File(filePath);if(chunkFile.exists()) chunkFile.delete();e.printStackTrace();return AjaxResult.error("分片"+(chunkIndex+1)+"上传失败");}}/*** 检测分片是否存在* 如果文件存在,则返回已经存在的分片下标集合。存在的就不上传* 如果文件不存在,则返回空集合,表示前端从头开始上传该文件* @param fileName* @return*/
@GetMapping("/checkChunk")
@ResponseBody
public AjaxResult checkChunk(@RequestParam("fileName") String fileName) {String uploadDirectory = chunkUploadPath+File.separator+fileName;List<Integer> list = new ArrayList<>();File file = new File(uploadDirectory);// 文件目录不存在if(!file.exists()||file.isFile()) return AjaxResult.success(list);File[] files = file.listFiles();// 文件目录下没有分片文件if(files == null) return AjaxResult.success(list);// 返回存在分片下标集合return AjaxResult.success(Arrays.stream(files).map(item->Integer.valueOf(item.getName().substring(item.getName().lastIndexOf("_")+1))).collect(Collectors.toList()));
}// 合并文件分片@PostMapping("/mergeChunks")@ResponseBodypublic AjaxResult mergeChunks(@RequestParam("fileName") String fileName, @RequestParam("totalChunks") int totalChunks) {String uploadDirectory = chunkUploadPath+File.separator+fileName;String mergedFilePath = uploadPath +File.separator+ fileName;try (OutputStream os = new FileOutputStream(mergedFilePath, true)) {for (int i = 0; i < totalChunks; i++) {Path chunkFilePath = Paths.get(uploadDirectory +File.separator+ fileName + "_" + i);Files.copy(chunkFilePath, os);Files.delete(chunkFilePath);}return AjaxResult.success();}catch (Exception e){e.printStackTrace();return AjaxResult.error(e.getMessage());}}

前端代码

/*** 开始上传*/
function startUpload(type) {const fileInput = document.getElementById('fileInput');const file = fileInput.files[0];if (!file) {alert("请选择文件");return;}const filename = file.name;if(type == 1){checkFile(filename).then(start => {uploadFile(file, start,Math.min(start + chunkSize, file.size));})}if(type == 2){checkChunk(filename).then(arr => {uploadChunk(file, arr);})}
}/**
* 切割文件为多个分片
* @param file
* @returns {*[]}
*/
function sliceFile(file) {const chunks = [];let offset = 0;while (offset < file.size) {const chunk = file.slice(offset, offset + chunkSize);chunks.push(chunk);offset += chunkSize;}return chunks;
}
/**
* 检查是否上传过
* @param filename
* @returns {Promise<unknown>}
*/
function checkChunk(filename) {return $fetch(prefix+`/checkChunk?fileName=${filename}`);
}/**
* 开始分片上传
* @param file 文件
* @param exists 存在的分片下标
*/
function uploadChunk(file,exists) {const chunkArr = sliceFile(file);Promise.all(chunkArr.map((chunk, index) => {if(!exists.includes(index)){const formData = new FormData();formData.append('file', chunk);formData.append('fileName', file.name);formData.append('chunkIndex', index);return $fetch(prefix+'/uploadChunk', {method: 'POST',body: formData});}})).then(uploadRes=> {// 合并分片const formData = new FormData();formData.append('fileName', file.name);formData.append('totalChunks', chunkArr.length);$fetch(prefix + '/mergeChunks', {method: 'POST',body:formData,}).then(mergeRes=>{console.log("合并成功")});});
}

以上优化后所有分片可以同时上传,所有分片上传都成功后进行合并。

最后是完整代码

@Controller()
@RequestMapping("/kuroshiro/file-upload")
public class FileUploadController {private String prefix = "kuroshiro/fragmentUpload";// 文件保存目录private final String uploadPath = RuoYiConfig.getUploadPath();// 分片保存目录private final String chunkUploadPath = uploadPath+File.separator+"chunks";/*** demo* @return*/@GetMapping("/demo")public String demo() {return prefix+"/demo";}/*** 分片上传** @param file 上传的文件* @param start 文件开始上传的位置* @param fileName 文件名称* @return  上传结果*/@PostMapping("/fragmentUpload")@ResponseBodypublic AjaxResult fragmentUpload(@RequestParam("file") MultipartFile file, @RequestParam("start") long start, @RequestParam("fileName") String fileName) {try {// 检查上传目录是否存在,如果不存在则创建File directory = new File(uploadPath);if (!directory.exists()) {directory.mkdirs();}// 设置上传文件的目标路径File targetFile = new File(uploadPath +File.separator+ fileName);// 创建 RandomAccessFile 对象以便进行文件的随机读写操作RandomAccessFile randomAccessFile = new RandomAccessFile(targetFile, "rw");// 获取 RandomAccessFile 对应的 FileChannelFileChannel channel = randomAccessFile.getChannel();// 设置文件通道的位置,即从哪里开始写入文件内容channel.position(start);// 从 MultipartFile 对象的资源通道中读取文件内容,并写入到指定位置channel.transferFrom(file.getResource().readableChannel(), start, file.getSize());// 关闭文件通道和 RandomAccessFile 对象channel.close();randomAccessFile.close();// 返回上传成功的响应return AjaxResult.success("上传成功");} catch (Exception e) {// 捕获异常并返回上传失败的响应return AjaxResult.error("上传失败");}}/*** 检测文件是否存在* 如果文件存在,则返回已经存在的文件大小。* 如果文件不存在,则返回 0,表示前端从头开始上传该文件。* @param filename* @return*/@GetMapping("/checkFile")@ResponseBodypublic AjaxResult checkFile(@RequestParam("filename") String filename) {File file = new File(uploadPath+File.separator + filename);if (file.exists()) {return AjaxResult.success(file.length());} else {return AjaxResult.success(0L);}}/*** 分片上传* @param file 文件* @param chunkIndex 分片下标*/@PostMapping("/uploadChunk")@ResponseBodypublic AjaxResult uploadChunk(@RequestParam("file") MultipartFile file, @RequestParam("chunkIndex") int chunkIndex,@RequestParam("fileName") String fileName) {String uploadDirectory = chunkUploadPath+File.separator+fileName;File directory = new File(uploadDirectory);if (!directory.exists()||directory.isFile()) {directory.mkdirs();}String filePath = uploadDirectory + File.separator + fileName+ "_" + chunkIndex;try (OutputStream os = new FileOutputStream(filePath)) {os.write(file.getBytes());return AjaxResult.success("分片"+(chunkIndex+1)+"上传成功");}catch (Exception e){// 保存失败后如果文件建立了就删除,下次上传时重新保存,避免文件内容错误File chunkFile = new File(filePath);if(chunkFile.exists()) chunkFile.delete();e.printStackTrace();return AjaxResult.error("分片"+(chunkIndex+1)+"上传失败");}}/*** 检测分片是否存在* 如果文件存在,则返回已经存在的分片下标集合。存在的就不上传* 如果文件不存在,则返回空集合,表示前端从头开始上传该文件* @param fileName* @return*/@GetMapping("/checkChunk")@ResponseBodypublic AjaxResult checkChunk(@RequestParam("fileName") String fileName) {String uploadDirectory = chunkUploadPath+File.separator+fileName;List<Integer> list = new ArrayList<>();File file = new File(uploadDirectory);// 文件目录不存在if(!file.exists()||file.isFile()) return AjaxResult.success(list);File[] files = file.listFiles();// 文件目录下没有分片文件if(files == null) return AjaxResult.success(list);// 返回存在分片下标集合return AjaxResult.success(Arrays.stream(files).map(item->Integer.valueOf(item.getName().substring(item.getName().lastIndexOf("_")+1))).collect(Collectors.toList()));}// 合并文件分片@PostMapping("/mergeChunks")@ResponseBodypublic AjaxResult mergeChunks(@RequestParam("fileName") String fileName, @RequestParam("totalChunks") int totalChunks) {String uploadDirectory = chunkUploadPath+File.separator+fileName;String mergedFilePath = uploadPath +File.separator+ fileName;try (OutputStream os = new FileOutputStream(mergedFilePath, true)) {for (int i = 0; i < totalChunks; i++) {Path chunkFilePath = Paths.get(uploadDirectory +File.separator+ fileName + "_" + i);Files.copy(chunkFilePath, os);Files.delete(chunkFilePath);}File chunkDir = new File(uploadDirectory);if (chunkDir.exists()) chunkDir.delete();return AjaxResult.success();}catch (Exception e){e.printStackTrace();return AjaxResult.error(e.getMessage());}}}
<!DOCTYPE html>
<html lang="zh" xmlns:th="http://www.thymeleaf.org" xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
<head><th:block th:include="include :: header('分片上传')" />
</head>
<body class="gray-bg">
<div class="container-div" id="chunk-div"><div class="row"><div class="col-sm-12 search-collapse"><form id="formId"><div class="select-list"><ul><li><label>选择文件:</label><input type="file" id="fileInput"/></li><li><a class="btn btn-primary btn-rounded btn-sm" @click="startUpload(1)"><i class="fa fa-upload"></i>&nbsp;开始上传1</a><a class="btn btn-primary btn-rounded btn-sm" @click="startUpload(2)"><i class="fa fa-upload"></i>&nbsp;开始上传2</a></li></ul></div></form></div><div class="col-sm-12" style="padding-left: 0;"><div class="ibox"><div class="ibox-content"><h3>上传进度</h3><ul class="sortable-list connectList agile-list" v-if="uploadMsg"><li v-for="item in uploadMsg" :class="item.status+'-element'">{{item.title}}<div class="agile-detail">{{item.result}}</div></li></ul></div></div></div></div>
</div>
<th:block th:include="include :: footer" />
<script th:inline="javascript">var prefix = ctx + "/kuroshiro/file-upload";new Vue({el: '#chunk-div',data: {// 每次上传大小chunkSize: 100 * 1024 * 1024,uploadMsg:{},startTime:0,},methods: {/*** 开始上传*/startUpload: function(type){const fileInput = document.getElementById('fileInput');const file = fileInput.files[0];if (!file) {alert("请选择文件");return;}const filename = file.name;this.uploadMsg = {};this.startTime = (new Date()).getTime();Vue.set(this.uploadMsg, 'checkMsg', {title:`文件检测`,result: "检测中... ...",status:"info"});if(type == 1){this.checkFile(filename).then(start => {this.uploadMsg['checkMsg'].result = `检测成功:已存在文件,大小为 ${start}`this.uploadFile(file, start,Math.min(start + this.chunkSize, file.size));},err => {this.uploadMsg['checkMsg'].result = `检测失败:${err}`})}if(type == 2){this.checkChunk(filename).then(arr => {this.uploadMsg['checkMsg'].result = `检测成功:已存在文件分片 ${arr.length}`this.uploadChunk(file, arr);},err => {this.uploadMsg['checkMsg'].result = `检测失败:${err}`this.uploadMsg['checkMsg'].status = `info`})}},/*** 检查是否上传过* @param filename* @returns {Promise<unknown>}*/checkFile: function(filename) {return this.$fetch(prefix+`/checkFile?filename=${filename}`);},/*** 开始分片上传* @param file 文件* @param start 开始位置* @param end 结束位置*/uploadFile: function(file, start,end) {if(start < end){const chunk = file.slice(start, end);const formData = new FormData();formData.append('file', chunk);formData.append('start', start);formData.append('fileName', file.name);Vue.set(this.uploadMsg, 'uploadMsg_'+start, {title:`分片 ${start} - ${end} 上传`,result: "上传中... ...",status:"info"});this.$fetch(prefix+'/fragmentUpload', {method: 'POST',body: formData}).then(response => {this.uploadMsg['uploadMsg_'+start].result = `上传成功`;// 递归调用this.uploadFile(file,end,Math.min(end + this.chunkSize, file.size))},err=>{this.uploadMsg['uploadMsg_'+start].result = `上传失败:${err}`;this.uploadMsg['uploadMsg_'+start].status = `danger`;})}else{this.uploadMsg['uploadSuccess'] = {title:`文件已上传`,result:`耗时:`+((new Date()).getTime()-this.startTime),status:"info"};}},/*** 切割文件为多个分片* @param file* @returns {*[]}*/sliceFile: function(file) {const chunks = [];let offset = 0;while (offset < file.size) {const chunk = file.slice(offset, offset + this.chunkSize);chunks.push(chunk);offset += this.chunkSize;}return chunks;},/*** 检查是否上传过* @param filename* @returns {Promise<unknown>}*/checkChunk: function(filename) {return this.$fetch(prefix+`/checkChunk?fileName=${filename}`);},/*** 开始分片上传* @param file 文件* @param exists 存在的分片下标*/uploadChunk: function(file,exists) {const chunkArr = this.sliceFile(file);Promise.all(chunkArr.map(async (chunk, index) => {if (!exists.includes(index)) {const formData = new FormData();formData.append('file', chunk);formData.append('fileName', file.name);formData.append('chunkIndex', index);Vue.set(this.uploadMsg, "upload_" + index, {title: `分片 ${index + 1} 上传`,result: "上传中... ...",status: "info"});return new Promise((resolve, reject) => {this.$fetch(prefix+'/uploadChunk', {method: 'POST',body: formData}).then(res => {resolve(res)this.uploadMsg["upload_"+index].result = "上传成功";},err => {reject(err)this.uploadMsg["upload_"+index].result = err;this.uploadMsg["upload_"+index].status = "danger";});})}})).then(uploadRes=> {this.uploadMsg["uploadSuccess"] = {title:`上传成功`,result: "耗时:"+((new Date()).getTime()-this.startTime),status:"info"};// 合并分片const formData = new FormData();formData.append('fileName', file.name);formData.append('totalChunks', chunkArr.length);Vue.set(this.uploadMsg, 'mergeChunks', {title:`合并分片`,result: "合并中... ...",status:"info"});this.$fetch(prefix + '/mergeChunks', {method: 'POST',body:formData,}).then(mergeRes=>{this.uploadMsg["mergeChunks"].result = "合并成功";},err => {this.uploadMsg["mergeChunks"].result = `合并失败:${err}`;this.uploadMsg["mergeChunks"].status = "danger";});});},$fetch: function(url,requestInit){return new Promise((resolve, reject) => {fetch(url,requestInit).then(response => {if (!response.ok) {throw new Error('请求失败');}return response.json();}).then(data => {if (data.code === 0) {resolve(data.data);} else {reject(data.msg)}}).catch(error => {reject(error)});});},}});</script>
</body>
</html>

相关文章:

文件上传 分片上传

分片上传则是将一个大文件分割成多个小块分别上传&#xff0c;最后再由服务器合并成完整的文件。这种做法的好处是可以并行处理多个小文件&#xff0c;提高上传效率&#xff1b;同时&#xff0c;如果某一部分上传失败&#xff0c;只需要重传这一部分&#xff0c;不影响其他部分…...

【0391】Postgres内核 checkpointer process ① 启动初始化

相关文章: 【0108】checkpointer运行原理(概念篇)(1) 【0278】checkpointer 共享内存(CheckpointerShmem)初始化(3) 文章目录 1. 启动 checkpointer process1.1 初始化 checkpointer PID1.2 注册 signal1.3 初始化 last checkpoint time2. 确认 config 的 shared memo…...

链路追踪SkyWalking

链路追踪 链路追踪作用链路追踪的关键概念链路追踪的工作原理常用链路追踪工具链路追踪的实现步骤链路追踪的典型场景 SkyWalkingSkyWalking 的主要功能SkyWalking 的架构安装 SkyWalking从 SkyWalking 的官方 GitHub 仓库 下载最新版本。配置后端存储SkyWalking使用&#xff0…...

Uniapp判断设备是安卓还是 iOS,并调用不同的方法

在 UniApp 中&#xff0c;可以通过 uni.getSystemInfoSync() 方法来获取设备信息&#xff0c;然后根据系统类型判断当前设备是安卓还是 iOS&#xff0c;并调用不同的方法。 示例代码 export default {onLoad() {this.checkPlatform();},methods: {checkPlatform() {// 获取系…...

计算机网络 (42)远程终端协议TELNET

前言 Telnet&#xff08;Telecommunication Network Protocol&#xff09;是一种网络协议&#xff0c;属于TCP/IP协议族&#xff0c;主要用于提供远程登录服务。 一、概述 Telnet协议是一种远程终端协议&#xff0c;它允许用户通过终端仿真器连接到远程主机&#xff0c;并在远程…...

rtthread学习笔记系列-- 23 环形缓冲块 ringblock

文章目录 23 环形缓冲块 ringblock23.1 初始化23.2 PUT & GET 块23.3 块释放23.4 rt_rbb_blk_queue_get23.5 rt_rbb_blk_alloc https://github.com/wdfk-prog/RT-Thread-Study 23 环形缓冲块 ringblock 环形块状缓冲区简称为&#xff1a;rbb。与传统的环形缓冲区不同的是&…...

HunyuanVideo 文生视频模型实践

HunyuanVideo 文生视频模型实践 flyfish 运行 HunyuanVideo 模型使用文本生成视频的推荐配置&#xff08;batch size 1&#xff09;&#xff1a; 模型分辨率(height/width/frame)峰值显存HunyuanVideo720px1280px129f60GHunyuanVideo544px960px129f45G 本项目适用于使用 N…...

Qt——QTableWidget 限制单元格输入范围的方法(正则表达式输入校验法、自定义代理类MyItemDelegrate)

【系列专栏】:博主结合工作实践输出的,解决实际问题的专栏,朋友们看过来! 《项目案例分享》 《极客DIY开源分享》 《嵌入式通用开发实战》 《C++语言开发基础总结》 《从0到1学习嵌入式Linux开发》...

深度学习论文: CAS-ViT: Convolutional Additive Self-attention Vision Transformers

深度学习论文: CAS-ViT: Convolutional Additive Self-attention Vision Transformers for Efficient Mobile Applications CAS-ViT: Convolutional Additive Self-attention Vision Transformers for Efficient Mobile Applications PDF:https://arxiv.org/pdf/2408.03703 PyT…...

PyCharm文档管理

背景&#xff1a;使用PyCharmgit做文档管理 需求&#xff1a;需要PyCharm自动识别docx/xslx/vsdx等文件类型&#xff0c;并在PyCharm内点击文档时唤起系统内关联应用(如word、excel、visio) 设置步骤&#xff1a; 1、file -》 settings -》file types 2、在Files opened i…...

QNAP 上常用的几款软件

当我们谈到 NAS&#xff08;Network Attached Storage&#xff09;时&#xff0c;QNAP 凭借多年的产品迭代、稳定的硬件性能和不断丰富的软件生态&#xff0c;已成为很多家庭及中小型企业的首选。除了存储本身&#xff0c;QNAP 提供的各种官方软件和应用&#xff0c;也为用户带…...

LabVIEW智能水肥一体灌溉控制系统

本文详细介绍了一种基于LabVIEW的智能水肥一体灌溉控制系统的设计与实现。该系统采用模糊控制策略&#xff0c;能够自动调节土壤湿度和肥液浓度&#xff0c;满足不同作物在不同生长阶段的需求&#xff0c;有效提高水肥利用效率&#xff0c;对现代精准农业具有重要的实践和推广价…...

提问:玩游戏输入法总弹出来咋回事哎

玩游戏时输入法总弹出来的问题&#xff0c;通常与电脑的输入法设置、操作系统配置以及游戏程序的兼容性有关。以下是一些常见的解决方法&#xff1a; 一、修改输入法快捷键 禁用不必要的输入法&#xff1a; 在系统的语言设置中&#xff0c;暂时禁用非活动的输入法&#xff0c;…...

链家房价数据爬虫和机器学习数据可视化预测

完整源码项目包获取→点击文章末尾名片&#xff01;...

【微服务】面试题 5、分布式系统理论:CAP 与 BASE 详解

分布式系统理论&#xff1a;CAP 与 BASE 详解 一、CAP 定理 背景与定义&#xff1a;1998 年由加州大学科学家埃里克布鲁尔提出&#xff0c;分布式系统存在一致性&#xff08;Consistency&#xff09;、可用性&#xff08;Availability&#xff09;、分区容错性&#xff08;Part…...

第十二章:算法与程序设计

文章目录&#xff1a; 一&#xff1a;基本概念 1.算法与程序 1.1 算法 1.2 程序 2.编译预处理 3.面向对象技术 4.程序设计方法 5.SOP标志作业流程 6.工具 6.1 自然语言 6.2 流程图 6.3 N/S图 6.4 伪代码 6.5 计算机语言 二&#xff1a;程序设计 基础 1.常数 …...

RAG技术:是将知识库的文档和问题共同输入到LLM中

RAG技术 RAG技术是将知识库的文档和问题共同输入到LLM中 RAG技术是先从知识库中检索出与问题相关的文档片段,然后将这些检索到的文档片段与问题一起输入到LLM中进行回答。具体过程如下: 文本分块 由于LLM的上下文窗口有限,需要将长文本资料分割成较小的块,以便LLM能够有…...

持续集成 01|Gitee介绍、Pycharm使用Gitee

目录 一、理论 二、 git的简介与安装 三、Gitee 1、注册网易163邮箱 2、注册Gitee账号 3、git和gitee管理代码工作原理 三、PyCharm安装配置Gitee 四、Pycharm使用Gitee插件的五种场景 1、将 Gitee的新仓库 Checkout&#xff08;检出&#xff09;到 Pycharm中 2、推送…...

信息安全、网络安全和数据安全的区别和联系

信息安全、网络安全和数据安全是信息安全领域的三大支柱&#xff0c;它们之间既存在区别又相互联系。以下是对这三者的详细比较&#xff1a; 一.区别 1.信息安全 定义 信息安全是指为数据处理系统建立和采用的技术和管理的安全保护&#xff0c;保护计算机硬件、软件和数据不…...

C++实现设计模式---抽象工厂模式 (Abstract Factory)

抽象工厂模式 (Abstract Factory) 抽象工厂模式 是一种创建型设计模式&#xff0c;提供一个接口&#xff0c;用于创建一组相关或互相依赖的对象&#xff0c;而无需指定它们的具体类。 意图 提供一个创建一组相关对象的接口&#xff0c;而无需指定它们的具体类。解决产品对象之…...

K8S开启/关闭审计日志

K8S默认禁用审计 开启/关闭 k8s 审计日志 默认 Kubernetes 集群不会输出审计日志信息。通过以下配置&#xff0c;可以开启 Kubernetes 的审计日志功能。 准备审计日志的 Policy 文件配置 API 服务器&#xff0c;开启审计日志重启并验证 准备审计日志 Policy 文件 apiVersio…...

css盒子水平垂直居中

目录 1采用flex弹性布局&#xff1a; 2子绝父相margin&#xff1a;负值&#xff1a; 3.子绝父相margin:auto&#xff1a; 4子绝父相transform&#xff1a; 5通过伪元素 6table布局 7grid弹性布局 文字 水平垂直居中链接&#xff1a;文字水平垂直居中-CSDN博客 以下为盒子…...

px、em 和 rem 的区别:深入理解 CSS 中的单位

文章目录 前言一、px - 像素 (Pixel)二、em - 相对父元素字体大小 (Ems)三、rem - 相对于根元素字体大小 (Root Ems)四、综合比较结语 前言 在CSS中&#xff0c;px、em和rem是三种用于定义尺寸&#xff08;如宽度、高度、边距、填充等&#xff09;的长度单位。它们各自有不同的…...

基于STM32设计的粮食仓库(粮仓)环境监测系统

一、前言 1.1 项目开发背景 随着现代农业的发展和粮食储存规模的扩大&#xff0c;粮仓环境的智能化监控需求日益增长。传统的粮仓管理方式通常依赖人工检测和定期巡查&#xff0c;效率低下且容易出现疏漏&#xff0c;无法及时发现潜在问题&#xff0c;可能导致粮食受潮、霉变…...

【后端面试总结】tls中.crt和.key的关系

tls中.crt和.key的关系 引言 在现代网络通信中&#xff0c;特别是基于SSL/TLS协议的加密通信中&#xff0c;.crt和.key文件扮演着至关重要的角色。这两个文件分别代表了数字证书和私钥&#xff0c;是确保通信双方身份认证和数据传输安全性的基石。本文旨在深入探讨TLS中.crt和…...

日拱一卒(20)——leetcode学习记录:大小为 K 且平均值大于等于阈值的子数组数目

一、题目 给定数组&#xff0c;统计数组中长度为k的子数组且该子数组的平均值大于threshold的数量 二、思路 滑动窗思路&#xff0c;计算长度为k的滑动窗的平均值&#xff0c;关键点在于&#xff0c;每滑动一次&#xff0c;只需要去掉头增加尾&#xff0c;而不需要重新全部计…...

项目练习:若依管理系统字典功能-Vue前端部分

文章目录 一、情景说明二、若依Vue相关代码及配置1、utils代码2、components组件3、api接口代码4、Vuex配置5、main.js配置 三、使用方法1、html部分2、js部分 一、情景说明 我们在做web系统的时候&#xff0c;肯定会遇到一些常量选择场景。 比如&#xff0c;性别&#xff1a;…...

apache-skywalking-apm-10.1.0使用

apache-skywalking-apm-10.1.0使用 本文主要介绍如何使用apache-skywalking-apm-10.1.0&#xff0c;同时配合elasticsearch-8.17.0-windows-x86_64来作为存储 es持久化数据使用。 步骤如下&#xff1a; 一、下载elasticsearch-8.17.0-windows-x86_64 1、下载ES(elasticsear…...

计算机视觉算法实战——视频分析(Video Analysis)

✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连 ✨ ✨个人主页欢迎您的访问 ✨期待您的三连✨ ​​​​​​ ​​​​​​​​​​​​ ​​​​​ 视频分析是计算机视觉中的一个重要领域&#xff0c;旨在从视频数据中提取有用的信息&…...

全网首发:编译libssh,产生类似undefined reference to `EVP_aes_256_ctr@OPENSSL_1_1_0‘的大量错误

具体错误 前面和后面的&#xff1a; /opt/linux/x86-arm/aarch64-mix210-linux/host_bin/../lib/gcc/aarch64-linux-gnu/7.3.0/../../../../aarch64-linux-gnu/bin/ld: warning: libcrypto.so.1.1, needed by ../lib/libssh.so.4.10.1, not found (try using -rpath or -rpat…...