>  기사  >  Java  >  vue+springboot를 사용하여 대용량 파일을 업로드하는 방법

vue+springboot를 사용하여 대용량 파일을 업로드하는 방법

王林
王林앞으로
2023-05-16 09:43:111131검색

Logic

대용량 파일을 업로드해야 하는 경우 다음 논리를 고려해야 합니다.

  • 대용량 파일을 업로드하려면 일반적으로 파일 조각(청크)을 업로드한 다음 모든 조각을 전체 파일로 병합해야 합니다. 다음 논리에 따라 구현할 수 있습니다.

  • 프런트 엔드에서는 페이지에 업로드할 파일을 선택하고 Blob.slice 메서드를 사용하여 파일을 슬라이스합니다. 일반적으로 각 슬라이스의 크기는 고정된 값입니다. (예: 5MB), 전체 크기가 기록됩니다.

  • 백엔드 서비스에 개별적으로 슬라이스를 업로드하면 XMLHttpRequest 또는 Axios와 같은 라이브러리를 사용하여 Ajax 요청을 보낼 수 있습니다. 각 슬라이스에는 현재 슬라이스 인덱스(0부터 시작), 총 슬라이스 수, 슬라이스 파일 데이터 등 세 가지 매개변수가 포함되어야 합니다.

  • 백엔드 서비스는 슬라이스를 수신한 후 이를 지정된 경로의 임시 파일에 저장하고 업로드된 슬라이스 인덱스 및 업로드 상태를 기록합니다. 조각 업로드에 실패하면 프런트 엔드에 조각을 재전송하라는 알림이 전달됩니다.

  • 모든 조각이 성공적으로 업로드되면 백엔드 서비스는 모든 조각의 내용을 읽고 이를 완전한 파일로 병합합니다. 파일 병합은 java.io.SequenceInputStream 및 BufferedOutputStream을 사용하여 수행할 수 있습니다.

  • 마지막으로 파일 업로드 성공 응답 결과를 프런트 엔드에 반환하면 됩니다.

Front end

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>File Upload</title>
</head>
<body>
    <input type="file" id="fileInput">
    <button onclick="upload()">Upload</button>
    <script>
        function upload() {
            let file = document.getElementById("fileInput").files[0];
            let chunkSize = 5 * 1024 * 1024; // 切片大小为5MB
            let totalChunks = Math.ceil(file.size / chunkSize); // 计算切片总数
            let index = 0;
            while (index < totalChunks) {
                let chunk = file.slice(index * chunkSize, (index + 1) * chunkSize);
                let formData = new FormData();
                formData.append("file", chunk);
                formData.append("index", index);
                formData.append("totalChunks", totalChunks);
                // 发送Ajax请求上传切片
                $.ajax({
                    url: "/uploadChunk",
                    type: "POST",
                    data: formData,
                    processData: false,
                    contentType: false,
                    success: function () {
                        if (++index >= totalChunks) {
                            // 所有切片上传完成,通知服务端合并文件
                            $.post("/mergeFile", {fileName: file.name}, function () {
                                alert("Upload complete!");
                            })
                        }
                    }
                });
            }
        }
    </script>
</body>
</html>

Backend

controller layer:

@RestController
public class FileController {

    @Value("${file.upload-path}")
    private String uploadPath;

    @PostMapping("/uploadChunk")
    public void uploadChunk(@RequestParam("file") MultipartFile file,
                            @RequestParam("index") int index,
                            @RequestParam("totalChunks") int totalChunks) throws IOException {
        // 以文件名+切片索引号为文件名保存切片文件
        String fileName = file.getOriginalFilename() + "." + index;
        Path tempFile = Paths.get(uploadPath, fileName);
        Files.write(tempFile, file.getBytes());
        // 记录上传状态
        String uploadFlag = UUID.randomUUID().toString();
        redisTemplate.opsForList().set("upload:" + fileName, index, uploadFlag);
        // 如果所有切片已上传,则通知合并文件
        if (isAllChunksUploaded(fileName, totalChunks)) {
            sendMergeRequest(fileName, totalChunks);
        }
    }

    @PostMapping("/mergeFile")
    public void mergeFile(String fileName) throws IOException {
        // 所有切片均已成功上传,进行文件合并
        List<File> chunkFiles = new ArrayList<>();
        for (int i = 0; i < getTotalChunks(fileName); i++) {
            String chunkFileName = fileName + "." + i;
            Path tempFile = Paths.get(uploadPath, chunkFileName);
            chunkFiles.add(tempFile.toFile());
        }
        Path destFile = Paths.get(uploadPath, fileName);
        try (OutputStream out = Files.newOutputStream(destFile);
             SequenceInputStream seqIn = new SequenceInputStream(Collections.enumeration(chunkFiles));
             BufferedInputStream bufIn = new BufferedInputStream(seqIn)) {
            byte[] buffer = new byte[1024];
            int len;
            while ((len = bufIn.read(buffer)) > 0) {
                out.write(buffer, 0, len);
            }
        }
        // 清理临时文件和上传状态记录
        for (int i = 0; i < getTotalChunks(fileName); i++) {
            String chunkFileName = fileName + "." + i;
            Path tempFile = Paths.get(uploadPath, chunkFileName);
            Files.deleteIfExists(tempFile);
            redisTemplate.delete("upload:" + chunkFileName);
        }
    }

    private int getTotalChunks(String fileName) {
        // 根据文件名获取总切片数
        return Objects.requireNonNull(Paths.get(uploadPath, fileName).toFile().listFiles()).length;
    }

    private boolean isAllChunksUploaded(String fileName, int totalChunks) {
        // 判断所有切片是否已都上传完成
        List<String> uploadFlags = redisTemplate.opsForList().range("upload:" + fileName, 0, -1);
        return uploadFlags != null && uploadFlags.size() == totalChunks;
    }

    private void sendMergeRequest(String fileName, int totalChunks) {
        // 发送合并文件请求
        new Thread(() -> {
            try {
                URL url = new URL("http://localhost:8080/mergeFile");
                HttpURLConnection conn = (HttpURLConnection) url.openConnection();
                conn.setRequestMethod("POST");
                conn.setDoOutput(true);
                conn.setDoInput(true);
                conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded;charset=utf-8");
                OutputStream out = conn.getOutputStream();
                String query = "fileName=" + fileName;
                out.write(query.getBytes());
                out.flush();
                out.close();
                BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8));
                while (br.readLine() != null) ;
                br.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }).start();
    }

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
}

그 중 file.upload-path는 파일 업로드를 위한 저장 경로로 application.properties나 application.yml에서 설정할 수 있습니다. 동시에 업로드 상태를 기록하려면 RedisTemplate Bean을 추가해야 합니다.

RedisTemplate 구성

RedisTemplate을 사용해야 한다면 다음 패키지를 도입해야 합니다

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>

동시에 yml에 redis 정보를 구성

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0

그런 다음 자신의 수업에서 이렇게 사용하세요

@Component
public class myClass {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    public void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }

    public Object get(String key) {
        return redisTemplate.opsForValue().get(key);
    }
}

Notes

  • 매번 제어해야 함 업로드된 슬라이스의 크기는 서버 리소스를 너무 많이 차지하거나 네트워크 불안정으로 인해 업로드 실패가 발생하는 것을 방지하기 위해 업로드 속도와 안정성을 고려하기 위한 것입니다.

  • 슬라이스 업로드 순서가 있습니다. 병합하기 전에 모든 슬라이스가 업로드되었는지 확인해야 합니다. 그렇지 않으면 파일이 불완전하거나 파일 병합 오류가 발생할 수 있습니다.

  • 업로드가 완료된 후에는 너무 많은 디스크 공간을 차지하여 발생하는 서버 충돌을 방지하기 위해 임시 파일을 적시에 정리해야 합니다. 만료된 임시 파일을 정리하기 위한 주기적인 작업을 설정할 수 있습니다.

위 내용은 vue+springboot를 사용하여 대용량 파일을 업로드하는 방법의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
이 기사는 yisu.com에서 복제됩니다. 침해가 있는 경우 admin@php.cn으로 문의하시기 바랍니다. 삭제