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

spring mvc异步请求 sse 大文件下载 断点续传下载Range

学习连接

异步Servlet3.0

Spring Boot 处理异步请求(DeferredResult 基础案例、DeferredResult 超时案例、DeferredResult 扩展案例、DeferredResult 方法汇总)

spring.io mvc Asynchronous Requests 官网文档

spring.io webflux&webclient官网文档

SpringBoot+vue 大文件分片下载
SpringBoot+vue文件上传&下载&预览&大文件分片上传&文件上传进度
spring mvc异步请求 & sse & 大文件下载 & 断点续传下载

文章目录

    • 学习连接
    • springmvc异步请求
      • DeferredResult
        • 示例
      • Callable
        • 示例
      • 异步处理
        • springmvc异步处理流程
        • Exception Handling异常处理
        • Interception拦截
        • 与WebFlux相比
      • HTTP流(ResponseBodyEmitter )
        • 示例
      • sse(SseEmitter)
      • 原数据直传
        • 示例(文件下载)
          • 后端代码
          • 前端代码
        • 示例(大文件下载)
          • 后端代码
          • 前端代码1
          • 前端代码2
          • 前端代码3
        • 断点续传概念
          • 概述
          • Range
          • Content-Range
      • 响应式类型
      • 断开连接
      • 配置
        • Servlet容器
        • Spring MVC

springmvc异步请求

Spring MVC广泛接入Servlet 3.0异步请求处理:

  • DeferredResult和Callable返回值,并为单个异步返回值提供基本支持。
  • 控制器可以流式传输多个值,包括SSE和原始数据。
  • 控制器可以使用反应式客户端并返回响应处理的反应式类型。

DeferredResult

一旦在Servlet容器中启用了异步请求处理特征,控制器方法就可以用DeferredResult包装任何支持的控制器方法返回值,如下例所示:

@GetMapping("/quotes")
@ResponseBody
public DeferredResult<String> quotes() {DeferredResult<String> deferredResult = new DeferredResult<String>();// Save the deferredResult somewhere..return deferredResult;
}// From some other thread...
deferredResult.setResult(result);

控制器可以从不同的线程异步地产生返回值——例如,响应外部事件(JMS消息)、定时任务或其他事件。

示例
@Slf4j
@RestController
@RequestMapping("/async")
public class TestController {@RequestMapping("testDeferred")public DeferredResult<String> testDeferred(Long timeoutValue) {log.info("testDeferred");// 1、timeoutValue为null,表示不超时.// 2、如果超时了,将返回这里默认值timeoutResult(不会影响给deferredResult设置值的线程)DeferredResult<String> deferredResult = new DeferredResult<>(timeoutValue,()->{return "timeoutValue";});new Thread(()->{try {log.info("异步处理 start");TimeUnit.SECONDS.sleep(5);// 此方法可以检测是否已超时,// 也就是即使超时,当前new的线程也会继续执行,下面setResult方法也会执行,只是不会把设置的值给前端,因为超时的默认值已经给了。log.info("是否超时: {}", deferredResult.isSetOrExpired());} catch (Exception e) {log.info("异步处理异常: {}", e);deferredResult.setErrorResult("error~");return;}deferredResult.setResult("testDeferred~");log.info("异步处理 end");}).start();log.info("testDeferred end");return deferredResult;}}

Callable

控制器可以使用java.util.concurrent.Callable包装任何支持的返回值,如下例所示:

@ResponseBody
@RequestMapping("test11")
public Callable<String> processUpload() {return new Callable<String>() {public String call() throws Exception {log.info("call");return "data";}};
}

该callable会交给配置的taskExecutor执行。

示例
@RequestMapping("testCallable")
public Callable<String> testCallable(Integer timeout) {log.info("testCallable start");Callable<String> callable = new Callable<String>() {@Overridepublic String call() throws Exception {TimeUnit.SECONDS.sleep(timeout);// task-1线程执行的log.info("testCallable 异步执行");return "call result";}};log.info("testCallable end");return callable;}

异步处理

springmvc异步处理流程

以下是Servlet异步请求处理的非常简洁的概述:

  • 可以通过调用request.startAsync()将ServletRequest置于异步模式。这样做的主要效果是Servlet(以及任何过滤器)可以退出,但响应保持打开状态,以便稍后完成处理。
  • 对request.startAsync()的调用返回AsyncContext,您可以使用它来进一步控制异步处理。例如,它提供了dispatch方法,类似于Servlet API的转发,只是它允许应用程序在Servlet容器线程上继续请求处理。
  • ServletRequest可以访问到对当前的DispatcherType,您可以使用它来区分处理初始请求、异步调度、转发和其他类型。

DeferredResult处理流程如下:

  • 控制器返回一个DeferredResult并将其保存在某个可以访问的内存的队列或列表中。
  • Spring MVC调用request.startAsync()。
  • 同时,DispatcherServlet和所有配置的过滤器退出请求处理线程,但响应保持打开状态。
  • 应用程序从某个线程设置DeferredResult,Spring MVC将请求分派(dispatcher)回Servlet容器。
  • 再次调用DispatcherServlet,并使用异步生成的返回值恢复处理

Callable处理流程如下:

  • 控制器返回一个Callable。
  • Spring MVC调用request.startAsync()并将Callable提交给TaskExecutor以在单独的线程中进行处理。
  • 同时,DispatcherServlet和所有过滤器退出Servlet容器线程,但响应保持打开状态。
  • 最终Callable产生一个结果,Spring MVC将请求分派回Servlet容器以完成处理。
  • 再次调用DispatcherServlet,并使用来自Callable的异步生成的返回值恢复处理。

有关进一步的背景和上下文,您还可以阅读在Spring MVC 3.2中介绍异步请求处理支持的博客文章。

(经过查看DeferredResultMethodReturnValueHandler,发现还可以返回ListenableFuture、CompletionStage类型的返回值。
源码的重点是在:WebAsyncManager的创建和使用、StandardServletAsyncWebRequest对异步请求的封装、RequestMappingHandlerAdapter#invokeHandlerMethod对分发之后的处理)

Exception Handling异常处理

当您使用DeferredResult时,您可以选择是调用setResult还是setErrorResult并带有异常。在这两种情况下,Spring MVC都会将请求分派回Servlet容器以完成处理。然后将其视为控制器方法返回给定值或产生给定异常。然后异常通过常规异常处理机制(例如,调用@ExceptionHandler方法)。

当您使用Callable时,会出现类似的处理逻辑,主要区别在于结果是从Callable返回的,或者由Callable引发异常。

Interception拦截

HandlerInterceptor实例可以是AsyncHandlerInterceptor类型,以在初始请求启动异步处时接收afterConcurrentHandlingStarted回调(而不是postHandle和afterCompletion)。

HandlerInterceptor实现还可以注册CallableProcessingInterceptor或DeferredResultProcessingInterceptor,以便更深入地与异步请求的生命周期集成(例如,处理超时事件)。AsyncHandlerInterceptor了解更多详细信息。

DeferredResult提供了onTimeout(Runnable)和onCompletion(Runnable)回调。有关详细信息,请参阅DeferredResultjavadoc。Callable可以替换为公开超时和完成回调的其他方法的WebAsyncTask。

与WebFlux相比

Servlet API最初是为Filter-Servlet链而构建的。Servlet 3.0中添加的异步请求处理允许应用程序退出Filter-Servlet链,但保留响应以供进一步处理。Spring MVC异步支持是围绕这种机制构建的。当控制器返回DeferredResult时,Filter-Servlet链就会退出,Servlet容器线程就会释放。稍后,当设置DeferredResult时,会进行ASYNC分派(到同一个URL),在此期间,控制器会再次映射,但不会调用它,而是使用DeferredResult值(就像控制器返回它一样)来恢复处理。

相比之下,Spring WebFlux既不基于Servlet API构建,也不需要这样的异步请求处理特征,因为它在设计上是异步的。异步处理内置于所有框架契约中,并在请求处理的所有阶段得到支持。

从编程模型的角度来看,Spring MVC和Spring WebFlux都支持异步和响应式类型作为控制器方法中的返回值。Spring MVC甚至支持流,包括响应式背压机制。但是,对响应的单个写入仍然是阻塞的(并且在单独的线程上执行),这与WebFlux不同,WebFlux依赖于非阻塞io,并且每次写入不需要额外的线程。

另一个根本区别是Spring MVC不支持controller方法参数中的异步或反应式类型(例如@RequestBody、@RequestPart等),也不支持将异步和反应式类型作为Model属性。而webflux支持所有。

HTTP流(ResponseBodyEmitter )

您可以对单个异步返回值使用DeferredResult和Callable。如果您想生成多个异步值并将它们写入响应怎么办?本节介绍如何执行此操作。

您可以使用ResponseBodyEmitter返回值生成对象流,其中每个对象都使用HttpMessageConverter序列化并写入响应,如下例所示:

@GetMapping("/events")
public ResponseBodyEmitter handle() {ResponseBodyEmitter emitter = new ResponseBodyEmitter();// Save the emitter somewhere..return emitter;
}// In some other thread
emitter.send("Hello once");// and again later on
emitter.send("Hello again");// and done at some point
emitter.complete();

您还可以使用ResponseBodyEmitter作为ResponseEntity中的主体,让您自定义响应的状态和标头。

当emitter抛出IOException(例如,如果远程客户端断开连接)时,应用程序不负责清理连接,也不应调起emitter.complete或emitter.completeWithErrorError。相反,servlet容器会自动启动AsyncListener错误通知,其中Spring MVC进行completeWithError调用。该调用反过来执行对应用程序的最后一次ASYNC分发,然后Spring MVC调用配置的异常解析器并完成请求。

(查看ResponseBodyEmitterReturnValueHandler,得知ResponseBodyEmitter也是基于DeferredResult来实现的)

示例
@RequestMapping("emitter")
public ResponseEntity<ResponseBodyEmitter> responseBodyEmitter() {log.info("testEmitter start");ResponseBodyEmitter emitter = new ResponseBodyEmitter(10000L);emitter.onCompletion(() -> {log.info("testEmitter onCompletion");});emitter.onTimeout(() -> {log.info("testEmitter onTimeout");});emitter.onError((e) -> {log.info("testEmitter onError: {}", e);});new Thread(()->{for (int i = 0; i < 10; i++) {try {emitter.send("testEmitter~" + i);log.info("testEmitter~" + i);} catch (IOException e) {throw new RuntimeException(e);}try {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}if (i > 5) {emitter.completeWithError(new ArithmeticException("计算错误"));}}log.info("发送完毕~");emitter.complete();}).start();log.info("testEmitter end");HttpHeaders httpHeaders = new HttpHeaders();httpHeaders.add("Access-Control-Allow-Origin", "*");httpHeaders.add("Content-Type", "text/html");httpHeaders.add("Cache-Control", "no-cache");httpHeaders.add("Transfer-Encoding", "chunked");return new ResponseEntity<ResponseBodyEmitter>(emitter, httpHeaders, HttpStatus.OK);
}
<!DOCTYPE html>
<html>
<head><title>分块数据实时展示</title>
</head>
<body><h1>实时数据接收:</h1><div id="output" style="border: 1px solid #ccc; padding: 10px; height: 300px; overflow-y: auto;"></div><script>// 要实现浏览器逐步接收并显示分块传输的数据,可以通过以下步骤使用 Fetch API(原生支持流式响应)配合前端实时渲染。// 启动请求并处理流式响应async function fetchStreamData() {const outputDiv = document.getElementById('output');try {const response = await fetch('http://localhost:8080/async/emitter');// 获取可读流const reader = response.body.getReader();const decoder = new TextDecoder();// 持续读取数据块while (true) {const { done, value } = await reader.read();if (done) break; // 流结束// 解码数据并追加到页面const chunk = decoder.decode(value, { stream: true });outputDiv.innerHTML += chunk;outputDiv.scrollTop = outputDiv.scrollHeight; // 自动滚动到底部}console.log('数据接收完成');} catch (error) {console.error('请求失败:', error);outputDiv.innerHTML += '请求失败: ' + error.message;}}// 页面加载后自动启动fetchStreamData();</script>
</body>
</html>

sse(SseEmitter)

SseEmitter(ResponseBodyEmitter的子类)支持Server-Sent Events,其中从服务器发送的事件根据W3C SSE规范进行格式化。要从控制器生成SSE流,返回SseEmitter,如下例所示:

@GetMapping(path="/events", produces=MediaType.TEXT_EVENT_STREAM_VALUE)
public SseEmitter handle() {SseEmitter emitter = new SseEmitter();// Save the emitter somewhere..return emitter;
}// In some other thread
emitter.send("Hello once");// and again later on
emitter.send("Hello again");// and done at some point
emitter.complete();

虽然SSE是流式传输到浏览器的主要选项,但请注意,IE不支持 Server-Sent Events。考虑将Spring的WebSocket与SockJS作为兜底(包括SSE)一起使用以覆盖大部分浏览器。

有关异常处理的说明,请参见HTTP流处理章节。

(使用与ResponseBodyEmitter完全一致,因为就是基于ResponseBodyEmitter。)

留意下:StreamingHttpOutputMessage 这个

原数据直传

有时,绕过消息转换机制并直接通过响应输出流(OutputStream)进行流式传输非常有用(例如实现文件下载功能)。为此,您可以将返回值的类型设为StreamingResponseBody,如下方示例所示:

@GetMapping("/download")
public StreamingResponseBody handle() {return new StreamingResponseBody() {@Overridepublic void writeTo(OutputStream outputStream) throws IOException {// write...}};
}

您可以使用StreamingResponseBody作为ResponseEntity中的主体来自定义响应的状态和标头。

(它内部是通过WebAsyncTask包装Callbale来实现的。它相比于直接使用repsonse的outputStream写入,是异步的,不会阻塞处理请求的线程。它支持分块传输吗?这点存疑。)

示例(文件下载)
后端代码
@GetMapping("/download")
public ResponseEntity<StreamingResponseBody> handle() {File file = new File("D:\\Projects\\practice\\demo-boot\\src\\main\\resources\\test.png");StreamingResponseBody streamingResponseBody = new StreamingResponseBody() {@Overridepublic void writeTo(OutputStream outputStream) throws IOException {byte[] buffer = new byte[1024];FileInputStream fis = new FileInputStream(file);int len = -1;while ((len = fis.read(buffer)) != -1) {outputStream.write(buffer, 0, len);}outputStream.flush();fis.close();}};return ResponseEntity.ok().header(HttpHeaders.ACCESS_CONTROL_ALLOW_ORIGIN, "*").header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + file.getName() + "\"").contentType(MediaType.APPLICATION_OCTET_STREAM).contentLength(file.length()).body(streamingResponseBody);
前端代码
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>File Download Example</title>
</head>
<body><h1>File Download Example</h1><button onclick="downloadWithFetch()">Download with Fetch (Streaming)</button><script>async function downloadWithFetch(filename) {try {const response = await fetch(`http://127.0.0.1:8080/async/download`);console.log(response.ok);if (!response.ok) {throw new Error('File not found');}// 获取文件名,可以从Content-Disposition头部解析let downloadFilename = 'demo.png';const contentDisposition = response.headers.get('Content-Disposition');if (contentDisposition && contentDisposition.indexOf('filename=') !== -1) {downloadFilename = contentDisposition.split('filename=')[1].replace(/"/g, '');}// 创建Blob对象并下载const blob = await response.blob();const url = window.URL.createObjectURL(blob);const a = document.createElement('a');a.href = url;a.download = downloadFilename;document.body.appendChild(a);a.click();// 清理window.URL.revokeObjectURL(url);document.body.removeChild(a);} catch (error) {console.error('Download failed:', error);alert('Download failed: ' + error.message);}}</script>
</body>
</html>
示例(大文件下载)
后端代码
@Slf4j
@RestController
@RequestMapping("/download")
public class LargeFileDownloadController {// 配置线程池(用于异步处理)// 或 Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());// 安全下载目录private static final Path SAFE_BASE_DIR = Paths.get("D:\\Projects\\practice\\demo-boot\\file");@CrossOrigin(origins = "*",methods = {RequestMethod.GET, RequestMethod.HEAD, RequestMethod.POST},allowedHeaders = "*",exposedHeaders = {"Accept-Ranges", "Content-Range", "Content-Type", "Content-Length"})@GetMapping("/{fileName}")public ResponseEntity<StreamingResponseBody> downloadLargeFile(HttpServletRequest request,HttpServletResponse response,@PathVariable String fileName,@RequestHeader(value = "Range", required = false) String rangeHeader) {// 1. 安全校验if (!isValidFileName(fileName)) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();}Path filePath = SAFE_BASE_DIR.resolve(fileName).normalize();// 2. 文件存在性检查if (!Files.exists(filePath) || Files.isDirectory(filePath)) {return ResponseEntity.notFound().build();}try {// 3. 获取文件信息long fileSize = Files.size(filePath);if (request.getMethod().equals("HEAD")) {return ResponseEntity.status(HttpStatus.OK).header("Accept-Ranges", "bytes").contentLength(fileSize).body(null);}HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);headers.setContentDisposition(ContentDisposition.builder("attachment").filename(fileName).build());log.info("Range请求头: {}", rangeHeader);// 4. 处理断点续传(Range请求)if (rangeHeader != null && rangeHeader.startsWith("bytes=")) {log.info("断点续传请求");return handleRangeRequest(filePath, fileSize, rangeHeader);}log.info("完整文件下载请求");// 5. 完整文件下载headers.setContentLength(fileSize);StreamingResponseBody responseBody = output -> {try (InputStream is = Files.newInputStream(filePath)) {byte[] buffer = new byte[64 * 1024]; // 64KB缓冲区int bytesRead;while ((bytesRead = is.read(buffer)) != -1) {output.write(buffer, 0, bytesRead);log.info("写入字节数: {}", bytesRead);output.flush();}} catch (IOException e) {log.error("文件下载中断", e);throw new RuntimeException(e);}};return ResponseEntity.ok().headers(headers).body(responseBody);} catch (IOException e) {return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build();}}// 处理断点续传(HTTP 206 Partial Content)private ResponseEntity<StreamingResponseBody> handleRangeRequest(Path filePath, long fullSize, String rangeHeader) throws IOException {// 解析Range头(示例简化实现)String[] ranges = rangeHeader.substring(6).split("-");long start = Long.parseLong(ranges[0]);long end = ranges.length > 1 ? Long.parseLong(ranges[1]) : fullSize - 1;long contentLength = end - start + 1;HttpHeaders headers = new HttpHeaders();headers.setContentType(MediaType.APPLICATION_OCTET_STREAM);headers.setContentLength(contentLength);headers.set("Content-Range", String.format("bytes %d-%d/%d", start, end, fullSize));StreamingResponseBody responseBody = output -> {try (RandomAccessFile raf = new RandomAccessFile(filePath.toFile(), "r")) {byte[] buffer = new byte[64 * 1024];raf.seek(start);log.info("RandomAccessFile跳转到: {}", start);// 还需要读的剩余字节数long remaining = contentLength;while (remaining > 0) {int readSize = (int) Math.min(buffer.length, remaining);int bytesRead = raf.read(buffer, 0, readSize);log.info("读取字节数: {}", bytesRead);if (bytesRead == -1) {log.info("无数据可读了");break;}output.write(buffer, 0, bytesRead);output.flush();remaining -= bytesRead;}log.info("读完了: {}", remaining);}};return ResponseEntity.status(HttpStatus.PARTIAL_CONTENT).headers(headers).body(responseBody);}// 校验文件名合法性(防止路径遍历)private boolean isValidFileName(String fileName) {return fileName.matches("[a-zA-Z0-9_\\-]+\\.?[a-zA-Z0-9_\\-]+");}
}
前端代码1

1、不占用tomcat的线程;
2、支持断点续传需要客户端支持;
3、前端能够看到进度
在这里插入图片描述

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><title>ajax 文件导出</title><script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head><body><button type="button" onclick="downloadFile()">下载</button><script type="text/javascript">const downloadFile = async () => {try {const response = await axios({method: 'get',url: `http://127.0.0.1:8080/download/test.mp4`,responseType: 'blob',onDownloadProgress: (progressEvent) => {const percent = Math.round((progressEvent.loaded * 100) / progressEvent.total);console.log(`下载进度: ${percent}%`);},});// 创建下载链接const url = window.URL.createObjectURL(new Blob([response.data]));const link = document.createElement('a');link.href = url;link.setAttribute('download', 'test.mp4');document.body.appendChild(link);link.click();link.remove();} catch (error) {console.error('下载失败:', error);if (error.response?.status === 404) {alert('文件不存在');}}};</script>
</body></html>
前端代码2

在这里插入图片描述

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>断点续传下载示例</title><style>.container {max-width: 600px;margin: 20px auto;padding: 20px;box-shadow: 0 0 10px rgba(0,0,0,0.1);}.progress-container {width: 100%;height: 20px;background-color: #f0f0f0;border-radius: 10px;margin: 20px 0;}.progress-bar {height: 100%;background-color: #4CAF50;border-radius: 10px;transition: width 0.3s ease;}button {padding: 10px 20px;background-color: #4CAF50;color: white;border: none;border-radius: 5px;cursor: pointer;}button:hover {background-color: #45a049;}#status {margin-top: 10px;color: #666;}#downloadLink {display: none;margin-top: 20px;color: #2196F3;text-decoration: none;}</style>
</head>
<body><div class="container"><button id="controlBtn">开始下载</button><div class="progress-container"><div id="progressBar" class="progress-bar" style="width: 0%"></div></div><div id="status">准备就绪</div><a id="downloadLink" download>下载文件</a></div><script>const fileUrl = 'http://127.0.0.1:8080/download/test.mp4'; // 替换为实际文件URLlet controller = null;let isDownloading = false;let receivedBytes = 0;let totalBytes = 0;let chunks = [];// 初始化IndexedDBconst initDB = () => {return new Promise((resolve, reject) => {const request = indexedDB.open('ResumableDownloadDB', 1);request.onupgradeneeded = (event) => {const db = event.target.result;if (!db.objectStoreNames.contains('downloads')) {db.createObjectStore('downloads', { keyPath: 'url' });}};request.onsuccess = () => resolve(request.result);request.onerror = () => reject(request.error);});};// 保存下载进度const saveProgress = async () => {const db = await initDB();const transaction = db.transaction('downloads', 'readwrite');const store = transaction.objectStore('downloads');store.put({ url: fileUrl, receivedBytes, chunks });};// 加载下载进度const loadProgress = async () => {const db = await initDB();return new Promise((resolve) => {const transaction = db.transaction('downloads');const store = transaction.objectStore('downloads');const request = store.get(fileUrl);request.onsuccess = () => {if (request.result) {receivedBytes = request.result.receivedBytes;chunks = request.result.chunks || [];}resolve();};});};// 更新进度显示const updateProgress = () => {const progress = (receivedBytes / totalBytes * 100).toFixed(1);document.getElementById('progressBar').style.width = `${progress}%`;document.getElementById('status').textContent = `已下载 ${progress}% (${formatBytes(receivedBytes)} / ${formatBytes(totalBytes)})`;};// 字节单位转换const formatBytes = (bytes) => {if (bytes === 0) return '0 B';const units = ['B', 'KB', 'MB', 'GB'];const i = Math.floor(Math.log(bytes) / Math.log(1024));return `${(bytes / Math.pow(1024, i)).toFixed(2)} ${units[i]}`;};// 开始/暂停下载const toggleDownload = async () => {if (isDownloading) {// 暂停下载controller.abort();isDownloading = false;await saveProgress();document.getElementById('controlBtn').textContent = '继续下载';} else {// 开始/继续下载isDownloading = true;document.getElementById('controlBtn').textContent = '暂停下载';try {await loadProgress();// 获取文件大小if (totalBytes === 0) {const headRes = await fetch(fileUrl, { method: 'HEAD' });totalBytes = parseInt(headRes.headers.get('Content-Length'), 10);if (!headRes.headers.get('Accept-Ranges')) {throw new Error('服务器不支持断点续传');}}controller = new AbortController();const response = await fetch(fileUrl, {headers: { 'Range': `bytes=${receivedBytes}-` },signal: controller.signal});if (response.status !== 206) {throw new Error('服务器不支持范围请求');}const reader = response.body.getReader();while (true) {const { done, value } = await reader.read();if (done) break;chunks.push(value.buffer);receivedBytes += value.byteLength;updateProgress();}// 下载完成const blob = new Blob(chunks);const url = URL.createObjectURL(blob);// 清理数据库记录const db = await initDB();const transaction = db.transaction('downloads', 'readwrite');transaction.objectStore('downloads').delete(fileUrl);// 显示下载链接document.getElementById('downloadLink').href = url;document.getElementById('downloadLink').style.display = 'inline';document.getElementById('status').textContent = '下载完成';} catch (err) {if (err.name === 'AbortError') {console.log('下载已暂停');} else {console.error('下载错误:', err);document.getElementById('status').textContent = `错误: ${err.message}`;}}isDownloading = false;document.getElementById('controlBtn').textContent = '开始下载';}};document.getElementById('controlBtn').addEventListener('click', toggleDownload);</script>
</body>
</html>
前端代码3

在这里插入图片描述

<!DOCTYPE html>
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>Document</title><script src="https://unpkg.com/axios/dist/axios.min.js"></script></head><body><button onClick="downloadChunks()">下载</button>
</body>
<script>function downloadChunks() {const chunkdownloadUrl = 'http://localhost:8080/download/test.mp4'// 分片下载大小 5MBconst chunkSize = 1024 * 1024 * 20;// 文件总大小(需要请求后端获得)let fileSize = 0;axios.head(chunkdownloadUrl).then(res => {// 定义 存储所有的分片的数组let chunks = [];// 获取文件总大小fileSize = res.headers['content-length']// 计算分片数量const chunksNum = Math.ceil(fileSize / chunkSize)// 定义下载文件分片的方法function downloadChunkFile(chunkIdx) {if (chunkIdx >= chunksNum) {alert('分片索引不可超过分片数量')return}let start = chunkIdx * chunkSizelet end = Math.min(start + chunkSize - 1, fileSize - 1)const range = `bytes=${start}-${end}`;axios({url: chunkdownloadUrl,method: 'post',headers: {Range: range},responseType: 'arraybuffer'}).then(response => {chunks.push(response.data)if (chunkIdx == chunksNum - 1) {// 下载好了console.log(chunks, 'chunks');// 组合chunks到单个文件const blob = new Blob(chunks);console.log(blob, 'blob');const link = document.createElement('a');link.href = window.URL.createObjectURL(blob);link.download = 'demo.mp4';link.click();return} else {++chunkIdxdownloadChunkFile(chunkIdx)}})}downloadChunkFile(0)})}
</script></html>
断点续传概念
概述

所谓断点续传,其实只是指下载,也就是要从文件已经下载的地方开始继续下载。在以前版本的HTTP协议是不支持断点的,HTTP/1.1开始就支持了。一般断点下载时才用到Range和Content-Range实体头。HTTP协议本身不支持断点上传,需要自己实现。

Range

Range:用于客户端到服务端的请求,在请求头中,指定第一个字节的位置和最后一个字节的位置,可以通过改字段指定下载文件的某一段大小及其单位,字节偏移从0开始。典型格式:

Ranges: (unit=first byte pos)-[last byte pos]

  • Ranges: bytes=4000- 下载从第4000字节开始到文件结束部分

  • Ranges: bytes=0~N 下载第0-N字节范围的内容

  • Ranges: bytes=M-N 下载第M-N字节范围的内容

  • Ranges: bytes=-N 下载最后N字节内容

以下几点需要注意:

  • 这个数据区间是个闭合区间,起始值是0,所以“Range: bytes=0-1”这样一个请求实际上是在请求开头的2个字节。

  • “Range: bytes=-200”,它不是表示请求文件开始位置的201个字节,而是表示要请求文件结尾处的200个字节。

  • 如果last byte pos小于first byte pos,那么这个Range请求就是无效请求,server需要忽略这个Range请求,然后回应一个200,把整个文件发给client。

  • 如果last byte pos大于等于文件长度,那么这个Range请求被认为是不能满足的,server需要回应一个416,Requested range not satisfiable。

示例解释:

  • 表示头500个字节:bytes=0-499

  • 表示第二个500字节:bytes=500-999

  • 表示最后500个字节:bytes=-500

  • 表示500字节以后的范围:bytes=500-

  • 第一个和最后一个字节:bytes=0-0,-1

  • 同时指定几个范围:bytes=500-600,601-999

Content-Range

用于响应头,指定整个实体中的一部分的插入位置,他也指示了整个实体的长度。在服务器向客户返回一个部分响应,它必须描述响应覆盖的范围和整个实体长度。一般格式:
Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth]

Header示例

GET /test.rar HTTP/1.1 
Connection: close 
Host: 116.1.219.219 
Range: bytes=0-801 //一般请求下载整个文件是bytes=0- 或不用这个头

一般正常回应

HTTP/1.1 200 OK 
Content-Length: 801      
Content-Type: application/octet-stream 
Content-Range: bytes 0-800/801 //801:文件总大小

一个最简单的断点续传实现大概如下:
1.客户端下载一个1024K的文件,已经下载了其中512K
2.网络中断,客户端请求续传,因此需要在HTTP头中申明本次需要续传的片段:Range:bytes=512000-
这个头通知服务端从文件的512K位置开始传输文件
3. 服务端收到断点续传请求,从文件的512K位置开始传输,并且在HTTP头中增加:
Content-Range:bytes 512000-/1024000
并且此时服务端返回的HTTP状态码应该是206,而不是200。

但是在实际场景中,会出现一种情况,即在终端发起续传请求时,URL对应的文件内容在服务端已经发生变化,此时续传的数据肯定是错误的。如何解决这个问题了?显然此时我们需要有一个标识文件唯一性的方法。在RFC2616中也有相应的定义,比如实现Last-Modified来标识文件的最后修改时间,这样即可判断出续传文件时是否已经发生过改动。同时RFC2616中还定义有一个ETag的头,可以使用ETag头来放置文件的唯一标识,比如文件的MD5值。

终端在发起续传请求时应该在HTTP头中申明If-Match 或者If-Modified-Since 字段,帮助服务端判别文件变化。

另外RFC2616中同时定义有一个If-Range头,终端如果在续传是使用If-Range。If-Range中的内容可以为最初收到的ETag头或者是Last-Modfied中的最后修改时候。服务端在收到续传请求时,通过If-Range中的内容进行校验,校验一致时返回206的续传回应,不一致时服务端则返回200回应,回应的内容为新的文件的全部数据。

响应式类型

Spring MVC支持在控制器中使用响应式客户端库(也可以阅读WebFlux部分中的响应式库)。这包括来自spring-webflux的WebClient和其他,例如Spring Data响应式数据存储库。在这种情况下,能够从控制器方法返回响应式类型很方便。

反应式返回值处理如下:

  • 单值promise(promise)会被自动适配,其处理方式与使用DeferredResult类似。例如:Reactor框架的Mono或RxJava的Single均支持这种适配。

  • 对于采用流式媒体类型(如 application/stream+json 或 text/event-stream)的多值流,框架会自动适配,其处理方式类似于使用 ResponseBodyEmitter 或 SseEmitter。例如:Reactor 的 Flux 或 RxJava 的 Observable 均支持此类适配。应用程序也可以直接返回 Flux<ServerSentEvent> Observable<ServerSentEvent>

  • 对于使用其他任何媒体类型(例如 application/json)的多值流,框架会将其适配为类似 DeferredResult<List<?>> 的处理方式。

Spring MVC通过spring-core的ReactiveAdapterRegistry支持React和RxJava,这使得它可以适应多个反应式库。

对于流式传输到响应,支持反应式反压机制,但对响应的写入仍然是阻塞的,并且通过配置 TaskExecutor在单独的线程上运行,以避免阻塞上游源(例如从WebClient返回的Flux)。默认情况下,SimpleAsyncTaskExecutor用于阻塞写入,但在负载下不适合。如果您计划使用反应式类型流式传输,您应该使用MVC配置来配置任务执行器。

断开连接

当远程客户端消失时,Servlet API不提供任何通知。因此,在流式传输响应时,无论是通过SseEmitter还是反应式类型,定期发送数据都很重要,因为如果客户端断开连接,写入就会失败。发送可以采取空(仅注释)SSE事件或任何其他数据的形式,对方必须将其解释为心跳并忽略。

或者,考虑使用具有内置心跳机制的Web消息传递解决方案(例如基于WebSocket的STOMP或带有SockJS的WebSocket)。

配置

必须在Servlet容器级别启用异步请求处理特征。MVC配置还公开了几个异步请求选项。

Servlet容器

过滤器和Servlet声明有一个true标志,需要设置为asyncSupported以启用异步请求处理。此外,应声明过滤器映射以处理ASYNC javax.servlet.DispatchType。

在Java配置中,当您使用AbstractAnnotationConfigDispatcherServletInitializer初始化Servlet容器时,这是自动完成的。

在web.xml配置中,您可以添加<async-supported>true</async-supported>到DispatcherServlet和Filter声明,并添加<dispatcher>ASYNC</dispatcher>到滤波器映射。

Spring MVC

MVC配置公开了以下与异步请求处理相关的选项:

  • Java配置:使用configureAsyncSupport回调/回传WebMvcConfigurer。
  • XML命名空间:使用<async-support>下的<mvc:annotation-driven>元素。

您可以配置以下内容:

  • 异步请求的默认超时值,如果未设置,则取决于底层Servlet容器。
  • AsyncTaskExecutor用于在使用反应式类型流式传输时阻止写入,以及执行从控制器方法返回的Callable实例。如果您使用反应式类型流式传输或具有返回Callable的控制器方法,我们强烈建议配置此属性,因为默认情况下,它是一个SimpleAsyncTaskExecutor。
  • DeferredResultProcessingInterceptor实现和CallableProcessingInterceptor实现。

请注意,您还可以在DeferredResult、ResponseBodyEmitter和SseEmitter上设置默认超时值SseEmitter对于Callable,您可以使用WebAsyncTask提供超时值。

相关文章:

spring mvc异步请求 sse 大文件下载 断点续传下载Range

学习连接 异步Servlet3.0 Spring Boot 处理异步请求&#xff08;DeferredResult 基础案例、DeferredResult 超时案例、DeferredResult 扩展案例、DeferredResult 方法汇总&#xff09; spring.io mvc Asynchronous Requests 官网文档 spring.io webflux&webclient官网文…...

Java项目集成大模型(通译千问)

1&#xff1a;打开阿里云搜索 通义大模型&#xff0c;然后点击丰富开发API接口。 2&#xff1a;可以看到有openAi和DashScope两种模式&#xff0c;我们这次采用DashScope方法&#xff0c;点击DashScope 3&#xff1a;点击获取Apikey 然后点击 前往我得Api-key跳转到控制台&…...

Lisp语言的安全协议

Lisp语言与安全协议的结合 引言 在信息社会的今天&#xff0c;数据的安全性和隐私保护愈发重要。随着网络攻击手段的不断演进&#xff0c;各种安全协议相继被提出与实现。与此同时&#xff0c;Lisp作为一种古老且灵活的计算机编程语言&#xff0c;虽然并不是主流的系统编程语…...

【图像处理基石】什么是自动曝光(AE)?

1. 什么是自动曝光&#xff08;AE&#xff09;&#xff1f; 自动曝光&#xff08;Auto Exposure, AE&#xff09;是一种通过调整相机参数&#xff08;如曝光时间、增益、光圈等&#xff09;使图像亮度达到目标值的技术。其核心是通过实时分析图像亮度&#xff0c;动态优化参数…...

清明假期间

1.思维导图 2.90题 3. #include <iostream> #include <cstring> #include <cstdlib> #include <unistd.h> #include <sstream> #include <vector> #include <memory>using namespace std;class Weapon;class Hero{ private:int hp;…...

el-tabs添加按钮增加点击禁止样式

前置文章 一、vue使用element-ui自定义样式思路分享【实操】 二、vue3&ts&el-tabs多个tab表单校验 现状确认 点击添加按钮&#xff0c;没有点击样式&#xff0c;用户感知不明显没有限制最大的tab添加数量&#xff0c;可以无限添加 调整目标&代码编写 调整目标…...

Elasticsearch DSL 中的 aggs 聚合

一、聚合的概述 Elasticsearch 的 聚合&#xff08;Aggregations&#xff09; 功能用于对数据进行多维分析和统计&#xff0c;支持从简单的指标计算到复杂的分组分析。以下是聚合的基本结构&#xff1a; {"aggs": { // 也可以使用"agg…...

API调用类型全面指南:理解基础知识

在当今快速发展的数字化环境中&#xff0c;API&#xff08;应用程序编程接口&#xff09;是不同软件应用程序能够无缝通信的基石。无论是开发移动应用、集成第三方服务&#xff0c;还是构建强大的Web平台&#xff0c;理解各种API调用类型至关重要。那么&#xff0c;API调用到底…...

华东师范​地面机器人融合空中无人机视角的具身导航!KiteRunner:语言驱动的户外环境合作式局部-全局导航策略

作者&#xff1a;Shibo Huang 1 ^{1} 1, Chenfan Shi 1 ^{1} 1, Jian Yang 2 ^{2} 2, Hanlin Dong 1 ^{1} 1, Jinpeng Mi 3 ^{3} 3, Ke Li 2 ^{2} 2, Jianfeng Zhang 1 ^{1} 1, Miao Ding 4 ^{4} 4, Peidong Liang 5 ^{5} 5, Xiong You 2 ^{2} 2, Xian Wei 1 ^{1} 1单位&#x…...

Ansible(6)——管理变量

目录 一、Ansible 变量&#xff1a; 1、什么是变量&#xff1a; 2、变量可能包含的值&#xff1a; 3、变量命名&#xff1a; 4、定义变量&#xff1a; 二、Playbook 中的变量&#xff1a; 1、在 Playbook 中定义变量&#xff1a; &#xff08;1&#xff09;最简单的定…...

Git常用问题收集

gitignore 忽略文件夹 不生效 有时候我们接手别人的项目时&#xff0c;发现有的忽略不对想要修改&#xff0c;但发现修改忽略.gitignore后无效。原因是如果某些文件已经被纳入版本管理在.gitignore中忽略路径是不起作用的&#xff0c;这时候需要先清除本地缓存&#xff0c;然后…...

构建高效多标签选择组件:从设计到实现

在现代Web应用中&#xff0c;多标签选择功能已成为常见需求&#xff0c;特别是在内容分类、文章标签、用户兴趣选择等场景。本文将深入解析一个完整的多标签选择实现方案&#xff0c;涵盖交互设计、核心功能和优化技巧。 组件功能概述 这个多标签选择组件提供以下核心功能&am…...

4.1论文阅读

一&#xff1a;PhDnet&#xff1a;一种用于遥感图像的新型物理感知去雾网络&#xff08;A novel physic-aware dehazing network for remote sensing images&#xff09; 论文链接 只是粗略读了一下&#xff0c;关于遥感图像去雾&#xff0c;圆形U--net&#xff0c;加入了物理…...

【渗透测试】Vulnhub靶机-HA: Armour-详细通关教程

下载地址&#xff1a;https://www.vulnhub.com/entry/ha-armour,370/ 目录 前言 信息收集 tftp获取&#xff08;spiderman&#xff09; 查看.htpasswd&#xff08;ant-man&#xff09; ssh欢迎信息提示&#xff08;hulkbuster&#xff09; 反弹shell 提权&#xff08;i…...

Flask使用MySQL数据库通过Flask-SQLAlchemy 迁移数据库,实际更新文件,但是提示没有检测到数据更新。

本地写了一个model的用户类&#xff0c;数据库连接信息正确&#xff0c;执行下面2条命令进行数据库迁移。 flask db migrate 生成迁移文件 flask db upgrade 执行迁移文件的升级 发现执行完后&#xff1a;提示没有检测到数据的更新 PS C:\Users\mu> flask db migrate IN…...

【leetcode100】前K个高频元素

1、题目描述 给你一个整数数组 nums 和一个整数 k &#xff0c;请你返回其中出现频率前 k 高的元素。你可以按 任意顺序 返回答案。 示例 1: 输入: nums [1,1,1,2,2,3], k 2 输出: [1,2]示例 2: 输入: nums [1], k 1 输出: [1] 2、初始思路 2.1 思路 全排列&#xf…...

内网渗透-MySQL提权

MySQL提权 mysql的权限提升通常有两种&#xff1a; UDF提权&#xff08;常用&#xff09; 写文件提权启动项提权mof提权一、UDF提权 UDF 全称为user defined function&#xff0c;用户自定义函数 用户可以添加自定义的新函数到Mysql中&#xff0c;以达到功能的扩充&#xf…...

Hibernate核心方法总结

Session中的核心方法梳理 1、save方法 这个方法表示将一个对象保存到数据库中&#xff0c;可以将一个不含OID的new出来的临时对象转换为一个处于Session缓存中具有OID的持久化对象。 需要注意的是&#xff1a;在save方法前设置OID是无效的但是也不会报错&#xff0c;在save方…...

DevOps 与持续集成(CI/CD)

1. DevOps 概述 DevOps(Development + Operations)是一种软件开发方法,强调开发(Dev)与运维(Ops)协作,通过自动化工具提高软件交付效率。其目标是: ✅ 提高部署速度 —— 频繁发布新版本 ✅ 减少人为错误 —— 通过自动化降低运维风险 ✅ 增强可观测性 —— 监控和日…...

下一代AI App架构:前端生成,后端消失

过去十年&#xff0c;Web 和 App 的开发范式基本稳定&#xff1a;前端负责交互体验&#xff0c;后端负责业务逻辑和数据管理。即使是“无服务架构”也只是将后端“拆散”而非“消失”。 但随着 AI 原生应用的兴起&#xff0c;特别是 大模型本地化、小模型部署、WebAssembly、L…...

告别过去,奔向未来

人生就是一个不断雕刻自己的过程&#xff01;一路走来&#xff0c;我们经历过酸甜苦辣咸&#xff0c;迷茫过&#xff0c;跌倒过&#xff0c;懈怠过……但是&#xff0c;我想说这又何妨&#xff01;一个成功人士的经历必定是跌跌宕宕&#xff0c;起起伏伏的。关键是我们要做到&a…...

AF3 Recycling机制

在 AlphaFold3中,输入数据的特征加工中生成了recycling 维度的数据,主要通过ensembled_transform_fns函数抽样得到不同的扰动的MSA、template特征等,类似于数据增强的作用。在数据集的加载和模型的训练中利用了这一维度的数据,增强了模型的稳定性和鲁棒性,避免单一预测结果…...

notepad++8.6.4安装及细节

notepad8.6.4下载安装&#xff08;附安装包&#xff09; 一、安装包下载1.1方法一&#xff1a;官网下载&#xff08;点击跳转&#xff09;1.2方法二&#xff1a;网盘链接分享8.6.4版本 二、安装过程细节2.1这里的组件建议全部勾选。点击“下一步”。2.2 勾选①&#xff1a;可以…...

谁该处理我的请假?——责任链模式

谁该处理我的请假&#xff1f;——责任链模式 一、生活中的责任链&#xff1a;请假审批流程二、责任链模式的核心特点三、代码实现&#xff1a;请假审批责任链四、工作中的实际应用场景五、框架中的经典应用六、模式本质理解 一、生活中的责任链&#xff1a;请假审批流程 想象…...

【NLP应用场景全解】自然语言处理如何改变世界?

自然语言处理作为人工智能的重要分支&#xff0c;正在加速改变各行各业。根据Statista预测&#xff0c;到2025年&#xff0c;全球NLP市场规模将达到438亿美元。本文将系统梳理NLP的主要应用场景&#xff0c;结合最新技术趋势&#xff0c;帮助你了解NLP技术的落地现状与未来发展…...

Hive 中书写SQL注意的地方

1.1 关于 DDL &#xff08;1&#xff09;创建带有主键约束的 Hive 表时报错。 Hive 目前还没有严格支持“主键约束”&#xff0c;创建带有主键约束的 HIVE 表时报了如下错误&#xff1a; SemanticException [Error 10326]: Invalid Constraint: syntax ENABLE/ENFORCED featu…...

Ubuntu 下 无界面环境 多进程/多线程 使用DrissionPage

使用wget “https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb” -O chrome.deb 安装chrome # !/usr/bin/python3 # -*- coding:utf-8 -*- """ author: JHC000abcgmail.com file: dp.py time: 2025/2/20 20:22 desc:wget "htt…...

Opencv计算机视觉编程攻略-第十节 估算图像之间的投影关系

目录 1. 计算图像对的基础矩阵 2. 用RANSAC 算法匹配图像 3. 计算两幅图像之间的单应矩阵 4. 检测图像中的平面目标 图像通常是由数码相机拍摄的&#xff0c;它通过透镜投射光线成像&#xff0c;是三维场景在二维平面上的投影&#xff0c;这表明场景和它的图像之间以及同一…...

RocketMQ 01

今天是2025/04/06 21:31 day 18 总路线请移步主页Java大纲相关文章 今天进行RocketMQ 1,2 个模块的归纳 首先是RocketMQ 的相关内容概括的思维导图 1. 核心组件 1.1 NameServer 核心功能 服务发现&#xff1a;作为轻量级注册中心&#xff0c;管理所有 Broker 的地址和路由信…...

牛客周赛———字符串

题目如下 思路&#xff08;贪心&#xff09; >和<的位置是固定不变的&#xff0c;所以先处理这两个符号&#xff0c;然后再遍历一遍检查‘Z’&#xff0c;如果不符合条件将Z的位置改变正负性使其满足条件&#xff0c;然后遍历的时候记数答案就行了&#xff0c;注意s的首…...

在Hive中,将数据从一个表查询并插入到另一个表

1. 确认目标表结构 确保目标表已存在且结构与查询结果匹配。若不存在&#xff0c;需先创建&#xff1a; CREATE TABLE target_table ( id INT, name STRING ) PARTITIONED BY (dt STRING) STORED AS ORC; 2. 选择插入方式 覆盖插入&#xff08;替换现有数据&#xff0…...

优雅实现级联选择器:CascadeSelect 类设计与实现

在现代Web开发中&#xff0c;级联选择器是一种常见的UI组件&#xff0c;它能够有效地组织和展示层级数据。本文将深入解析一个功能完善的级联选择器实现——CascadeSelect类&#xff0c;展示如何用面向对象的方式构建可复用的UI组件。 组件概述 CascadeSelect是一个二级下拉框…...

26考研 | 王道 | 数据结构 | 第五章 树

第五章 树 5.1. 树的概念 5.1.1. 树的基本定义 树:n(n>0)个节点的有限集合&#xff0c;是一种逻辑结构&#xff0c;当n0时为空树&#xff0c;且非空树满足: 有且仅有一个特定的称为根的节点当n>1时&#xff0c;其余结点可分为m (m >0) 个互不相交的有限集合&#x…...

Spring 怎么解决循环依赖问题?

Spring 循环依赖&#xff08;circular dependency&#xff09; 指的是多个 Bean 之间的相互依赖&#xff0c;比如&#xff1a; A 依赖 B&#xff0c;B 又依赖 A&#xff1b;或者 A → B → C → A 这种嵌套循环依赖。 这是一个常见又棘手的问题&#xff0c;但 Spring 是可以解…...

微软推出首款量子计算芯片Majorana 1

全球首款拓扑架构量子芯片问世&#xff0c;2025年2月20日&#xff0c;经过近20年研究&#xff0c;微软推出了首款量子计算芯片Majorana 1&#xff0c;其宣传视频如本文末尾所示。 微软表示&#xff0c;开发Majorana 1需要创造一种全新的物质状态&#xff0c;即所谓的“拓扑体”…...

OSI模型中协议数据单元(PDU)

OSI模型中协议数据单元&#xff08;PDU&#xff09; 协议数据单元&#xff08;Protocol Data Unit, PDU&#xff09;是网络通信中每一层协议处理的数据单位&#xff0c;其内容和格式由特定层的协议定义。PDU在不同OSI层次中有不同的名称和结构&#xff0c;体现了分层模型的核心…...

代码训练营day24 回溯算法

回溯算法part03 93.复原IP地址 题目链接/文章讲解&#xff1a;代码随想录 视频讲解&#xff1a;回溯算法如何分割字符串并判断是合法IP&#xff1f;| LeetCode&#xff1a;93.复原IP地址_哔哩哔哩_bilibili 本题关键在于终止条件 插入3个’.’时判断 ip地址最后一段是否…...

DP Alt Mode 与 DP协议的关系

1. 什么是 “Alt Mode”&#xff08;替代模式&#xff09;&#xff1f; Alt Mode&#xff08;Alternative Mode&#xff09; 是 USB Type-C 接口 的扩展协议机制&#xff0c;允许通过 物理接口复用&#xff08;Pin Reuse&#xff09; 将USB-C接口动态切换为其他协议&#xff0…...

【欧拉筛】哥德巴赫猜想题解

哥德巴赫猜想题解 题目传送门 1292. 哥德巴赫猜想 一、题目描述 哥德巴赫猜想指出&#xff1a;任意一个大于4的偶数都可以拆成两个奇素数之和。给定多个偶数(6 ≤ n < 10^6)&#xff0c;验证它们是否符合这个猜想。对于每个偶数&#xff0c;输出其素数分解中两数差最大的…...

A*算法详解及Python实现

一、什么是A*算法&#xff1f; A*&#xff08;读作"A-star"&#xff09;算法是一种广泛使用的路径查找和图形遍历算法&#xff0c;它结合了Dijkstra算法的完备性和贪婪最佳优先搜索的高效性。A*算法通过使用启发式函数来估算从当前节点到目标节点的成本&#xff0c;…...

【C++】第九节—string类(中)——详解+代码示例

hello&#xff01; 云边有个稻草人-CSDN博客 C_云边有个稻草人的博客-CSDN博客 菜鸡进化中。。。 目录 【补充】 二、string类里面的常用接口 1.resize 2.insert 3.assign 4.erase 5.replacefind 6.c_str 7.rfindsubstr 8.find_first_of、find_last_of、find_first…...

vite.config.js常用配置

vite 是一个基于 Vue3 单文件组件的非打包开发服务器&#xff0c;它做到了本地快速开发启动&#xff1a; 快速的冷启动&#xff0c;不需要等待打包操作&#xff1b; 即时的热模块更新&#xff0c;替换性能和模块数量的解耦让更新飞起&#xff1b; 真正的按需编译&#xff0c;不…...

【C++11(下)】—— 我与C++的不解之缘(三十二)

前言 随着 C11 的引入&#xff0c;现代 C 语言在语法层面上变得更加灵活、简洁。其中最受欢迎的新特性之一就是 lambda 表达式&#xff08;Lambda Expression&#xff09;&#xff0c;它让我们可以在函数内部直接定义匿名函数。配合 std::function 包装器 使用&#xff0c;可以…...

李臻20242817_安全文件传输系统项目报告_第6周

安全文件传输系统项目报告&#xff08;第 1 周&#xff09; 1. 代码链接 Gitee 仓库地址&#xff1a;https://gitee.com/li-zhen1215/homework/tree/master/Secure-file 代码结构说明&#xff1a; project-root/├── src/ # 源代码目录│ ├── main.c # 主程序入口│ ├…...

使用SymPy求解矩阵微分方程

引言 在数学、物理、工程等领域,微分方程常常被用来描述系统的变化和动态过程。对于多变量系统或者多方程系统,矩阵微分方程是非常常见的,它可以用来描述如电路、控制系统、振动系统等复杂的动态行为。今天,我们将通过Python 中的 SymPy 库来求解矩阵微分方程,帮助大家轻…...

Flutter之用户输入网络数据缓存

目录&#xff1a; 1、处理用户输入1.1、按钮1.2、文本1.3、富文本1.4、TextField1.5、Form 2、复选框、Switch 和 Radio2.1、复选框2.2、Switch2.3、Radio 3、选择日期或时间4、滑动5、网络和数据6、本地缓存6.1、在本地内存中缓存数据 7、web端和Flutter样式控制对比7.1、文本…...

华为IP(4)

VRRP&#xff08;虚拟路由冗余协议&#xff09; 前言&#xff1a; 局域网中的用户终端通常采用配置一个默认网关的形式访问外部网络&#xff0c;如果默认网关设备发生故障&#xff0c;那么所有用户终端访问外部网络的流量将会中断。可以通过部署多个网关的方式来解决单点故障…...

蓝桥杯刷题周计划(第四周)

目录 前言题目一题目代码题解分析 题目二题目代码题解分析 题目三题目代码题解分析 题目四题目代码题解分析 题目五题目代码题解分析 题目六题目代码题解分析 题目七题目代码题解分析 题目八题目代码题解分析 题目九题目代码题解分析 题目十题目代码题解分析题目代码题解分析 题…...

华为网路设备学习-17

目录 一、加密算法 二、验证算法 三、IPsec协议 1.IKE协议&#xff08;密钥交换协议&#xff09; ①‌ISAKMP&#xff08;Internet Security Association and Key Management Protocol&#xff09;互联网安全关联和密钥管理协议 ②安全关联&#xff08;SA&#xff09; ③…...

【动态规划】深入动态规划 非连续子序列问题

文章目录 前言例题一、最长递增子序列二、摆动序列三、最长递增子序列的个数四、最长数对链五、最长定差子序列六、最长的斐波那契子序列的长度七、最长等差数列八、等差数列划分II - 子序列 结语 前言 什么是动态规划中的非连续子序列问题&#xff1f; 动态规划中的非连续子序…...