最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

java實(shí)現(xiàn)文件分片上傳并且斷點(diǎn)續(xù)傳的示例代碼

 更新時(shí)間:2023年05月25日 09:12:36   作者:NameGo  
本文主要介紹了java實(shí)現(xiàn)文件分片上傳并且斷點(diǎn)續(xù)傳的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧

一、簡單的分片上傳

針對第一個(gè)問題,如果文件過大,上傳到一半斷開了,若重新開始上傳的話,會很消耗時(shí)間,并且你也并不知道距離上次斷開時(shí),已經(jīng)上傳到哪一部分了。因此我們應(yīng)該先對大文件進(jìn)行分片處理,防止上面提到的問題。

前端代碼:

<!DOCTYPE html>
<html>
<head>
? ? <title>文件上傳示例</title>
? ? <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
</head>
<body>
<form>
? ? <input type="file" id="fileInput" multiple>
? ? <button type="button" onclick="upload()" >上傳</button>
</form>
<script>
? ? function upload() {
? ? ? ? var fileInput = document.getElementById('fileInput');
? ? ? ? var fileName = document.getElementById("fileInput").files[0].name;
? ? ? ? var files = fileInput.files;
? ? ? ? var chunkSize = 1024 * 10; // 每個(gè)塊的大小為10KB
? ? ? ? var totalChunks = Math.ceil(files[0].size / chunkSize); // 文件總塊數(shù)
? ? ? ? var currentChunk = 0; // 當(dāng)前塊數(shù)
? ? ? ? // 分片上傳文件
? ? ? ? function uploadChunk() {
? ? ? ? ? ? var xhr = new XMLHttpRequest();
? ? ? ? ? ? var formData = new FormData();
? ? ? ? ? ? // 將當(dāng)前塊數(shù)和總塊數(shù)添加到formData中
? ? ? ? ? ? formData.append('currentChunk', currentChunk);
? ? ? ? ? ? formData.append('totalChunks', totalChunks);
? ? ? ? ? ? formData.append('fileName',fileName);
? ? ? ? ? ? // 計(jì)算當(dāng)前塊在文件中的偏移量和長度
? ? ? ? ? ? var start = currentChunk * chunkSize;
? ? ? ? ? ? var end = Math.min(files[0].size, start + chunkSize);
? ? ? ? ? ? var chunk = files[0].slice(start, end);
? ? ? ? ? ? // 添加當(dāng)前塊到formData中
? ? ? ? ? ? formData.append('chunk', chunk);
? ? ? ? ? ? // 發(fā)送分片到后端
? ? ? ? ? ? xhr.open('POST', '/file/upload');
? ? ? ? ? ? xhr.send(formData);
? ? ? ? ? ? xhr.onload = function() {
? ? ? ? ? ? ? ? // 更新當(dāng)前塊數(shù)
? ? ? ? ? ? ? ? currentChunk++;
? ? ? ? ? ? ? ? // 如果還有未上傳的塊,則繼續(xù)上傳
? ? ? ? ? ? ? ? if (currentChunk < totalChunks) {
? ? ? ? ? ? ? ? ? ? uploadChunk();
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? // 所有塊都上傳完畢,進(jìn)行文件合并
? ? ? ? ? ? ? ? ? ? mergeChunks(fileName);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? // 合并所有分片
? ? ? ? function mergeChunks() {
? ? ? ? ? ? var xhr = new XMLHttpRequest();
? ? ? ? ? ? xhr.open("POST", "/file/merge", true);
? ? ? ? ? ? xhr.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
? ? ? ? ? ? xhr.onreadystatechange = function() {
? ? ? ? ? ? ? ? if (xhr.readyState === 4) {
? ? ? ? ? ? ? ? ? ? if (xhr.status === 200) {
? ? ? ? ? ? ? ? ? ? ? ? console.log("文件上傳完成:", xhr.responseText);
? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? console.error(xhr.responseText);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
? ? ? ? ? ? xhr.send("fileName=" + fileName);
? ? ? ? }
? ? ? ? // 開始上傳
? ? ? ? uploadChunk();
? ? }
</script>
</body>
</html>

ps:以上代碼使用了html+js完成,請求是使用了xhr來發(fā)送請求。其中xhr.open的地址為自己本地的接口地址。由于平時(shí)測試并不需要真正上傳大型文件,所以每個(gè)分片的大小定義為10KB,以此模擬大文件上傳。

后端代碼:

@RestController
@RequestMapping("/file")
public class FileController {
? ? @Autowired
? ? private ResourceLoader resourceLoader;
? ? @Value("${my.config.savePath}")
? ? private String uploadPath;
? ? private Map<String, List<File>> chunksMap = new ConcurrentHashMap<>();
? ? @PostMapping("/upload")
? ? public void upload(@RequestParam int currentChunk, @RequestParam int totalChunks,
? ? ? ? ? ? ? ? ? ? ? ?@RequestParam MultipartFile chunk,@RequestParam String fileName) throws IOException {
? ? ? ? // 將分片保存到臨時(shí)文件夾中
? ? ? ? String chunkName = chunk.getOriginalFilename() + "." + currentChunk;
? ? ? ? File chunkFile = new File(uploadPath, chunkName);
? ? ? ? chunk.transferTo(chunkFile);
? ? ? ? // 記錄分片上傳狀態(tài)
? ? ? ? List<File> chunkList = chunksMap.get(fileName);
? ? ? ? if (chunkList == null) {
? ? ? ? ? ? chunkList = new ArrayList<>(totalChunks);
? ? ? ? ? ? chunksMap.put(fileName, chunkList);
? ? ? ? }
? ? ? ? chunkList.add(chunkFile);
? ? }
? ? @PostMapping("/merge")
? ? public String merge(@RequestParam String fileName) throws IOException {
? ? ? ? // 獲取所有分片,并按照分片的順序?qū)⑺鼈兒喜⒊梢粋€(gè)文件
? ? ? ? List<File> chunkList = chunksMap.get(fileName);
? ? ? ? if (chunkList == null || chunkList.size() == 0) {
? ? ? ? ? ? throw new RuntimeException("分片不存在");
? ? ? ? }
? ? ? ? File outputFile = new File(uploadPath, fileName);
? ? ? ? try (FileChannel outChannel = new FileOutputStream(outputFile).getChannel()) {
? ? ? ? ? ? for (int i = 0; i < chunkList.size(); i++) {
? ? ? ? ? ? ? ? try (FileChannel inChannel = new FileInputStream(chunkList.get(i)).getChannel()) {
? ? ? ? ? ? ? ? ? ? inChannel.transferTo(0, inChannel.size(), outChannel);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? chunkList.get(i).delete(); // 刪除分片
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? chunksMap.remove(fileName); // 刪除記錄
? ? ? ? // 獲取文件的訪問URL
? ? ? ? Resource resource =?
? ? ? ? ?? ??? ??? ?resourceLoader.getResource("file:" + uploadPath + fileName); //由于是本地文件,所以開頭是"file",如果是服務(wù)器,請改成自己服務(wù)器前綴
? ? ? ? return resource.getURI().toString();
? ? }
}

 ps: 使用一個(gè)map記錄上傳了哪些分片,這里將分片存在了本地的文件夾,等到分片都上傳完成后合并并刪除分片。用ConcurrentHashMap代替HashMap是因?yàn)樗诙嗑€程下是安全的。
以上只是一個(gè)簡單的文件上傳代碼,但是只要在這上面另做修改就可以解決上面提到的問題。

二、解決問題

1. 怎么避免大量的硬盤讀寫

上面代碼有一個(gè)弊端,就是將分片的內(nèi)容存在了本地的文件夾里。而且在合并的時(shí)候判斷上傳是否完全也是從文件夾讀取文件的。對磁盤的大量讀寫操作不僅速度慢,還會導(dǎo)致服務(wù)器崩潰,因此下面代碼使用了redis來存儲分片信息,避免對磁盤過多讀寫。(你也可以使用mysql或者其他中間件來存儲信息,由于讀寫盡量不要在mysql,所以我使用了redis)。

2.目標(biāo)文件過大,如果在上傳過程中斷開了怎么辦

使用redis來存儲分片內(nèi)容,當(dāng)斷開后,文件信息還是存儲在redis中,用戶再次上傳時(shí),檢測redis是否有該分片的內(nèi)容,如果有則跳過。

3. 前端頁面上傳的文件數(shù)據(jù)與原文件數(shù)據(jù)不一致該如何發(fā)現(xiàn)

前端在調(diào)用上傳接口時(shí),先計(jì)算文件的校驗(yàn)和,然后將文件和校驗(yàn)和一并傳給后端,后端對文件再計(jì)算一次校驗(yàn)和,兩個(gè)校驗(yàn)和進(jìn)行對比,如果相等,則說明數(shù)據(jù)一致,如果不一致則報(bào)錯(cuò),讓前端重新上傳該片段。
js計(jì)算校驗(yàn)和代碼:

// 計(jì)算文件的 SHA-256 校驗(yàn)和
? ? function calculateHash(fileChunk) {
? ? ? ? return new Promise((resolve, reject) => {
? ? ? ? ? ? const blob = new Blob([fileChunk]);
? ? ? ? ? ? const reader = new FileReader();
? ? ? ? ? ? reader.readAsArrayBuffer(blob);
? ? ? ? ? ? reader.onload = () => {
? ? ? ? ? ? ? ? const arrayBuffer = reader.result;
? ? ? ? ? ? ? ? const crypto = window.crypto || window.msCrypto;
? ? ? ? ? ? ? ? const digest = crypto.subtle.digest("SHA-256", arrayBuffer);
? ? ? ? ? ? ? ? digest.then(hash => {
? ? ? ? ? ? ? ? ? ? const hashArray = Array.from(new Uint8Array(hash));
? ? ? ? ? ? ? ? ? ? const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
? ? ? ? ? ? ? ? ? ? resolve(hashHex);
? ? ? ? ? ? ? ? });
? ? ? ? ? ? };
? ? ? ? ? ? reader.onerror = () => {
? ? ? ? ? ? ? ? reject(new Error('Failed to calculate hash'));
? ? ? ? ? ? };
? ? ? ? });
? ? }
? ? public static String calculateHash(byte[] fileChunk) throws Exception {
? ? ? ? MessageDigest md = MessageDigest.getInstance("SHA-256");
? ? ? ? md.update(fileChunk);
? ? ? ? byte[] hash = md.digest();
? ? ? ? ByteBuffer byteBuffer = ByteBuffer.wrap(hash);
? ? ? ? StringBuilder hexString = new StringBuilder();
? ? ? ? while (byteBuffer.hasRemaining()) {
? ? ? ? ? ? hexString.append(String.format("%02x", byteBuffer.get()));
? ? ? ? }
? ? ? ? return hexString.toString();
? ? }

注意點(diǎn):

這里前端和后端計(jì)算校驗(yàn)和的算法一定要是一致的,不然得不到相同的結(jié)果。

在前端中使用了crypto對文件進(jìn)行計(jì)算,需要引入相關(guān)的js。

你可以使用script引入也可以直接下載js

<script src="https://cdn.bootcss.com/crypto-js/3.1.9-1/crypto-js.min.js"></script>

crypto的下載地址

4. 上傳過程中如果斷開了應(yīng)該如何判斷哪些分片沒有上傳

對redis檢測哪個(gè)分片的下標(biāo)不存在,若不存在則存入list,最后將list返回給前端

    boolean allChunksUploaded = true;
    List<Integer> missingChunkIndexes = new ArrayList<>();
     for (int i = 0; i < hashMap.size(); i++) {
         if (!hashMap.containsKey(String.valueOf(i))) {
             allChunksUploaded = false;
             missingChunkIndexes.add(i);
         }
     }
     if (!allChunksUploaded) {
         return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(missingChunkIndexes);
     }

三、完整代碼

1、引入依賴

<dependency>
      <groupId>io.lettuce</groupId>
    <artifactId>lettuce-core</artifactId>
    <version>6.1.4.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

lettuce是一個(gè)Redis客戶端,你也可以不引入,直接使用redisTemplat就行了

2、前端代碼

<!DOCTYPE html>
<html lang="en">
<head>
? ? <meta charset="UTF-8">
? ? <title>File Upload Demo</title>
</head>
<body>
<input type="file" id="fileInput" multiple>
<button type="button" onclick="uploadFile()" >上傳</button>
<div id="progressBar"></div>
<script src="https://cdn.bootcss.com/crypto-js/3.1.9-1/crypto-js.min.js"></script>
<script>
? ? var fileId = "";
? ? var fileName = null;
? ? var file;
? ? const chunkSize = 1024 * 10; // 每個(gè)分片的大小10KB
? ? async function uploadFile() {
? ? ? ? var fileInput = document.getElementById('fileInput');
? ? ? ? file = fileInput.files[0];
? ? ? ? fileName = document.getElementById("fileInput").files[0].name;
? ? ? ? // 分片上傳文件
? ? ? ? const chunks = Math.ceil(file.size / chunkSize);
? ? ? ? for (let i = 0; i < chunks; i++) {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? await uploadChunk(file, i);
? ? ? ? ? ? } catch (error) {
? ? ? ? ? ? ? ? console.error('Failed to upload chunk', i, error);
? ? ? ? ? ? ? ? // 如果上傳失敗,則嘗試恢復(fù)上傳
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? await uploadChunk(file, i);
? ? ? ? ? ? ? ? } catch (error) {
? ? ? ? ? ? ? ? ? ? console.error('Failed to resume upload', i, error);
? ? ? ? ? ? ? ? ? ? return;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? // 合并文件
? ? ? ? try {
? ? ? ? ? ? const fileUrl = await mergeFile();
? ? ? ? ? ? console.log('File URL:', fileUrl);
? ? ? ? } catch (error) {
? ? ? ? ? ? console.error('Failed to merge file', error);
? ? ? ? }
? ? }
? ? function uploadChunk(file, chunkIndex) {
? ? ? ? return new Promise((resolve, reject) => {
? ? ? ? ? ? let fileTemp = file.slice(chunkIndex * chunkSize, (chunkIndex + 1) * chunkSize);
? ? ? ? ? ? var myPromise = calculateHash(fileTemp);
? ? ? ? ? ? myPromise.then(result =>{
? ? ? ? ? ? ? ? const formData = new FormData();
? ? ? ? ? ? ? ? formData.append('chunk',fileTemp);
? ? ? ? ? ? ? ? formData.append('chunkIndex', chunkIndex);
? ? ? ? ? ? ? ? formData.append('chunkChecksum', result);
? ? ? ? ? ? ? ? formData.append('chunkSize', chunkSize);
? ? ? ? ? ? ? ? formData.append('fileId',fileId);
? ? ? ? ? ? ? ? const xhr = new XMLHttpRequest();
? ? ? ? ? ? ? ? xhr.open('POST', '/hospital/file2/upload', true);
? ? ? ? ? ? ? ? xhr.onload = () => {
? ? ? ? ? ? ? ? ? ? if (xhr.status === 200) {
? ? ? ? ? ? ? ? ? ? ? ? resolve(xhr.response);
? ? ? ? ? ? ? ? ? ? ? ? fileId = xhr.responseText
? ? ? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? ? ? reject(xhr.statusText);
? ? ? ? ? ? ? ? ? ? }
? ? ? ? ? ? ? ? };
? ? ? ? ? ? ? ? xhr.onerror = () => {
? ? ? ? ? ? ? ? ? ? reject(xhr.statusText);
? ? ? ? ? ? ? ? };
? ? ? ? ? ? ? ? xhr.send(formData);
? ? ? ? ? ? })
? ? ? ? });
? ? }
? ? function mergeFile() {
? ? ? ? return new Promise((resolve, reject) => {
? ? ? ? ? ? const xhr = new XMLHttpRequest();
? ? ? ? ? ? const formData = new FormData();
? ? ? ? ? ? formData.append('fileId',fileId);
? ? ? ? ? ? formData.append('fileName',fileName);
? ? ? ? ? ? xhr.open('POST', '/hospital/file2/merge', true);
? ? ? ? ? ? xhr.onload = () => {
? ? ? ? ? ? ? ? if (xhr.status === 200) {
? ? ? ? ? ? ? ? ? ? resolve(xhr.response);
? ? ? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? ? ? reject(xhr.statusText);
? ? ? ? ? ? ? ? ? ? resume(xhr.response.replace(/\[|]/g,'').split(','));
? ? ? ? ? ? ? ? }
? ? ? ? ? ? };
? ? ? ? ? ? xhr.onerror = () => {
? ? ? ? ? ? ? ? reject(xhr.statusText);
? ? ? ? ? ? };
? ? ? ? ? ? xhr.send(formData);
? ? ? ? });
? ? }
? ? async function resume(list){
? ? ? ? for (let i = 0; i < list.length; i++) {
? ? ? ? ? ? try {
? ? ? ? ? ? ? ? await uploadChunk(file, i);
? ? ? ? ? ? } catch (error) {
? ? ? ? ? ? ? ? console.error('Failed to upload chunk', i, error);
? ? ? ? ? ? ? ? // 如果上傳失敗,則嘗試恢復(fù)上傳
? ? ? ? ? ? ? ? try {
? ? ? ? ? ? ? ? ? ? await uploadChunk(file, i);
? ? ? ? ? ? ? ? } catch (error) {
? ? ? ? ? ? ? ? ? ? console.error('Failed to resume upload', i, error);
? ? ? ? ? ? ? ? ? ? return;
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? }
? ? ? ? // 合并文件
? ? ? ? try {
? ? ? ? ? ? const fileUrl = await mergeFile();
? ? ? ? ? ? console.log('File URL:', fileUrl);
? ? ? ? } catch (error) {
? ? ? ? ? ? console.error('Failed to merge file', error);
? ? ? ? }
? ? }
? ? // 計(jì)算文件的 SHA-256 校驗(yàn)和
? ? function calculateHash(fileChunk) {
? ? ? ? return new Promise((resolve, reject) => {
? ? ? ? ? ? const blob = new Blob([fileChunk]);
? ? ? ? ? ? const reader = new FileReader();
? ? ? ? ? ? reader.readAsArrayBuffer(blob);
? ? ? ? ? ? reader.onload = () => {
? ? ? ? ? ? ? ? const arrayBuffer = reader.result;
? ? ? ? ? ? ? ? const crypto = window.crypto || window.msCrypto;
? ? ? ? ? ? ? ? const digest = crypto.subtle.digest("SHA-256", arrayBuffer);
? ? ? ? ? ? ? ? digest.then(hash => {
? ? ? ? ? ? ? ? ? ? const hashArray = Array.from(new Uint8Array(hash));
? ? ? ? ? ? ? ? ? ? const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
? ? ? ? ? ? ? ? ? ? resolve(hashHex);
? ? ? ? ? ? ? ? });
? ? ? ? ? ? };
? ? ? ? ? ? reader.onerror = () => {
? ? ? ? ? ? ? ? reject(new Error('Failed to calculate hash'));
? ? ? ? ? ? };
? ? ? ? });
? ? }
</script>
</body>
</html>

3、后端接口代碼

@RestController
@RequestMapping("/file2")
public class File2Controller {
? ? private static final String FILE_UPLOAD_PREFIX = "file_upload:";
? ? @Autowired
? ? private ResourceLoader resourceLoader;
? ? @Value("${my.config.savePath}")
? ? private String uploadPath;
? ? @Autowired
? ? private ThreadLocal<RedisConnection> redisConnectionThreadLocal;
// ? ?@Autowired
// ? ?private RedisTemplate redisTemplate;
? ? @PostMapping("/upload")
? ? public ResponseEntity<?> uploadFile(@RequestParam("chunk") MultipartFile chunk,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @RequestParam("chunkIndex") Integer chunkIndex,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @RequestParam("chunkSize") Integer chunkSize,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @RequestParam("chunkChecksum") String chunkChecksum,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? @RequestParam("fileId") String fileId) throws Exception {
? ? ? ? if (StringUtils.isBlank(fileId) || StringUtils.isEmpty(fileId)) {
? ? ? ? ? ? fileId = UUID.randomUUID().toString();
? ? ? ? }
? ? ? ? String key = FILE_UPLOAD_PREFIX + fileId;
? ? ? ? byte[] chunkBytes = chunk.getBytes();
? ? ? ? String actualChecksum = calculateHash(chunkBytes);
? ? ? ? if (!chunkChecksum.equals(actualChecksum)) {
? ? ? ? ? ? return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("Chunk checksum does not match");
? ? ? ? }
// ? ? ? ?if(!redisTemplate.opsForHash().hasKey(key,String.valueOf(chunkIndex))) {
// ? ? ? ? ? ?redisTemplate.opsForHash().put(key, String.valueOf(chunkIndex), chunkBytes);
// ? ? ? ?}
? ? ? ? RedisConnection connection = redisConnectionThreadLocal.get();
? ? ? ? Boolean flag = connection.hExists(key.getBytes(), String.valueOf(chunkIndex).getBytes());
? ? ? ? if (flag==null || flag == false) {
? ? ? ? ? ? connection.hSet(key.getBytes(), String.valueOf(chunkIndex).getBytes(), chunkBytes);
? ? ? ? }
? ? ? ? return ResponseEntity.ok(fileId);
? ? }
? ? public static String calculateHash(byte[] fileChunk) throws Exception {
? ? ? ? MessageDigest md = MessageDigest.getInstance("SHA-256");
? ? ? ? md.update(fileChunk);
? ? ? ? byte[] hash = md.digest();
? ? ? ? ByteBuffer byteBuffer = ByteBuffer.wrap(hash);
? ? ? ? StringBuilder hexString = new StringBuilder();
? ? ? ? while (byteBuffer.hasRemaining()) {
? ? ? ? ? ? hexString.append(String.format("%02x", byteBuffer.get()));
? ? ? ? }
? ? ? ? return hexString.toString();
? ? }
? ? @PostMapping("/merge")
? ? public ResponseEntity<?> mergeFile(@RequestParam("fileId") String fileId, @RequestParam("fileName") String fileName) throws IOException {
? ? ? ? String key = FILE_UPLOAD_PREFIX + fileId;
? ? ? ? RedisConnection connection = redisConnectionThreadLocal.get();
? ? ? ? try {
? ? ? ? ? ? Map<byte[], byte[]> chunkMap = connection.hGetAll(key.getBytes());
// ? ? ? ? ? ?Map chunkMap = redisTemplate.opsForHash().entries(key);
? ? ? ? ? ? if (chunkMap.isEmpty()) {
? ? ? ? ? ? ? ? return ResponseEntity.status(HttpStatus.NOT_FOUND).body("File not found");
? ? ? ? ? ? }
? ? ? ? ? ? Map<String,byte[]> hashMap = new HashMap<>();
? ? ? ? ? ? for(Map.Entry<byte[],byte[]> entry :chunkMap.entrySet()){
? ? ? ? ? ? ? ? hashMap.put((new String(entry.getKey())),entry.getValue());
? ? ? ? ? ? }
? ? ? ? ? ? // 檢測是否所有分片都上傳了
? ? ? ? ? ? boolean allChunksUploaded = true;
? ? ? ? ? ? List<Integer> missingChunkIndexes = new ArrayList<>();
? ? ? ? ? ? for (int i = 0; i < hashMap.size(); i++) {
? ? ? ? ? ? ? ? if (!hashMap.containsKey(String.valueOf(i))) {
? ? ? ? ? ? ? ? ? ? allChunksUploaded = false;
? ? ? ? ? ? ? ? ? ? missingChunkIndexes.add(i);
? ? ? ? ? ? ? ? }
? ? ? ? ? ? }
? ? ? ? ? ? if (!allChunksUploaded) {
? ? ? ? ? ? ? ? return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(missingChunkIndexes);
? ? ? ? ? ? }
? ? ? ? ? ? File outputFile = new File(uploadPath, fileName);
? ? ? ? ? ? boolean flag = mergeChunks(hashMap, outputFile);
? ? ? ? ? ? Resource resource = resourceLoader.getResource("file:" + uploadPath + fileName);
? ? ? ? ? ? if (flag == true) {
? ? ? ? ? ? ? ? connection.del(key.getBytes());
// ? ? ? ? ? ? ? ?redisTemplate.delete(key);
? ? ? ? ? ? ? ? return ResponseEntity.ok().body(resource.getURI().toString());
? ? ? ? ? ? } else {
? ? ? ? ? ? ? ? return ResponseEntity.status(555).build();
? ? ? ? ? ? }
? ? ? ? } catch (Exception e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
? ? ? ? }
? ? }
? ? private boolean mergeChunks(Map<String, byte[]> chunkMap, File destFile) {
? ? ? ? try (FileOutputStream outputStream = new FileOutputStream(destFile)) {
? ? ? ? ? ? // 將分片按照順序合并
? ? ? ? ? ? for (int i = 0; i < chunkMap.size(); i++) {
? ? ? ? ? ? ? ? byte[] chunkBytes = chunkMap.get(String.valueOf(i));
? ? ? ? ? ? ? ? outputStream.write(chunkBytes);
? ? ? ? ? ? }
? ? ? ? ? ? return true;
? ? ? ? } catch (IOException e) {
? ? ? ? ? ? e.printStackTrace();
? ? ? ? ? ? return false;
? ? ? ? }
? ? }
}

4、redis配置

@Configuration
public class RedisConfig {
? ? @Value("${spring.redis.host}")
? ? private String host;
? ? @Value("${spring.redis.port}")
? ? private int port;
? ? @Value("${spring.redis.password}")
? ? private String password;
? ? @Bean
? ? public RedisConnectionFactory redisConnectionFactory() {
? ? ? ? RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
? ? ? ? config.setHostName(host);
? ? ? ? config.setPort(port);
? ? ? ? config.setPassword(RedisPassword.of(password));
? ? ? ? return new LettuceConnectionFactory(config);
? ? }
? ? @Bean
? ? public ThreadLocal<RedisConnection> redisConnectionThreadLocal(RedisConnectionFactory redisConnectionFactory) {
? ? ? ? return ThreadLocal.withInitial(() -> redisConnectionFactory.getConnection());
? ? }
}

使用 redisConnectionThreadLocal 是為了避免多次建立連接,很耗時(shí)間

總結(jié)

以上就是該功能的完整代碼。使用代碼記得修改uploadPath,避免代碼找不到目錄路徑。在代碼最后,可以使用mysql對整個(gè)文件計(jì)算校驗(yàn)和,將校驗(yàn)和結(jié)果和文件名、文件大小、文件類型存入數(shù)據(jù)庫中,在下次大文件上傳前先判斷是否存在。若存在就不要上傳避免占用空間。

到此這篇關(guān)于java實(shí)現(xiàn)文件分片上傳并且斷點(diǎn)續(xù)傳的示例代碼的文章就介紹到這了,更多相關(guān)java 文件分片上傳內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • IntelliJIDEA中實(shí)現(xiàn)SpringBoot多實(shí)例運(yùn)行的兩種方式

    IntelliJIDEA中實(shí)現(xiàn)SpringBoot多實(shí)例運(yùn)行的兩種方式

    在微服務(wù)開發(fā)中,經(jīng)常需要同時(shí)啟動(dòng)多個(gè)服務(wù)實(shí)例進(jìn)行測試或模擬集群環(huán)境,?IntelliJ?IDEA?作為Java開發(fā)者常用工具,提供了靈活的多實(shí)例啟動(dòng)支持,本文將詳細(xì)介紹如何通過修改配置?和批量啟動(dòng)?兩種方式實(shí)現(xiàn)SpringBoot多實(shí)例運(yùn)行,并解決常見問題,需要的朋友可以參考下
    2025-03-03
  • 你可能真沒用過這些 IDEA 插件(建議收藏)

    你可能真沒用過這些 IDEA 插件(建議收藏)

    IDEA 全稱 IntelliJ IDEA,是java編程語言開發(fā)的集成環(huán)境。IntelliJ在業(yè)界被公認(rèn)為最好的java開發(fā)工具。這篇文章主要介紹 IDEA 必用插件的安裝及用法,需要的朋友可以參考下
    2020-08-08
  • Hibernate的Session_flush與隔離級別代碼詳解

    Hibernate的Session_flush與隔離級別代碼詳解

    這篇文章主要介紹了Hibernate的Session_flush與隔離級別代碼詳解,分享了相關(guān)代碼示例,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-02-02
  • java字符串的替換replace、replaceAll、replaceFirst的區(qū)別說明

    java字符串的替換replace、replaceAll、replaceFirst的區(qū)別說明

    這篇文章主要介紹了java字符串的替換replace、replaceAll、replaceFirst的區(qū)別說明,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-03-03
  • No ‘Access-Control-Allow-Origin‘ header is present跨域及解決

    No ‘Access-Control-Allow-Origin‘ header is&nb

    這篇文章主要介紹了No ‘Access-Control-Allow-Origin‘ header is present跨域及解決方案,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-02-02
  • Java多種文件復(fù)制方式以及效率對比分析

    Java多種文件復(fù)制方式以及效率對比分析

    本文總結(jié)了Java復(fù)制文件的多種方式,包括傳統(tǒng)的字節(jié)流、字符流、NIO系列、第三方包中的FileUtils等,并提供了不同方式的效率比較,同時(shí),還介紹了遍歷文件和文件夾的方法,并使用Swing進(jìn)行了簡單的圖形界面展示,通過實(shí)際測試,對比了不同方法的性能,提供了參考
    2025-11-11
  • java中 spring 定時(shí)任務(wù) 實(shí)現(xiàn)代碼

    java中 spring 定時(shí)任務(wù) 實(shí)現(xiàn)代碼

    java中 spring 定時(shí)任務(wù) 實(shí)現(xiàn)代碼,需要的朋友可以參考一下
    2013-03-03
  • Java Stream 的 forEachOrdered 與 forEach的區(qū)別與適用場景

    Java Stream 的 forEachOrdered 與 forE

    在Java Stream API中,forEach和forEachOrdered是兩個(gè)常用的終止操作,用于對流中的元素執(zhí)行迭代處理,本文將從多個(gè)維度深入分析Java Stream的forEachOrdered與forEach的區(qū)別與適用場景,感興趣的朋友一起看看吧
    2025-08-08
  • Java swing 圖像處理多種效果實(shí)現(xiàn)教程

    Java swing 圖像處理多種效果實(shí)現(xiàn)教程

    這篇文章主要介紹了Java swing 圖像處理多種效果實(shí)現(xiàn)教程,具有很好的參考價(jià)值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-09-09
  • 使用@ApiModel遇到的問題及解決

    使用@ApiModel遇到的問題及解決

    這篇文章主要介紹了使用@ApiModel遇到的問題及解決,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06

最新評論

柞水县| 安泽县| 永清县| 辽宁省| 呼图壁县| 会理县| 霍林郭勒市| 兴义市| 永昌县| 庐江县| 德州市| 柘城县| 鹤山市| 惠东县| 天全县| 班戈县| 自治县| 县级市| 儋州市| 仙桃市| 青州市| 阜南县| 明溪县| 瓦房店市| 化隆| 宁陕县| 鹤山市| 建宁县| 论坛| 射洪县| 福鼎市| 外汇| 富源县| 巩义市| 和顺县| 商南县| 闽清县| 永昌县| 东至县| 麻栗坡县| 山阴县|