中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

免費(fèi)天眼查公司查詢aso優(yōu)化公司

免費(fèi)天眼查公司查詢,aso優(yōu)化公司,長沙企業(yè)建站,wordpress頁面評論一、簡介 隨著互聯(lián)網(wǎng)的快速發(fā)展,大文件的傳輸成為了互聯(lián)網(wǎng)應(yīng)用的重要組成部分。然而,由于網(wǎng)絡(luò)不穩(wěn)定等因素的影響,大文件的傳輸經(jīng)常會出現(xiàn)中斷的情況,這時(shí)需要重新傳輸,導(dǎo)致傳輸效率低下。 為了解決這個(gè)問題&#xff…

一、簡介

隨著互聯(lián)網(wǎng)的快速發(fā)展,大文件的傳輸成為了互聯(lián)網(wǎng)應(yīng)用的重要組成部分。然而,由于網(wǎng)絡(luò)不穩(wěn)定等因素的影響,大文件的傳輸經(jīng)常會出現(xiàn)中斷的情況,這時(shí)需要重新傳輸,導(dǎo)致傳輸效率低下。

為了解決這個(gè)問題,可以實(shí)現(xiàn)大文件的斷點(diǎn)續(xù)傳功能。斷點(diǎn)續(xù)傳功能可以在傳輸中斷后繼續(xù)傳輸,而不需要從頭開始傳輸。這樣可以大大提高傳輸?shù)男省?/strong>

Spring Boot是一個(gè)快速開發(fā)的Java Web開發(fā)框架,可以幫助我們快速搭建一個(gè)Web應(yīng)用程序。在Spring Boot中,我們可以很容易地實(shí)現(xiàn)大文件的斷點(diǎn)續(xù)傳功能。

本文將介紹如何使用Spring Boot實(shí)現(xiàn)大文件的斷點(diǎn)續(xù)傳功能。

二、Spring Boot實(shí)現(xiàn)大文件斷點(diǎn)續(xù)傳的原理

實(shí)現(xiàn)大文件的斷點(diǎn)續(xù)傳功能,需要在客戶端和服務(wù)端都進(jìn)行相應(yīng)的實(shí)現(xiàn)。

實(shí)現(xiàn)示例1

服務(wù)端如何將一個(gè)大視頻文件做切分,分段響應(yīng)給客戶端,讓瀏覽器可以漸進(jìn)式地播放。

Spring Boot實(shí)現(xiàn)HTTP分片下載斷點(diǎn)續(xù)傳,從而實(shí)現(xiàn)H5頁面的大視頻播放問題,實(shí)現(xiàn)漸進(jìn)式播放,每次只播放需要播放的內(nèi)容就可以了,不需要加載整個(gè)文件到內(nèi)存中。

文件的斷點(diǎn)續(xù)傳、文件多線程并發(fā)下載(迅雷就是這么玩的)等。

  <dependencyManagement><dependencies><dependency><groupId>cn.hutool</groupId><artifactId>hutool-bom</artifactId><version>5.8.18</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement><dependencies><dependency><groupId>cn.hutool</groupId><artifactId>hutool-core</artifactId></dependency><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId><optional>true</optional></dependency></dependencies>

代碼實(shí)現(xiàn)

ResourceController

package com.example.insurance.controller;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;import com.example.insurance.common.ContentRange;
import com.example.insurance.common.MediaContentUtil;
import com.example.insurance.common.NioUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRange;
import org.springframework.http.HttpStatus;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StopWatch;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** 內(nèi)容資源控制器*/
@SuppressWarnings("unused")
@Slf4j
@RestController("resourceController")
@RequestMapping(path = "/resource")
public class ResourceController {/*** 獲取文件內(nèi)容** @param fileName 內(nèi)容文件名稱* @param response 響應(yīng)對象*/@GetMapping("/media/{fileName}")public void getMedia(@PathVariable String fileName, HttpServletRequest request, HttpServletResponse response,@RequestHeader HttpHeaders headers) {
//        printRequestInfo(fileName, request, headers);String filePath = MediaContentUtil.filePath();try {this.download(fileName, filePath, request, response, headers);} catch (Exception e) {log.error("getMedia error, fileName={}", fileName, e);}}/*** 獲取封面內(nèi)容** @param fileName 內(nèi)容封面名稱* @param response 響應(yīng)對象*/@GetMapping("/cover/{fileName}")public void getCover(@PathVariable String fileName, HttpServletRequest request, HttpServletResponse response,@RequestHeader HttpHeaders headers) {
//        printRequestInfo(fileName, request, headers);String filePath = MediaContentUtil.filePath();try {this.download(fileName, filePath, request, response, headers);} catch (Exception e) {log.error("getCover error, fileName={}", fileName, e);}}// ======= internal =======private static void printRequestInfo(String fileName, HttpServletRequest request, HttpHeaders headers) {String requestUri = request.getRequestURI();String queryString = request.getQueryString();log.debug("file={}, url={}?{}", fileName, requestUri, queryString);log.info("headers={}", headers);}/*** 設(shè)置請求響應(yīng)狀態(tài)、頭信息、內(nèi)容類型與長度 等。* <pre>* <a href="https://www.rfc-editor.org/rfc/rfc7233">*     HTTP/1.1 Range Requests</a>* 2. Range Units* 4. Responses to a Range Request** <a href="https://www.rfc-editor.org/rfc/rfc2616.html">*     HTTP/1.1</a>* 10.2.7 206 Partial Content* 14.5 Accept-Ranges* 14.13 Content-Length* 14.16 Content-Range* 14.17 Content-Type* 19.5.1 Content-Disposition* 15.5 Content-Disposition Issues** <a href="https://www.rfc-editor.org/rfc/rfc2183">*     Content-Disposition</a>* 2. The Content-Disposition Header Field* 2.1 The Inline Disposition Type* 2.3 The Filename Parameter* </pre>** @param response     請求響應(yīng)對象* @param fileName     請求的文件名稱* @param contentType  內(nèi)容類型* @param contentRange 內(nèi)容范圍對象*/private static void setResponse(HttpServletResponse response, String fileName, String contentType,ContentRange contentRange) {// http狀態(tài)碼要為206:表示獲取部分內(nèi)容response.setStatus(HttpStatus.PARTIAL_CONTENT.value());// 支持?jǐn)帱c(diǎn)續(xù)傳,獲取部分字節(jié)內(nèi)容// Accept-Ranges:bytes,表示支持Range請求response.setHeader(HttpHeaders.ACCEPT_RANGES, ContentRange.BYTES_STRING);// inline表示瀏覽器直接使用,attachment表示下載,fileName表示下載的文件名response.setHeader(HttpHeaders.CONTENT_DISPOSITION,"inline;filename=" + MediaContentUtil.encode(fileName));// Content-Range,格式為:[要下載的開始位置]-[結(jié)束位置]/[文件總大小]// Content-Range: bytes 0-10/3103,格式為bytes 開始-結(jié)束/全部response.setHeader(HttpHeaders.CONTENT_RANGE, contentRange.toContentRange());response.setContentType(contentType);// Content-Length: 11,本次內(nèi)容的大小response.setContentLengthLong(contentRange.applyAsContentLength());}/*** <a href="https://www.jianshu.com/p/08db5ba3bc95">*     Spring Boot 處理 HTTP Headers</a>*/private void download(String fileName, String path, HttpServletRequest request, HttpServletResponse response,HttpHeaders headers)throws IOException {Path filePath = Paths.get(path + fileName);if (!Files.exists(filePath)) {log.warn("file not exist, filePath={}", filePath);return;}long fileLength = Files.size(filePath);
//        long fileLength2 = filePath.toFile().length() - 1;
//        // fileLength=1184856, fileLength2=1184855
//        log.info("fileLength={}, fileLength2={}", fileLength, fileLength2);// 內(nèi)容范圍ContentRange contentRange = applyAsContentRange(headers, fileLength, request);// 要下載的長度long contentLength = contentRange.applyAsContentLength();log.debug("contentRange={}, contentLength={}", contentRange, contentLength);// 文件類型String contentType = request.getServletContext().getMimeType(fileName);// mimeType=video/mp4, CONTENT_TYPE=nulllog.debug("mimeType={}, CONTENT_TYPE={}", contentType, request.getContentType());setResponse(response, fileName, contentType, contentRange);// 耗時(shí)指標(biāo)統(tǒng)計(jì)StopWatch stopWatch = new StopWatch("downloadFile");stopWatch.start(fileName);try {// case-1.參考網(wǎng)上他人的實(shí)現(xiàn)
//            if (fileLength >= Integer.MAX_VALUE) {
//                NioUtils.copy(filePath, response, contentRange);
//            } else {
//                NioUtils.copyByChannelAndBuffer(filePath, response, contentRange);
//            }// case-2.使用現(xiàn)成APINioUtils.copyByBio(filePath, response, contentRange);
//            NioUtils.copyByNio(filePath, response, contentRange);// case-3.視頻分段漸進(jìn)式播放
//            if (contentType.startsWith("video")) {
//                NioUtils.copyForBufferSize(filePath, response, contentRange);
//            } else {
//                // 圖片、PDF等文件
//                NioUtils.copyByBio(filePath, response, contentRange);
//            }} finally {stopWatch.stop();log.info("download file, fileName={}, time={} ms", fileName, stopWatch.getTotalTimeMillis());}}private static ContentRange applyAsContentRange(HttpHeaders headers, long fileLength, HttpServletRequest request) {/** 3.1. Range - HTTP/1.1 Range Requests* https://www.rfc-editor.org/rfc/rfc7233#section-3.1* Range: "bytes" "=" first-byte-pos "-" [ last-byte-pos ]** For example:* bytes=0-* bytes=0-499*/// Range:告知服務(wù)端,客戶端下載該文件想要從指定的位置開始下載List<HttpRange> httpRanges = headers.getRange();String range = request.getHeader(HttpHeaders.RANGE);// httpRanges=[], range=null// httpRanges=[448135688-], range=bytes=448135688-log.debug("httpRanges={}, range={}", httpRanges, range);// 開始下載位置long firstBytePos;// 結(jié)束下載位置long lastBytePos;if (CollectionUtils.isEmpty(httpRanges)) {firstBytePos = 0;lastBytePos = fileLength - 1;} else {HttpRange httpRange = httpRanges.get(0);firstBytePos = httpRange.getRangeStart(fileLength);lastBytePos = httpRange.getRangeEnd(fileLength);}return new ContentRange(firstBytePos, lastBytePos, fileLength);}
}

NioUtils

package com.example.insurance.common;import javax.servlet.http.HttpServletResponse;
import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import java.nio.MappedByteBuffer;
import java.nio.channels.Channels;
import java.nio.channels.FileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.NioUtil;
import cn.hutool.core.io.StreamProgress;
import cn.hutool.core.io.unit.DataSize;
import lombok.extern.slf4j.Slf4j;
import org.apache.catalina.connector.ClientAbortException;/*** NIO相關(guān)工具封裝,主要針對Channel讀寫、拷貝等封裝*/
@Slf4j
public final class NioUtils {/*** 緩沖區(qū)大小 16KB** @see NioUtil#DEFAULT_BUFFER_SIZE* @see NioUtil#DEFAULT_LARGE_BUFFER_SIZE*/
//    private static final int BUFFER_SIZE = NioUtil.DEFAULT_MIDDLE_BUFFER_SIZE;private static final int BUFFER_SIZE = (int) DataSize.ofKilobytes(16L).toBytes();/*** <pre>* <a href="https://blog.csdn.net/qq_32099833/article/details/109703883">*     Java后端實(shí)現(xiàn)視頻分段漸進(jìn)式播放</a>* 服務(wù)端如何將一個(gè)大的視頻文件做切分,分段響應(yīng)給客戶端,讓瀏覽器可以漸進(jìn)式地播放。* 文件的斷點(diǎn)續(xù)傳、文件多線程并發(fā)下載(迅雷就是這么玩的)等。** <a href="https://blog.csdn.net/qq_32099833/article/details/109630499">*     大文件分片上傳前后端實(shí)現(xiàn)</a>* </pre>*/public static void copyForBufferSize(Path filePath, HttpServletResponse response, ContentRange contentRange) {String fileName = filePath.getFileName().toString();RandomAccessFile randomAccessFile = null;OutputStream outputStream = null;try {// 隨機(jī)讀文件randomAccessFile = new RandomAccessFile(filePath.toFile(), "r");// 移動訪問指針到指定位置randomAccessFile.seek(contentRange.getStart());// 注意:緩沖區(qū)大小 2MB,視頻加載正常;1MB時(shí)有部分視頻加載失敗int bufferSize = BUFFER_SIZE;//獲取響應(yīng)的輸出流outputStream = new BufferedOutputStream(response.getOutputStream(), bufferSize);// 每次請求只返回1MB的視頻流byte[] buffer = new byte[bufferSize];int len = randomAccessFile.read(buffer);//設(shè)置此次相應(yīng)返回的數(shù)據(jù)長度response.setContentLength(len);// 將這1MB的視頻流響應(yīng)給客戶端outputStream.write(buffer, 0, len);log.info("file download complete, fileName={}, contentRange={}",fileName, contentRange.toContentRange());} catch (ClientAbortException | IORuntimeException e) {// 捕獲此異常表示用戶停止下載log.warn("client stop file download, fileName={}", fileName);} catch (Exception e) {log.error("file download error, fileName={}", fileName, e);} finally {IoUtil.close(outputStream);IoUtil.close(randomAccessFile);}}/*** 拷貝流,拷貝后關(guān)閉流。** @param filePath     源文件路徑* @param response     請求響應(yīng)* @param contentRange 內(nèi)容范圍*/public static void copyByBio(Path filePath, HttpServletResponse response, ContentRange contentRange) {String fileName = filePath.getFileName().toString();InputStream inputStream = null;OutputStream outputStream = null;try {RandomAccessFile randomAccessFile = new RandomAccessFile(filePath.toFile(), "r");randomAccessFile.seek(contentRange.getStart());inputStream = Channels.newInputStream(randomAccessFile.getChannel());outputStream = new BufferedOutputStream(response.getOutputStream(), BUFFER_SIZE);StreamProgress streamProgress = new StreamProgressImpl(fileName);long transmitted = IoUtil.copy(inputStream, outputStream, BUFFER_SIZE, streamProgress);log.info("file download complete, fileName={}, transmitted={}", fileName, transmitted);} catch (ClientAbortException | IORuntimeException e) {// 捕獲此異常表示用戶停止下載log.warn("client stop file download, fileName={}", fileName);} catch (Exception e) {log.error("file download error, fileName={}", fileName, e);} finally {IoUtil.close(outputStream);IoUtil.close(inputStream);}}/*** 拷貝流,拷貝后關(guān)閉流。* <pre>* <a href="https://www.cnblogs.com/czwbig/p/10035631.html">*     Java NIO 學(xué)習(xí)筆記(一)----概述,Channel/Buffer</a>* </pre>** @param filePath     源文件路徑* @param response     請求響應(yīng)* @param contentRange 內(nèi)容范圍*/public static void copyByNio(Path filePath, HttpServletResponse response, ContentRange contentRange) {String fileName = filePath.getFileName().toString();InputStream inputStream = null;OutputStream outputStream = null;try {RandomAccessFile randomAccessFile = new RandomAccessFile(filePath.toFile(), "r");randomAccessFile.seek(contentRange.getStart());inputStream = Channels.newInputStream(randomAccessFile.getChannel());outputStream = new BufferedOutputStream(response.getOutputStream(), BUFFER_SIZE);StreamProgress streamProgress = new StreamProgressImpl(fileName);long transmitted = NioUtil.copyByNIO(inputStream, outputStream,BUFFER_SIZE, streamProgress);log.info("file download complete, fileName={}, transmitted={}", fileName, transmitted);} catch (ClientAbortException | IORuntimeException e) {// 捕獲此異常表示用戶停止下載log.warn("client stop file download, fileName={}", fileName);} catch (Exception e) {log.error("file download error, fileName={}", fileName, e);} finally {IoUtil.close(outputStream);IoUtil.close(inputStream);}}/*** <pre>* <a href="https://blog.csdn.net/lovequanquqn/article/details/104562945">*     SpringBoot Java實(shí)現(xiàn)Http方式分片下載斷點(diǎn)續(xù)傳+實(shí)現(xiàn)H5大視頻漸進(jìn)式播放</a>* SpringBoot 實(shí)現(xiàn)Http分片下載斷點(diǎn)續(xù)傳,從而實(shí)現(xiàn)H5頁面的大視頻播放問題,實(shí)現(xiàn)漸進(jìn)式播放,每次只播放需要播放的內(nèi)容就可以了,不需要加載整個(gè)文件到內(nèi)存中。* 二、Http分片下載斷點(diǎn)續(xù)傳實(shí)現(xiàn)* 四、緩存文件定時(shí)刪除任務(wù)* </pre>*/public static void copy(Path filePath, HttpServletResponse response, ContentRange contentRange) {String fileName = filePath.getFileName().toString();// 要下載的長度long contentLength = contentRange.applyAsContentLength();BufferedOutputStream outputStream = null;RandomAccessFile randomAccessFile = null;// 已傳送數(shù)據(jù)大小long transmitted = 0;try {randomAccessFile = new RandomAccessFile(filePath.toFile(), "r");randomAccessFile.seek(contentRange.getStart());outputStream = new BufferedOutputStream(response.getOutputStream(), BUFFER_SIZE);// 把數(shù)據(jù)讀取到緩沖區(qū)中byte[] buffer = new byte[BUFFER_SIZE];int len = BUFFER_SIZE;//warning:判斷是否到了最后不足4096(buffer的length)個(gè)byte這個(gè)邏輯((transmitted + len) <= contentLength)要放前面//不然會會先讀取randomAccessFile,造成后面讀取位置出錯(cuò);while ((transmitted + len) <= contentLength && (len = randomAccessFile.read(buffer)) != -1) {outputStream.write(buffer, 0, len);transmitted += len;log.info("fileName={}, transmitted={}", fileName, transmitted);}//處理不足buffer.length部分if (transmitted < contentLength) {len = randomAccessFile.read(buffer, 0, (int) (contentLength - transmitted));outputStream.write(buffer, 0, len);transmitted += len;log.info("fileName={}, transmitted={}", fileName, transmitted);}log.info("file download complete, fileName={}, transmitted={}", fileName, transmitted);} catch (ClientAbortException e) {// 捕獲此異常表示用戶停止下載log.warn("client stop file download, fileName={}, transmitted={}", fileName, transmitted);} catch (Exception e) {log.error("file download error, fileName={}, transmitted={}", fileName, transmitted, e);} finally {IoUtil.close(outputStream);IoUtil.close(randomAccessFile);}}/*** 通過數(shù)據(jù)傳輸通道和緩沖區(qū)讀取文件數(shù)據(jù)。* <pre>* 當(dāng)文件長度超過{@link Integer#MAX_VALUE}時(shí),* 使用{@link FileChannel#map(FileChannel.MapMode, long, long)}報(bào)如下異常。* java.lang.IllegalArgumentException: Size exceeds Integer.MAX_VALUE*   at sun.nio.ch.FileChannelImpl.map(FileChannelImpl.java:863)*   at com.example.insurance.controller.ResourceController.download(ResourceController.java:200)* </pre>** @param filePath     源文件路徑* @param response     請求響應(yīng)* @param contentRange 內(nèi)容范圍*/public static void copyByChannelAndBuffer(Path filePath, HttpServletResponse response, ContentRange contentRange) {String fileName = filePath.getFileName().toString();// 要下載的長度long contentLength = contentRange.applyAsContentLength();BufferedOutputStream outputStream = null;FileChannel inChannel = null;// 已傳送數(shù)據(jù)大小long transmitted = 0;long firstBytePos = contentRange.getStart();long fileLength = contentRange.getLength();try {inChannel = FileChannel.open(filePath, StandardOpenOption.READ, StandardOpenOption.WRITE);// 建立直接緩沖區(qū)MappedByteBuffer inMap = inChannel.map(FileChannel.MapMode.READ_ONLY, firstBytePos, fileLength);outputStream = new BufferedOutputStream(response.getOutputStream(), BUFFER_SIZE);// 把數(shù)據(jù)讀取到緩沖區(qū)中byte[] buffer = new byte[BUFFER_SIZE];int len = BUFFER_SIZE;// warning:判斷是否到了最后不足4096(buffer的length)個(gè)byte這個(gè)邏輯((transmitted + len) <= contentLength)要放前面// 不然會會先讀取file,造成后面讀取位置出錯(cuò)while ((transmitted + len) <= contentLength) {inMap.get(buffer);outputStream.write(buffer, 0, len);transmitted += len;log.info("fileName={}, transmitted={}", fileName, transmitted);}// 處理不足buffer.length部分if (transmitted < contentLength) {len = (int) (contentLength - transmitted);buffer = new byte[len];inMap.get(buffer);outputStream.write(buffer, 0, len);transmitted += len;log.info("fileName={}, transmitted={}", fileName, transmitted);}log.info("file download complete, fileName={}, transmitted={}", fileName, transmitted);} catch (ClientAbortException e) {// 捕獲此異常表示用戶停止下載log.warn("client stop file download, fileName={}, transmitted={}", fileName, transmitted);} catch (Exception e) {log.error("file download error, fileName={}, transmitted={}", fileName, transmitted, e);} finally {IoUtil.close(outputStream);IoUtil.close(inChannel);}}}

ContentRange

package com.example.insurance.common;import lombok.AllArgsConstructor;
import lombok.Getter;/*** 內(nèi)容范圍對象* <pre>* <a href="https://www.rfc-editor.org/rfc/rfc7233#section-4.2">*     4.2. Content-Range - HTTP/1.1 Range Requests</a>* Content-Range: "bytes" first-byte-pos "-" last-byte-pos  "/" complete-length** For example:* Content-Range: bytes 0-499/1234* </pre>** @see org.apache.catalina.servlets.DefaultServlet.Range*/
@Getter
@AllArgsConstructor
public class ContentRange {/*** 第一個(gè)字節(jié)的位置*/private final long start;/*** 最后一個(gè)字節(jié)的位置*/private long end;/*** 內(nèi)容完整的長度/總長度*/private final long length;public static final String BYTES_STRING = "bytes";/*** 組裝內(nèi)容范圍的響應(yīng)頭。* <pre>* <a href="https://www.rfc-editor.org/rfc/rfc7233#section-4.2">*     4.2. Content-Range - HTTP/1.1 Range Requests</a>* Content-Range: "bytes" first-byte-pos "-" last-byte-pos  "/" complete-length** For example:* Content-Range: bytes 0-499/1234* </pre>** @return 內(nèi)容范圍的響應(yīng)頭*/public String toContentRange() {return BYTES_STRING + ' ' + start + '-' + end + '/' + length;
//        return "bytes " + start + "-" + end + "/" + length;}/*** 計(jì)算內(nèi)容完整的長度/總長度。** @return 內(nèi)容完整的長度/總長度*/public long applyAsContentLength() {return end - start + 1;}/*** Validate range.** @return true if the range is valid, otherwise false*/public boolean validate() {if (end >= length) {end = length - 1;}return (start >= 0) && (end >= 0) && (start <= end) && (length > 0);}@Overridepublic String toString() {return "firstBytePos=" + start +", lastBytePos=" + end +", fileLength=" + length;}
}

StreamProgressImpl

package com.example.insurance.common;import cn.hutool.core.io.StreamProgress;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;/*** 數(shù)據(jù)流進(jìn)度條*/
@Slf4j
@AllArgsConstructor
public class StreamProgressImpl implements StreamProgress {private final String fileName;@Overridepublic void start() {log.info("start progress {}", fileName);}@Overridepublic void progress(long total, long progressSize) {log.debug("progress {}, total={}, progressSize={}", fileName, total, progressSize);}@Overridepublic void finish() {log.info("finish progress {}", fileName);}
}

MediaContentUtil

package com.example.insurance.common;import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;/*** 文件內(nèi)容輔助方法集*/
public final class MediaContentUtil {public static String filePath() {String osName = System.getProperty("os.name");String filePath = "/data/files/";if (osName.startsWith("Windows")) {filePath = "D:\" + filePath;}
//        else if (osName.startsWith("Linux")) {
//            filePath = MediaContentConstant.FILE_PATH;
//        }else if (osName.startsWith("Mac") || osName.startsWith("Linux")) {filePath = "/home/admin" + filePath;}return filePath;}public static String encode(String fileName) {return URLEncoder.encode(fileName, StandardCharsets.UTF_8);}public static String decode(String fileName) {return URLDecoder.decode(fileName, StandardCharsets.UTF_8);}
}

實(shí)現(xiàn)示例2

代碼實(shí)現(xiàn)

(1)客戶端需要實(shí)現(xiàn)以下功能
  • 建立連接:客戶端需要連接服務(wù)端,并建立連接。
  • 分塊傳輸文件:客戶端需要將文件分成若干塊,并逐塊傳輸。在傳輸中,每個(gè)塊傳輸完成后,需要將已傳輸?shù)奈恢冒l(fā)送給服務(wù)端,以便服務(wù)端記錄傳輸位置。
  • 計(jì)算MD5值:在傳輸完成后,客戶端需要計(jì)算文件的MD5值,以確保傳輸?shù)耐暾浴?/li>
  • 與服務(wù)端比較MD5值:在計(jì)算出MD5值后,客戶端需要將MD5值發(fā)送給服務(wù)端,并與服務(wù)端返回的MD5值比較,以確保傳輸?shù)耐暾浴?/li>
(2)服務(wù)端需要實(shí)現(xiàn)以下功能
  • 建立連接:服務(wù)端需要等待客戶端連接,并建立連接。
  • 接收文件:服務(wù)端需要接收客戶端傳輸?shù)奈募?。在接收文件時(shí),需要記錄傳輸?shù)奈恢?#xff0c;并在傳輸中斷后繼續(xù)接收文件。
  • 計(jì)算MD5值:在接收完成后,服務(wù)端需要計(jì)算文件的MD5值,以確保傳輸?shù)耐暾浴?/li>
  • 返回MD5值:在計(jì)算出MD5值后,服務(wù)端需要將MD5值返回給客戶端。
1.編寫客戶端代碼

在客戶端中,我們需要實(shí)現(xiàn)以下功能:

  • 建立連接:使用Java的Socket類建立與服務(wù)端的連接。
  • 分塊傳輸文件:將文件分成若干塊,并逐塊傳輸。在傳輸中,每個(gè)塊傳輸完成后,需要將已傳輸?shù)奈恢冒l(fā)送給服務(wù)端,以便服務(wù)端記錄傳輸位置。
  • 計(jì)算MD5值:在傳輸完成后,計(jì)算文件的MD5值,以確保傳輸?shù)耐暾浴?/li>
  • 與服務(wù)端比較MD5值:將MD5值發(fā)送給服務(wù)端,并與服務(wù)端返回的MD5值比較,以確保傳輸?shù)耐暾浴?/li>

以下是客戶端代碼的實(shí)現(xiàn):

@RestController
@RequestMapping("/file")
public class FileController {@PostMapping("/upload")public ResponseEntity<?> uploadFile(@RequestParam("file") MultipartFile file,@RequestParam("fileName") String fileName,@RequestParam("startPosition") long startPosition) {try {            // 建立連接Socket socket = new Socket("localhost", 8080);OutputStream outputStream = socket.getOutputStream();ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);// 分塊傳輸文件FileInputStream fileInputStream = (FileInputStream) file.getInputStream();fileInputStream.skip(startPosition);byte[] buffer = new byte[1024];int len;while ((len = fileInputStream.read(buffer)) != -1) {outputStream.write(buffer, 0, len);}// 計(jì)算MD5值fileInputStream.getChannel().position(0);String md5 = DigestUtils.md5Hex(fileInputStream);// 與服務(wù)端比較MD5值InputStream inputStream = socket.getInputStream();ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);String serverMd5 = (String) objectInputStream.readObject();if (!md5.equals(serverMd5)) {throw new RuntimeException("MD5值不匹配");}// 關(guān)閉連接objectOutputStream.close();outputStream.close();socket.close();} catch (Exception e) {e.printStackTrace();return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());}return ResponseEntity.ok().build();}
}
2.編寫服務(wù)端代碼

在服務(wù)端中,我們需要實(shí)現(xiàn)以下功能:

  • 建立連接:使用Java的ServerSocket類等待客戶端連接,并建立連接。
  • 接收文件:接收客戶端傳輸?shù)奈募T诮邮瘴募r(shí),需要記錄傳輸?shù)奈恢?#xff0c;并在傳輸中斷后繼續(xù)接收文件。
  • 計(jì)算MD5值:在接收完成后,計(jì)算文件的MD5值,以確保傳輸?shù)耐暾浴?/li>
  • 返回MD5值:將MD5值返回給客戶端。

以下是服務(wù)端代碼的實(shí)現(xiàn):

@RestController
@RequestMapping("/file")
public class FileController {private final String FILE_PATH = "/tmp/upload/";@PostMapping("/upload")public ResponseEntity<?> uploadFile(HttpServletRequest request, @RequestParam("fileName") String fileName) {try {// 建立連接            ServerSocket serverSocket = new ServerSocket(8080);Socket socket = serverSocket.accept();InputStream inputStream = socket.getInputStream();ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);// 接收文件            String filePath = FILE_PATH + fileName;RandomAccessFile randomAccessFile = new RandomAccessFile(filePath, "rw");long startPosition = randomAccessFile.length();randomAccessFile.seek(startPosition);byte[] buffer = new byte[1024];int len;while ((len = inputStream.read(buffer)) != -1) {randomAccessFile.write(buffer, 0, len);}   // 計(jì)算MD5值        FileInputStream fileInputStream = new FileInputStream(filePath);String md5 = DigestUtils.md5Hex(fileInputStream);// 返回MD5值        OutputStream outputStream = socket.getOutputStream();ObjectOutputStream objectOutputStream = new ObjectOutputStream(outputStream);objectOutputStream.writeObject(md5);        // 關(guān)閉連objectInputStream.close();inputStream.close();randomAccessFile.close();socket.close();serverSocket.close();} catch (Exception e) {e.printStackTrace();return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());}return ResponseEntity.ok().build();}
}
3. 編寫前端代碼

在前端中,我們需要實(shí)現(xiàn)以下功能:

  • 選擇文件:提供一個(gè)文件選擇框,讓用戶選擇要上傳的文件。
  • 分塊上傳:將文件分塊上傳到服務(wù)器。在上傳過程中,需要記錄上傳的位置,并在上傳中斷后繼續(xù)上傳。

以下是前端代碼的實(shí)現(xiàn):

<html>
<head><meta charset="UTF-8"><title>Spring Boot File Upload</title><script src="https://cdn.bootcss.com/jquery/3.3.1/jquery.min.js"></script>
</head>
<body><input type="file" id="file">
<button onclick="upload()">Upload</button>
<script>    var file;
var startPosition = 0;
$('#file').on('change', function () {file = this.files[0];
});function upload() {if (!file) {alert('Please select a file!');return;}var formData = new FormData();formData.append('file', file);formData.append('fileName', file.name);formData.append('startPosition', startPosition);$.ajax({url: '/file/upload',type: 'post',data: formData,cache: false,processData: false,contentType: false,success: function () {alert('Upload completed!');},error: function (xhr) {alert(xhr.responseText);},xhr: function () {var xhr = $.ajaxSettings.xhr();xhr.upload.onprogress = function (e) {if (e.lengthComputable) {var percent = e.loaded / e.total * 100;console.log('Upload percent: ' + percent.toFixed(2) + '%');}};return xhr;}});
}</script>
</body>
</html>

總結(jié)

本文介紹了如何使用Spring Boot實(shí)現(xiàn)大文件斷點(diǎn)續(xù)傳。在實(shí)現(xiàn)中,我們使用了Java的RandomAccessFile類來實(shí)現(xiàn)文件的分塊上傳和斷點(diǎn)續(xù)傳,使用了Spring Boot的RestController注解來實(shí)現(xiàn)Web服務(wù)的開發(fā),使用了jQuery的Ajax函數(shù)來實(shí)現(xiàn)前端頁面的開發(fā)。

在實(shí)際開發(fā)中,需要注意以下幾點(diǎn)

  • 上傳文件的大小和分塊的大小需要根據(jù)實(shí)際情況進(jìn)行設(shè)置,以確保上傳速度和服務(wù)器的穩(wěn)定性。
  • 在上傳過程中,需要對異常情況進(jìn)行處理,以確保程序的健壯性。
  • 在上傳完成后,需要對上傳的文件進(jìn)行校驗(yàn),以確保傳輸?shù)耐暾浴?/li>
http://www.risenshineclean.com/news/22698.html

相關(guān)文章:

  • 網(wǎng)站被攻擊空間關(guān)了怎么辦網(wǎng)站seo快速
  • 教做視頻的網(wǎng)站優(yōu)化系統(tǒng)的軟件
  • 南京做機(jī)床的公司網(wǎng)站智能優(yōu)化大師下載
  • 有那些專門做外貿(mào)的網(wǎng)站呀seo的優(yōu)化方案
  • 做彩票網(wǎng)站需要什么收錢的百度競價(jià)有點(diǎn)擊無轉(zhuǎn)化
  • 公司做網(wǎng)站需要什么免費(fèi)入駐的賣貨平臺有哪些
  • 安卓是哪個(gè)公司開發(fā)的seo優(yōu)化自動點(diǎn)擊軟件
  • 網(wǎng)站優(yōu)化最牛逼的軟件最有效的惡意點(diǎn)擊軟件
  • 怎么建立自己網(wǎng)站 asp如何進(jìn)行關(guān)鍵詞分析
  • 做ppt用什么網(wǎng)站北京優(yōu)化seo排名優(yōu)化
  • 教育公司 網(wǎng)站建設(shè)愛客crm
  • 做網(wǎng)站怎么備案seo綜合查詢什么意思
  • 專門做配電箱的網(wǎng)站b站推廣網(wǎng)站mmm
  • 多語種網(wǎng)站營銷寧波seo關(guān)鍵詞優(yōu)化制作
  • 怎樣弄網(wǎng)站的導(dǎo)航欄百度競價(jià)是什么意思
  • 上饒網(wǎng)站開發(fā)有哪些網(wǎng)絡(luò)推廣平臺
  • 專門做活動的網(wǎng)站怎么注冊一個(gè)自己的網(wǎng)站
  • 視頻網(wǎng)站哪個(gè)做的好網(wǎng)絡(luò)營銷意思
  • 企業(yè)網(wǎng)站建設(shè)網(wǎng)頁推廣軟文300字
  • 網(wǎng)站怎么做評估北京seo優(yōu)化排名
  • 網(wǎng)站建設(shè)員官網(wǎng)設(shè)計(jì)比較好看的網(wǎng)站
  • 怎樣辦網(wǎng)站seo關(guān)鍵字排名
  • 攝影師個(gè)人網(wǎng)站制作自動點(diǎn)擊器免費(fèi)下載
  • 新建設(shè)電影院 網(wǎng)站開網(wǎng)站需要什么流程
  • php畢業(yè)設(shè)計(jì)二手網(wǎng)站怎么做寧波網(wǎng)站建設(shè)推廣平臺
  • 中國菲律賓南海開戰(zhàn)seo對網(wǎng)絡(luò)推廣的作用是
  • 韶關(guān)網(wǎng)站開發(fā)seo網(wǎng)站搭建是什么
  • 新疆網(wǎng)站黨建設(shè)備百度小說風(fēng)云榜排行榜官網(wǎng)
  • 個(gè)人做電子商務(wù)網(wǎng)站seo研究中心南寧線下
  • 專業(yè)注冊公司代辦海東地區(qū)谷歌seo網(wǎng)絡(luò)優(yōu)化