断点续传使用场景,完整前后端实现示例,包括上传,下载,验证

断点续传在多个场景中非常有用,包括但不限于大文件上传、跨国或跨区域文件传输、移动设备文件传输、备份和同步以及软件更新等。接下来,我将为你提供一个基于Java的后端实现示例,结合前端逻辑来完成整个断点续传的功能,包括上传、下载和验证。

后端实现(使用Spring Boot)

添加依赖

首先,在pom.xml中添加必要的依赖:

<dependencies>
    <!-- Spring Web -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <!-- Apache Commons IO for file handling -->
    <dependency>
        <groupId>commons-io</groupId>
        <artifactId>commons-io</artifactId>
        <version>2.11.0</version>
    </dependency>

    <!-- Jackson for JSON processing -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>
控制器代码
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;

@RestController
@RequestMapping("/api")
public class FileController {

    private static final String UPLOAD_DIR = "uploads/";

    @PostMapping("/upload")
    public ResponseEntity<?> uploadChunk(@RequestParam("file") MultipartFile file,
                                         @RequestParam("fileName") String fileName,
                                         @RequestParam("chunkIndex") int chunkIndex,
                                         @RequestParam("totalChunks") int totalChunks) throws IOException {
        // 创建目录
        String dirPath = UPLOAD_DIR + fileName;
        new File(dirPath).mkdirs();

        // 保存分片
        File chunkFile = new File(dirPath, chunkIndex + ".part");
        try (OutputStream os = new FileOutputStream(chunkFile)) {
            os.write(file.getBytes());
        }

        // 记录已上传的分片
        Set<Integer> uploadedChunks = loadUploadedChunks(fileName);
        uploadedChunks.add(chunkIndex);
        saveUploadedChunks(fileName, uploadedChunks);

        if (uploadedChunks.size() == totalChunks) {
            mergeChunks(fileName, totalChunks);
        }

        return ResponseEntity.ok().build();
    }

    @GetMapping("/download")
    public ResponseEntity<byte[]> download(@RequestParam("fileName") String fileName) throws IOException {
        String filePath = UPLOAD_DIR + fileName + "/" + fileName;
        byte[] bytes = Files.readAllBytes(Paths.get(filePath));

        return ResponseEntity.ok()
                .header("Content-Disposition", "attachment; filename=" + fileName)
                .body(bytes);
    }

    @GetMapping("/check")
    public ResponseEntity<Map<String, Object>> check(@RequestParam("fileName") String fileName) {
        Set<Integer> uploadedChunks = loadUploadedChunks(fileName);
        Map<String, Object> response = new HashMap<>();
        response.put("uploadedChunks", uploadedChunks);
        return ResponseEntity.ok(response);
    }

    private Set<Integer> loadUploadedChunks(String fileName) {
        String filePath = UPLOAD_DIR + fileName + "/uploadedChunks.json";
        File file = new File(filePath);
        Set<Integer> chunks = new HashSet<>();
        if (file.exists()) {
            try {
                chunks = new HashSet<>(Arrays.asList(FileUtils.readFileToString(file, "UTF-8").split(",")))
                        .stream().map(Integer::parseInt).collect(Collectors.toSet());
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return chunks;
    }

    private void saveUploadedChunks(String fileName, Set<Integer> uploadedChunks) {
        String filePath = UPLOAD_DIR + fileName + "/uploadedChunks.json";
        try {
            FileUtils.writeStringToFile(new File(filePath), String.join(",", uploadedChunks.stream().map(String::valueOf).toList()), "UTF-8");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void mergeChunks(String fileName, int totalChunks) throws IOException {
        String dirPath = UPLOAD_DIR + fileName;
        String mergedFilePath = UPLOAD_DIR + fileName + "/" + fileName;

        try (OutputStream os = new FileOutputStream(mergedFilePath)) {
            for (int i = 0; i < totalChunks; i++) {
                File chunkFile = new File(dirPath, i + ".part");
                byte[] bytes = Files.readAllBytes(chunkFile.toPath());
                os.write(bytes);
                Files.delete(chunkFile.toPath());
            }
        }
    }
}

前端实现(使用Vue.js)

安装依赖

确保你已经安装了axios用于HTTP请求处理:

npm install axios
上传组件
<template>
  <div>
    <input type="file" @change="selectFile">
    <button @click="upload">上传</button>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      file: null,
      chunkSize: 5 * 1024 * 1024, // 5MB
      uploadedChunks: []
    };
  },
  methods: {
    selectFile(e) {
      this.file = e.target.files[0];
      this.uploadedChunks = [];
    },
    async upload() {
      const fileName = this.file.name;
      const totalChunks = Math.ceil(this.file.size / this.chunkSize);

      // 获取已上传的分片信息
      const { data } = await axios.get('/api/check', { params: { fileName } });
      this.uploadedChunks = data.uploadedChunks;

      for (let i = 0; i < totalChunks; i++) {
        if (this.uploadedChunks.includes(i)) continue;

        const start = i * this.chunkSize;
        const end = Math.min(start + this.chunkSize, this.file.size);
        const chunk = this.file.slice(start, end);

        const formData = new FormData();
        formData.append('file', new Blob([chunk]));
        formData.append('fileName', fileName);
        formData.append('chunkIndex', i);
        formData.append('totalChunks', totalChunks);

        await axios.post('/api/upload', formData);
        this.uploadedChunks.push(i);
      }
      alert('文件上传成功');
    }
  }
};
</script>
下载组件
<template>
  <div>
    <input type="text" v-model="fileName">
    <button @click="download">下载</button>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      fileName: ''
    };
  },
  methods: {
    async download() {
      const response = await axios.get('/api/download', { params: { fileName: this.fileName }, responseType: 'blob' });

      const url = window.URL.createObjectURL(new Blob([response.data]));
      const link = document.createElement('a');
      link.href = url;
      link.setAttribute('download', this.fileName);
      document.body.appendChild(link);
      link.click();
    }
  }
};
</script>
验证组件
<template>
  <div>
    <input type="text" v-model="fileName">
    <button @click="check">验证</button>
    <p>已上传分片:{{ uploadedChunks }}</p>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      fileName: '',
      uploadedChunks: []
    };
  },
  methods: {
    async check() {
      const { data } = await axios.get('/api/check', { params: { fileName: this.fileName } });
      this.uploadedChunks = data.uploadedChunks;
    }
  }
};
</script>

这个示例展示了如何使用Spring Boot作为后端,并结合Vue.js前端来实现断点续传功能。你可以根据自己的需求进行调整和优化,例如增加错误处理、进度条显示等功能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值