【分块解决大文件上传的最佳实践】
前言
前几天看了一篇关于大文件上传分块实现的博客,代码实现过于复杂且冗长,而且没有进行外网上传的测试。因此,我决定自己动手实现一个大文件上传,并进行优化。
实现思路
在许多应用中,大文件上传是常见的需求,比如上传视频、图片、日志文件等。常见的上传方法往往会受到文件大小和网络环境的限制,导致上传失败或上传速度慢。例如我们通过spring boot直接上传1GB的大文件,如果直接进行上传那么会特别的慢,同时也很大可能会失败。特别是如果1GB的大文件快上传成功了,出现网络波动。那么要进行重新上传。这显然不是我们需要的。
为了提高上传的效率和稳定性,我们可以采用分块上传的方式。
实现原理
(1)将大文件分成多个小块:通过设定每个块的大小(例如,10MB),将大文件拆分为多个小文件块。
(2)逐块上传:每次上传一个文件块,直到文件上传完毕。
(3)服务器接收并合并文件块:服务器端接收每个文件块并在所有块上传完成后合并成一个完整的文件。
原理很简单,就是将一个大文件分成多个小块逐次上传,最后合并。这个过程与数据导出类似:如果要导出数百万条数据,通常也会采用分页的方式,每次查询一部分数据,分批导出。
上传文件信息
我看到的博主将文件信息与分块上传放在同一个接口中,但这样每次上传都会传递重复的文件数据。为避免这种情况,我将文件的基础信息单独提取出来,只在第一次上传时保存,以减少冗余数据传输。
@PostMapping(value = "saveFileMetadata")public ResponseEntity<String> saveFileMetadata(@RequestBody FileMetadata fileMetadata) {log.info("fileMetadata文件名:{}", fileMetadata.getFileName());fileMetadata.setFileName(FileChunkUtil.generateFileName(fileMetadata.getFileName()));fileMetadata.setChunksUploaded(new boolean[fileMetadata.getChunkCount()]);FileMetadataRepository.put(fileMetadata.getFileName(), fileMetadata);log.info("返回fileMetadata文件名:{}", fileMetadata.getFileName());// 设置响应的Content-Type为UTF-8编码return ResponseEntity.ok().header("Content-Type", "application/json;charset=UTF-8").body(fileMetadata.getFileName());}
chunksUploaded 文件上传状态,是将每个分块的上传状态做个标识。分块上传成功,更新该分块索引标识。只有所有分块都上传成功,才能进行分块合并。
public class FileMetadata {/*** 文件的大小(单位:字节)*/private Long fileSize;/*** 文件名*/private String fileName;/*** 文件的总块数*/private Integer chunkCount;/*** 文件块的上传状态* 每个元素表示对应块是否上传完成*/private boolean[] chunksUploaded;
}
分块上传
这边分块上传将文件分割,然后再上传。
客户端实现
@Testpublic void fileUploadChunkTest() throws IOException {final StopWatch stopWatch = new StopWatch("fileUploadTest");stopWatch.start("saveFileMetadata");File file = new File("C:\\Newand\\file\\上传测试文件\\1GB文件.zip"); // 输入文件路径int chunkSizeInBytes = 10 * 1024 * 1024; // 每个分片的大小为10MB// 获取文件大小long fileSize = file.length();final RestTemplate restTemplate = new RestTemplate();final String resultFileName = saveFileMetadata(file, chunkSizeInBytes, fileSize);log.info(resultFileName);stopWatch.stop();stopWatch.start("chunk");// 打开输入流try (FileInputStream fis = new FileInputStream(file)) {byte[] buffer = new byte[chunkSizeInBytes];int chunkNumber = 0;// 循环读取文件并创建分片while (fis.read(buffer) != -1) {String chunkFileName = file.getName() + ".part" + chunkNumber;File chunkFile = new File(file.getParent(), chunkFileName);// 将当前分片写入磁盘try (FileOutputStream fos = new FileOutputStream(chunkFile)) {fos.write(buffer);}// 上传MultiValueMap<String, Object> body = new LinkedMultiValueMap<>();body.add("file", new FileSystemResource(chunkFile));body.add("chunkNumber", chunkNumber);body.add("filename", resultFileName);HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.MULTIPART_FORM_DATA);HttpEntity<MultiValueMap<String, Object>> requestEntity = new HttpEntity<>(body, headers);String serverUrl = "http://localhost:8099/file/chunk";ResponseEntity<String> response = restTemplate.postForEntity(serverUrl, requestEntity, String.class);System.out.println("Response code: " + response.getStatusCode() + " Response body: " + response.getBody());// 清空缓冲区,以便下次读取buffer = new byte[(int) chunkSizeInBytes];chunkNumber++;}}stopWatch.stop();stopWatch.start("merge");final String result2 = restTemplate.getForObject("http://localhost:8099/file/merge?filename=" + resultFileName, String.class);System.out.println("合并完成:" + result2);stopWatch.stop();System.out.println("用时:" + stopWatch.prettyPrint(TimeUnit.MILLISECONDS));}private String saveFileMetadata(File file, int chunkSizeInBytes, long fileSize) {final long chunkCount = (fileSize + chunkSizeInBytes - 1) / chunkSizeInBytes;final JSONObject jsonObject = new JSONObject();jsonObject.put("fileSize", fileSize);jsonObject.put("fileName", file.getName());jsonObject.put("chunkCount", chunkCount);final String resultFileName = new RestTemplate().postForObject("http://localhost:8099/file/saveFileMetadata", jsonObject, String.class);return resultFileName;}
服务端实现
控制层
package com.ji.helper.controller.file;import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;/*** 分片上传* @author jisl on 2025/1/24 14:40**/
@RestController
@RequestMapping("/file")
@Slf4j
public class FileController {@PostMapping(value = "saveFileMetadata")public ResponseEntity<String> saveFileMetadata(@RequestBody FileMetadata fileMetadata) {log.info("fileMetadata文件名:{}", fileMetadata.getFileName());fileMetadata.setFileName(FileChunkUtil.generateFileName(fileMetadata.getFileName()));fileMetadata.setChunksUploaded(new boolean[fileMetadata.getChunkCount()]);FileMetadataRepository.put(fileMetadata.getFileName(), fileMetadata);log.info("返回fileMetadata文件名:{}", fileMetadata.getFileName());// 设置响应的Content-Type为UTF-8编码return ResponseEntity.ok().header("Content-Type", "application/json;charset=UTF-8").body(fileMetadata.getFileName());}/*** 分块上传文件** @param chunk 文件块信息* @return 响应*/@PostMapping(value = "/chunk")public ResponseEntity<String> chunk(FileUploadChunk chunk) {log.info("分块文件:" + chunk.getFilename());FileChunkUtil.saveFile(chunk);return ResponseEntity.ok("File Chunk Upload Success");}/*** 文件合并** @param filename 文件名* @return 响应*/@GetMapping(value = "merge")public ResponseEntity<Void> merge(@RequestParam String filename) {log.info("merge文件名:{}", filename);FileChunkUtil.merge(filename);return ResponseEntity.ok().build();}// /**
// * 获取指定文件
// *
// * @param filename 文件名称
// * @return 文件
// */
// @GetMapping("/files/{filename:.+}")
// public ResponseEntity<String> getFile(@PathVariable("filename") String filename) {
// return ResponseEntity.ok().header(HttpHeaders.CONTENT_DISPOSITION,
// "attachment; filename=\"" + filename + "\"").body("");
// }
}
文件工具类
package com.ji.helper.controller.file;import cn.hutool.core.date.DateTime;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.ArrayUtil;
import lombok.extern.slf4j.Slf4j;import java.io.BufferedOutputStream;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;/*** @author jisl on 2025/1/24 14:41**/
@Slf4j
public class FileChunkUtil {public static final String LOCAL_ROOT_DICT = "C:/Newand/file/data";public static final String ROOT_DICT = "/data/fileUpload";public static void saveFile(FileUploadChunk chunk) {final String tmpFilePath = getTmpFilePath(chunk.getFilename(), chunk.getChunkNumber());log.info("目标文件路径:{}", tmpFilePath);final FileMetadata fileMetadata = FileMetadataRepository.get(chunk.getFilename());try {// 创建目标文件对象File dest = new File(Paths.get(tmpFilePath).toString());checkAndCreateParentDir(dest);chunk.getFile().transferTo(dest);log.info("保存文件执行完毕");} catch (IOException e) {log.error("保存文件失败:", e);throw new RuntimeException("保存文件失败:" + e.getMessage());}
// 设置该块文件 为已上传fileMetadata.getChunksUploaded()[chunk.getChunkNumber()] = true;}private static void checkAndCreateParentDir(File dest) {// 检查父目录是否存在,如果不存在则创建File parentDir = dest.getParentFile();if (parentDir != null && !parentDir.exists()) {parentDir.mkdirs(); // 创建父目录}}public static String generateFileName(String fileName) {return new DateTime().toString("yyyyMMdd") + File.separator + fileName;}public static void merge(String filename) {final FileMetadata fileMetadata = FileMetadataRepository.get(filename);Assert.isFalse(ArrayUtil.contains(fileMetadata.getChunksUploaded(), false));File mergeFile = new File(getRootDict(), fileMetadata.getFileName());if (mergeFile.exists()) {mergeFile.delete();}// 检查父目录是否存在,如果不存在则创建checkAndCreateParentDir(mergeFile);try (BufferedOutputStream outputStream = new BufferedOutputStream(Files.newOutputStream(mergeFile.toPath()))) {for (int i = 0; i < fileMetadata.getChunkCount(); i++) {File chunkFile = new File(getTmpFilePath(fileMetadata.getFileName(), i));Files.copy(chunkFile.toPath(), outputStream);}} catch (IOException e) {log.info("File [{}] merge failed", filename, e);throw new RuntimeException(e);}}private static String getTmpFilePath(String fileName, int chunkNumber) {return getRootDict() + File.separator + "tmp" + File.separator + fileName + ".part" + chunkNumber;}private static boolean isWin() {String os = System.getProperty("os.name");return os.toLowerCase().startsWith("win");}private static String getRootDict() {return isWin() ? LOCAL_ROOT_DICT : ROOT_DICT;}}
文件信息类
package com.ji.helper.controller.file;import lombok.Data;@Data
public class FileMetadata {/*** 文件的大小(单位:字节)*/private Long fileSize;/*** 文件名*/private String fileName;/*** 文件的总块数*/private Integer chunkCount;/*** 文件块的上传状态* 每个元素表示对应块是否上传完成*/private boolean[] chunksUploaded;
}
文件类
package com.ji.helper.controller.file;import lombok.Data;
import org.springframework.web.multipart.MultipartFile;@Data
public class FileUploadChunk {/*** 当前文件块,从0开始*/private Integer chunkNumber;/*** 当前块的实际大小(单位:字节)*/private Long currentChunkSize;/*** 文件名*/private String filename;/*** 当前分块的文件内容*/private MultipartFile file;// getters and setters
}
文件信息保存仓库
package com.ji.helper.controller.file;import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @author jisl on 2025/1/23 10:50*/
public class FileMetadataRepository {private static final Map<String, FileMetadata> FILE_METADATA_CACHE = new ConcurrentHashMap<>(16);private FileMetadataRepository() {}public static FileMetadata put(String fileName, FileMetadata channel) {return FILE_METADATA_CACHE.put(fileName, channel);}public static FileMetadata get(String fileName) {return FILE_METADATA_CACHE.get(fileName);}public static Boolean containsKey(String fileName) {return FILE_METADATA_CACHE.containsKey(fileName);}public static void remove(String fileName) {FILE_METADATA_CACHE.remove(fileName);}public static int size() {return FILE_METADATA_CACHE.size();}public static Map<String, FileMetadata> getCache() {return FILE_METADATA_CACHE;}}
结语
分块上传,这边比较适合大文件和网络环境不稳定情景下。这边我尝试在内网环境和外网环境传输1GB大小文件,内网环境下直接上传更快。而在外网环境下则是分块上传明显更快。这边需要根据具体的问题场景,使用合适的解决方案才是最优解。
相关文章:
【分块解决大文件上传的最佳实践】
前言 前几天看了一篇关于大文件上传分块实现的博客,代码实现过于复杂且冗长,而且没有进行外网上传的测试。因此,我决定自己动手实现一个大文件上传,并进行优化。 实现思路 在许多应用中,大文件上传是常见的需求&…...
机器学习中的关键概念:通过SKlearn的MNIST实验深入理解
欢迎来到我的主页:【Echo-Nie】 本篇文章收录于专栏【机器学习】 1 sklearn相关介绍 Scikit-learn 是一个广泛使用的开源机器学习库,提供了简单而高效的数据挖掘和数据分析工具。它建立在 NumPy、SciPy 和 matplotlib 等科学计算库之上,支持…...
【Elasticsearch】post_filter
post_filter是 Elasticsearch 中的一种后置过滤机制,用于在查询执行完成后对结果进行过滤。以下是关于post_filter的详细介绍: 工作原理 • 查询后过滤:post_filter在查询执行完毕后对返回的文档集进行过滤。这意味着所有与查询匹配的文档都…...
Git基础
目录 一、Git介绍二、Git下载与配置1、下载安装Git2、Git配置2.1 注册码云账号2.2 Git配置 三、Git开发流程1、相关代码2、上述代码执行截图示例 四、Git提交&撤销五、Git资料 一、Git介绍 Git是一种分布式版本控制系统,广泛用于软件开发项目的版本管理。它由L…...
深度学习系列--02.损失函数
一.定义 损失函数(Loss Function)是机器学习和深度学习中用于衡量模型预测结果与真实标签之间差异的函数,它在模型训练和评估过程中起着至关重要的作用 二.作用 1.指导模型训练 提供优化方向:在训练模型时,我们的目…...
如何在自己mac电脑上私有化部署deep seek
在 Mac 电脑上私有化部署 DeepSeek 的步骤如下: 1. 环境准备 安装 Homebrew(如果尚未安装): Homebrew 是 macOS 上的包管理工具,用于安装依赖。 /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com…...
攻防世界 fileclude
代码审计 WRONG WAY! <?php include("flag.php"); highlight_file(__FILE__);//高亮显示文件的源代码 if(isset($_GET["file1"]) && isset($_GET["file2"]))//检查file1和file2参数是否存在 {$file1 $_GET["file1"];$fi…...
Ubuntu24登录PostgreSql数据库的一般方法
命令格式如 psql -U user -d db 或者 sudo psql -U user -d db 修改配置 /etc/postgresql/16/main/postgresql.conf 改成md5,然后重新启动pgsql sudo systemctl restart postgresql...
3.5 Go(特殊函数)
目录 一、匿名函数 1、匿名函数的特点: 2、匿名函数代码示例 2、匿名函数的类型 二、递归函数 1. 递推公式版本 2. 循环改递归 三、嵌套函数 1、嵌套函数用途 2、代码示例 3、作用域 & 变量生存周期 四、闭包 1、闭包使用场景 2、代码示例 五、De…...
设计模式学习(三)
行为模式 职责链模式(Chain of Responsibility Pattern) 定义 它允许多个对象有机会处理请求,从而避免请求的发送者与接收者之间的耦合。职责链模式将这些对象连成一条链,并沿着这条链传递请求,直到有对象处理它为止…...
挑战项目 --- 微服务编程测评系统(在线OJ系统)
一、前言 1.为什么要做项目 面试官要问项目,考察你到底是理论派还是实战派? 1.希望从你的项目中看到你的真实能力和对知识的灵活运用。 2.展示你在面对问题和需求时的思考方式及解决问题的能力。 3.面试官会就你项目提出一些问题,或扩展需求…...
堆(Heap)的原理与C++实现
1. 什么是堆? 堆(Heap)是一种特殊的树形数据结构,通常用于实现优先队列。堆可以分为两种类型: 最大堆(Max Heap):每个节点的值都大于或等于其子节点的值。最小堆(Min H…...
(10) 如何获取 linux 系统上的 TCP 、 UDP 套接字的收发缓存的默认大小,以及代码范例
(1) 先介绍下后面的代码里要用到的基础函数: 以及: (2) 接着给出现代版的 读写 socket 参数的系统函数 : 以及: (3) 给出 一言的 范例代码,获取…...
linux 进程补充
环境变量 基本概念 环境变量(environment variables)一般是指在操作系统中用来指定操作系统运行环境的一些参数 如:我们在编写C/C代码的时候,在链接的时候,从来不知道我们的所链接的动态静态库在哪 里,但是照样可以链接成功&#…...
通过docker安装部署deepseek以及python实现
前提条件 Docker 安装:确保你的系统已经安装并正确配置了 Docker。可以通过运行 docker --version 来验证 Docker 是否安装成功。 网络环境:保证设备有稳定的网络连接,以便拉取 Docker 镜像和模型文件。 步骤一:拉取 Ollama Docker 镜像 Ollama 可以帮助我们更方便地管理…...
vim-plug的自动安装与基本使用介绍
vim-plug介绍 Vim-plug 是一个轻量级的 Vim 插件管理器,它允许你轻松地管理 Vim 插件的安装、更新和卸载。相较于其他插件管理器,vim-plug 的优点是简单易用,速度较快,而且支持懒加载插件(即按需加载) 自动…...
Python 自学秘籍:开启编程之旅,人生苦短,我用python。
从2009年,用了几次python后就放弃了,一直用的php,现在人工智能时代,完全没php什么事情。必须搞python了,虽然已经40多岁了。死磕python了。让滔滔陪着你一起学python 吧。 开启新世界 在当今人工智能化的时代ÿ…...
DRGDIP 2.0时代下基于PostgreSQL的成本管理实践与探索(上)
一、引言 1.1 研究背景与意义 在医疗领域的改革进程中, DRG/DIP 2.0 时代,医院成本管理的重要性愈发凸显。新的医保支付方式下,医院的收入不再单纯取决于医疗服务项目的数量,而是与病种的分组、费用标准以及成本控制紧密相关。这…...
swift 专题三 swift 规范一
一、Swift编码命名规范 对类、结构体、枚举和协议等类型的命名应该采用大驼峰法,如 SplitViewController。 文件名采用大驼峰法,如BlockOperation.swift。 对于扩展文件,有时扩展定义在一个独立的文件中,用“原始类型名 扩展名…...
Vue的状态管理:用响应式 API 做简单状态管理、状态管理库(Pinia )
文章目录 引言单向数据流多个组件共享一个共同的状态I 用响应式 API 做简单状态管理使用 reactive()创建一个在多个组件实例间共享的响应式对象使用ref()返回一个全局状态II 状态管理库Pinia枚举状态管理引言 单向数据流 每一个 Vue 组件实例都在“管理”它自己的响应式状态了…...
排序算法--希尔排序
希尔排序是插入排序的改进版本,适合中等规模数据排序,性能优于简单插入排序。 // 希尔排序函数 void shellSort(int arr[], int n) {// 初始间隔(gap)为数组长度的一半,逐步缩小for (int gap n / 2; gap > 0; gap …...
HAL库 Systick定时器 基于STM32F103EZT6 野火霸道,可做参考
目录 1.时钟选择(这里选择高速外部时钟) 编辑 2.调试模式和时基源选择: 3.LED的GPIO配置 这里用板子的红灯PB5 4.工程配置 5.1ms的systick中断实现led闪烁 源码: 6.修改systick的中断频率 7.systick定时原理 SysTick 定时器的工作原理 中断触发机制 HAL_SYSTICK_Co…...
ESXI虚拟机中部署docker会降低服务器性能
在 8 核 16GB 的 ESXi 虚拟机中部署 Docker 的性能影响分析 在 ESXi 虚拟机中运行 Docker 容器时,性能影响主要来自以下几个方面: 虚拟化开销:ESXi 虚拟化层和 Docker 容器化层的叠加。资源竞争:虚拟机与容器之间对 CPU、内存、…...
前端 | JavaScript中的reduce方法
1. 什么是reduce reduce 方法是 JavaScript 中数组的重要方法之一,用于对数组中的元素进行累积计算。它接收一个回调函数作为参数,并返回一个最终计算结果。reduce 在许多场景下都非常有用,比如求和、数组扁平化、对象计数、数据转换等。 2…...
基于联合概率密度与深度优化的反潜航空深弹命中概率模型研究摘要
前言:项目题材来自数学建模2024年的D题,文章内容为笔者和队友原创,提供一个思路。 摘要 随着现代军事技术的发展,深水炸弹在特定场景下的反潜作战效能日益凸显,如何最大化的发挥深弹威力也成为重要研究课题。本文针对评估深弹投掷落点对命中潜艇概率的影响进行分析,综合利…...
将OneDrive上的文件定期备份到移动硬盘
背景: 我在oneDrive上存了很多文件,分布在多个文件夹中,也有套了好几层文件夹的情况。我希望每隔一段时间,将oneDrive上的所有文件向移动硬盘上拷贝一份,但是我只想将距离上一次向移动硬盘拷贝的文件相比,发…...
01vue3实战-----前言
01vue3实战-----前言 1.大前端时代2.技术栈3.项目大致展示4.创建Vue项目4.1Vue CLI4.2create-vue 5.参考资料 1.大前端时代 前端移动端iOS/android开发桌面端 window/mac 常用的electron框架来开发其它平台:穿戴设备、车载系统(智能汽车)、VR、AR…web3方向 2.技术栈 开发工…...
SQL 秒变三线表 sql导出三线表
🎯SQL 秒变三线表,校园小助手超神啦 宝子们,搞数据分析、写论文的时候,从 SQL 里导出数据做成三线表是不是特别让人头疼😩 手动调整格式,不仅繁琐,还容易出错,分分钟把人逼疯&#…...
C_位运算符及其在单片机寄存器的操作
C语言的位运算符用于直接操作二进制位,本篇简单结束各个位运算符的作业及其在操作寄存器的应用场景。 一、位运算符的简单说明 1、按位与运算符(&) 功能:按位与运算符对两个操作数的每一位执行与操作。如果两个对应的二进制…...
Rust错误处理:从灭火器到核按钮的生存指南
开篇:错误处理的生存哲学 在Rust的平行宇宙里,错误分为两种人格: panic! → 核按钮💣(不可恢复,全系统警报)Result → 灭火器🧯(可控制,局部处理࿰…...
YK人工智能(六)——万字长文学会基于Torch模型网络可视化
1. 可视化网络结构 随着深度神经网络做的的发展,网络的结构越来越复杂,我们也很难确定每一层的输入结构,输出结构以及参数等信息,这样导致我们很难在短时间内完成debug。因此掌握一个可以用来可视化网络结构的工具是十分有必要的…...
对象的实例化、内存布局与访问定位
一、创建对象的方式 二、创建对象的步骤: 一、判断对象对应的类是否加载、链接、初始化: 虚拟机遇到一条new指令,首先去检查这个指令的参数能否在Metaspace的常量池中定位到一个类的符号引用,并且检查这个符号引用代表的类是否已经被加载、解析和初始化…...
Java面试题2025-并发编程进阶(线程池和并发容器类)
线程池 一、什么是线程池 为什么要使用线程池 在开发中,为了提升效率的操作,我们需要将一些业务采用多线程的方式去执行。 比如有一个比较大的任务,可以将任务分成几块,分别交给几个线程去执行,最终做一个汇总就可…...
Vue Router 客户端路由解决方案:axios 响应拦截(跳转到登录页面)
文章目录 引言客户端路由 vs. 服务端路由简单的路由案例术语I Vue Router 提供的组件RouterLinkRouterViewII 创建路由器实例调用 createRouter() 函数创建路由选项III 注册路由器插件通过调用 use() 来完成注册路由器插件的职责对于组合式 API,Vue Router 给我们提供了一些组…...
Redis的通用命令
⭐️前言⭐️ 本文主要介绍Redis的通用命令 🍉欢迎点赞 👍 收藏 ⭐留言评论 🍉博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言 🍉博客中涉及源码及博主日常练习代码均已上传GitHub 📍内容导…...
[Python人工智能] 四十九.PyTorch入门 (4)利用基础模块构建神经网络并实现分类预测
从本专栏开始,作者正式研究Python深度学习、神经网络及人工智能相关知识。前文讲解PyTorch构建回归神经网络。这篇文章将介绍如何利用PyTorch构建神经网络实现分类预测,其是使用基础模块构建。前面我们的Python人工智能主要以TensorFlow和Keras为主,而现在最主流的深度学习框…...
SpringBoot使用 easy-captcha 实现验证码登录功能
文章目录 一、 环境准备1. 解决思路2. 接口文档3. redis下载 二、后端实现1. 引入依赖2. 添加配置3. 后端代码实现4. 前端代码实现 在前后端分离的项目中,登录功能是必不可少的。为了提高安全性,通常会加入验证码验证。 easy-captcha 是一个简单易用的验…...
RabbitMQ 从入门到精通:从工作模式到集群部署实战(一)
#作者:闫乾苓 文章目录 RabbitMQ简介RabbitMQ与VMware的关系架构工作流程RabbitMQ 队列工作模式及适用场景简单队列模式(Simple Queue)工作队列模式(Work Queue)发布/订阅模式(Publish/Subscribeÿ…...
Unity中的虚拟相机(Cinemachine)
Unity Cinemachine详解 什么是Cinemachine Cinemachine是Unity官方推出的智能相机系统,它提供了一套完整的工具来创建复杂的相机运动和行为,而无需编写大量代码。它能够大大简化相机管理,提高游戏开发效率。 Cinemachine的主要组件 1. Vi…...
响应式编程_04Spring 5 中的响应式编程技术栈_WebFlux 和 Spring Data Reactive
文章目录 概述响应式Web框架Spring WebFlux响应式数据访问Spring Data Reactive 概述 https://spring.io/reactive 2017 年,Spring 发布了新版本 Spring 5, Spring 5 引入了很多核心功能,这其中重要的就是全面拥抱了响应式编程的设计思想和实…...
网络设备的安全加固
设备的安全始终是信息网络安全的一个重要方面,攻击者往往通过控制网络中设备来破坏系统和信息,或扩大已有的破坏。网络设备包括主机(服务器、工作站、PC)和网络设施(交换机、路由器等)。 一般说来ÿ…...
OpenCV:特征检测总结
目录 一、什么是特征检测? 二、OpenCV 中的常见特征检测方法 1. Harris 角点检测 2. Shi-Tomasi 角点检测 3. Canny 边缘检测 4. SIFT(尺度不变特征变换) 5. ORB 三、特征检测的应用场景 1. 图像匹配 2. 运动检测 3. 自动驾驶 4.…...
Java高频面试之SE-17
hello啊,各位观众姥爷们!!!本牛马baby今天又来了!哈哈哈哈哈嗝🐶 Java缓冲区溢出,如何解决? 在 Java 中,缓冲区溢出 (Buffer Overflow) 虽然不是像 C/C 中那样直接可见…...
移动机器人规划控制入门与实践:基于navigation2 学习笔记(一)
课程实践: (1)手写A*代码并且调试,总结优缺点 (2)基于Gazebo仿真,完成给定机器人在给定地图中的导航调试 (3)使用Groot设计自己的导航行为树 掌握一门技术 规划控制概述 常见移动机器人...
每日一题洛谷P5721 【深基4.例6】数字直角三角形c++
#include<iostream> using namespace std; int main() {int n;cin >> n;int t 1;for (int i 0; i < n; i) {for (int j 0; j < n - i; j) {printf("%02d",t);t;}cout << endl;}return 0; }...
RTMP 和 WebRTC
WebRTC(Web Real-Time Communication)和 RTMP(Real-Time Messaging Protocol)是两种完全不同的流媒体协议,设计目标、协议栈、交互流程和应用场景均有显著差异。以下是两者的详细对比,涵盖协议字段、交互流程及核心设计思想。 一、协议栈与设计目标对比 特性RTMPWebRTC传…...
数据库技术基础
1 数据库系统概述 1.1 数据库的4个概念 (1)数据(信息) 数据:指已记录或可获取的事实,是数据库存储的最小单元。除文本、数字外,还有图形、图像、声音等。 数据由于能为用户利用才被记录和保…...
如何获取sql数据中时间的月份、年份(类型为date)
可用自带的函数month来实现 如: 创建表及插入数据: create table test (id int,begindate datetime) insert into test values (1,2015-01-01) insert into test values (2,2015-02-01) 执行sql语句,获取月份: select MONTH(begindate)…...
每日Attention学习18——Grouped Attention Gate
模块出处 [ICLR 25 Submission] [link] UltraLightUNet: Rethinking U-shaped Network with Multi-kernel Lightweight Convolutions for Medical Image Segmentation 模块名称 Grouped Attention Gate (GAG) 模块作用 轻量特征融合 模块结构 模块特点 特征融合前使用Group…...
分析用户请求K8S里ingress-nginx提供的ingress流量路径
前言 本文是个人的小小见解,欢迎大佬指出我文章的问题,一起讨论进步~ 我个人的疑问点 进入的流量是如何自动判断进入iptables的四表?k8s nodeport模式的原理? 一 本机环境介绍 节点名节点IPK8S版本CNI插件Master192.168.44.1…...