偷偷摘套内射激情视频,久久精品99国产国产精,中文字幕无线乱码人妻,中文在线中文a,性爽19p

SpringBoot 實(shí)戰(zhàn):文件上傳之秒傳、斷點(diǎn)續(xù)傳、分片上傳

開(kāi)發(fā) 前端
在日常開(kāi)發(fā)中,文件上傳的場(chǎng)景多種多樣。比如,在線教育平臺(tái)上的視頻資源上傳,社交平臺(tái)上的圖片分享,以及企業(yè)內(nèi)部的知識(shí)文檔管理等。

文件上傳功能幾乎是每個(gè) Web 應(yīng)用不可或缺的一部分。無(wú)論是個(gè)人博客中的圖片上傳,還是企業(yè)級(jí)應(yīng)用中的文檔管理,文件上傳都扮演著至關(guān)重要的角色。今天,松哥和大家來(lái)聊聊文件上傳中的幾個(gè)高級(jí)玩法——秒傳、斷點(diǎn)續(xù)傳和分片上傳。

一 文件上傳的常見(jiàn)場(chǎng)景

在日常開(kāi)發(fā)中,文件上傳的場(chǎng)景多種多樣。比如,在線教育平臺(tái)上的視頻資源上傳,社交平臺(tái)上的圖片分享,以及企業(yè)內(nèi)部的知識(shí)文檔管理等。這些場(chǎng)景對(duì)文件上傳的要求也各不相同,有的追求速度,有的注重穩(wěn)定性,還有的需要考慮文件大小和安全性。因此,針對(duì)不同需求,我們有了秒傳、斷點(diǎn)續(xù)傳和分片上傳等解決方案。

二 秒傳、斷點(diǎn)上傳與分片上傳

秒傳

秒傳,顧名思義,就是幾乎瞬間完成文件上傳的過(guò)程。其實(shí)現(xiàn)原理是通過(guò)計(jì)算文件的哈希值(如 MD5 或 SHA-1),然后將這個(gè)唯一的標(biāo)識(shí)符發(fā)送給服務(wù)器。如果服務(wù)器上已經(jīng)存在相同的文件,則直接返回成功信息,避免了重復(fù)上傳。這種方式不僅節(jié)省了帶寬,也大大提高了用戶體驗(yàn)。

斷點(diǎn)續(xù)傳

斷點(diǎn)續(xù)傳是指在網(wǎng)絡(luò)不穩(wěn)定或者用戶主動(dòng)中斷上傳后,能夠從上次中斷的地方繼續(xù)上傳,而不需要重新開(kāi)始整個(gè)過(guò)程。這對(duì)于大文件上傳尤為重要,因?yàn)樗梢杂行Х乐挂蚓W(wǎng)絡(luò)問(wèn)題導(dǎo)致的上傳失敗,同時(shí)也能節(jié)約用戶的流量和時(shí)間。

分片上傳

分片上傳則是將一個(gè)大文件分割成多個(gè)小塊分別上傳,最后再由服務(wù)器合并成完整的文件。這種做法的好處是可以并行處理多個(gè)小文件,提高上傳效率;同時(shí),如果某一部分上傳失敗,只需要重傳這一部分,不影響其他部分。

三 秒傳實(shí)戰(zhàn)

后端實(shí)現(xiàn)

在 SpringBoot 項(xiàng)目中,我們可以使用 MessageDigest 類來(lái)計(jì)算文件的 MD5 值,然后檢查數(shù)據(jù)庫(kù)中是否存在該文件。

@RestController
@RequestMapping("/file")
public class FileController {
    @Autowired
    FileService fileService;

    @PostMapping("/upload1")
    public ResponseEntity<String> secondUpload(@RequestParam(value = "file",required = false) MultipartFile file,@RequestParam(required = false,value = "md5") String md5) {
        try {
            // 檢查數(shù)據(jù)庫(kù)中是否已存在該文件
            if (fileService.existsByMd5(md5)) {
                return ResponseEntity.ok("文件已存在");
            }
            // 保存文件到服務(wù)器
            file.transferTo(new File("/path/to/save/" + file.getOriginalFilename()));
            // 保存文件信息到數(shù)據(jù)庫(kù)
            fileService.save(new FileInfo(file.getOriginalFilename(), DigestUtils.md5DigestAsHex(file.getInputStream())));
            return ResponseEntity.ok("上傳成功");
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("上傳失敗");
        }
    }
}

前端調(diào)用

前端可以通過(guò) JavaScript 的 FileReader API 讀取文件內(nèi)容,通過(guò) spark-md5 計(jì)算 MD5 值,然后發(fā)送給后端進(jìn)行校驗(yàn)。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>秒傳</title>
    <script src="spark-md5.js"></script>
</head>
<body>
<input type="file" id="fileInput" />
<button onclick="startUpload()">開(kāi)始上傳</button>
<hr>
<script>
    async function startUpload() {
        const fileInput = document.getElementById('fileInput');
        const file = fileInput.files[0];
        if (!file) {
            alert("請(qǐng)選擇文件");
            return;
        }

        const md5 = await calculateMd5(file);
        const formData = new FormData();
        formData.append('md5', md5);

        const response = await fetch('/file/upload1', {
            method: 'POST',
            body: formData
        });

        const result = await response.text();
        if (response.ok) {
            if (result != "文件已存在") {
                // 開(kāi)始上傳文件
            }
        } else {
            console.error("上傳失敗: " + result);
        }
    }

    function calculateMd5(file) {
        return new Promise((resolve, reject) => {
            const reader = new FileReader();
            reader.onloadend = () => {
                const spark = new SparkMD5.ArrayBuffer();
                spark.append(reader.result);
                resolve(spark.end());
            };
            reader.onerror = () => reject(reader.error);
            reader.readAsArrayBuffer(file);
        });
    }
</script>
</body>
</html>

前端分為兩個(gè)步驟:

  1. 計(jì)算文件的 MD5 值,計(jì)算之后發(fā)送給服務(wù)端確定文件是否存在。
  2. 如果文件已經(jīng)存在,則不需要繼續(xù)上傳文件;如果文件不存在,則開(kāi)始上傳文件,上傳文件和 MD5 校驗(yàn)請(qǐng)求類似,上面的案例代碼中我就沒(méi)有重復(fù)演示了,松哥在書(shū)里和之前的課程里都多次講過(guò)文件上傳,這里不再啰嗦。

四 分片上傳實(shí)戰(zhàn)

分片上傳關(guān)鍵是在前端對(duì)文件切片,比如一個(gè) 10MB 的文件切為 10 份,每份 1MB。每次上傳的時(shí)候,需要多一個(gè)參數(shù)記錄當(dāng)前上傳的文件切片的起始位置。

比如一個(gè) 10MB 的文件,切為 10 份,每份 1MB,那么:

  • 第 0 片,從 0 開(kāi)始,一共是 1024*1024 個(gè)字節(jié)。
  • 第 1 片,從 1024*1024 開(kāi)始,一共是 1024*1024 個(gè)字節(jié)。
  • 第 2 片...

把這個(gè)搞懂,后面的代碼就好理解了。

后端實(shí)現(xiàn)

private static final String UPLOAD_DIR = System.getProperty("user.home") + "/uploads/";
/**
 * 上傳文件到指定位置
 *
 * @param file 上傳的文件
 * @param start 文件開(kāi)始上傳的位置
 * @return ResponseEntity<String> 上傳結(jié)果
 */
@PostMapping("/upload2")
public ResponseEntity<String> resumeUpload(@RequestParam("file") MultipartFile file, @RequestParam("start") long start,@RequestParam("fileName") String fileName) {
    try {
        File directory = new File(UPLOAD_DIR);
        if (!directory.exists()) {
            directory.mkdirs();
        }
        File targetFile = new File(UPLOAD_DIR + fileName);
        RandomAccessFile randomAccessFile = new RandomAccessFile(targetFile, "rw");
        FileChannel channel = randomAccessFile.getChannel();
        channel.position(start);
        channel.transferFrom(file.getResource().readableChannel(), start, file.getSize());
        channel.close();
        randomAccessFile.close();
        return ResponseEntity.ok("上傳成功");
    } catch (Exception e) {
        System.out.println("上傳失敗: "+e.getMessage());
        return ResponseEntity.status(500).body("上傳失敗");
    }
}

后端每次處理的時(shí)候,需要先設(shè)置文件的起始位置。

前端調(diào)用

前端需要將文件切分成多個(gè)小塊,然后依次上傳。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>分片示例</title>
</head>
<body>
    <input type="file" id="fileInput" />
    <button onclick="startUpload()">開(kāi)始上傳</button>

    <script>
        async function startUpload() {
            const fileInput = document.getElementById('fileInput');
            const file = fileInput.files[0];
            if (!file) {
                alert("請(qǐng)選擇文件");
                return;
            }

            const filename = file.name;
            let start = 0;

            uploadFile(file, start);
        }

        async function uploadFile(file, start) {
            const chunkSize = 1024 * 1024; // 每個(gè)分片1MB
            const total = Math.ceil(file.size / chunkSize);

            for (let i = 0; i < total; i++) {
                const chunkStart = start + i * chunkSize;
                const chunkEnd = Math.min(chunkStart + chunkSize, file.size);
                const chunk = file.slice(chunkStart, chunkEnd);

                const formData = new FormData();
                formData.append('file', chunk);
                formData.append('start', chunkStart);
                formData.append('fileName', file.name);

                const response = await fetch('/file/upload2', {
                    method: 'POST',
                    body: formData
                });

                const result = await response.text();
                if (response.ok) {
                    console.log(`分片 ${i + 1}/${total} 上傳成功`);
                } else {
                    console.error(`分片 ${i + 1}/${total} 上傳失敗: ${result}`);
                    break;
                }
            }
        }
    </script>
</body>
</html>

五 斷點(diǎn)續(xù)傳實(shí)戰(zhàn)

斷點(diǎn)續(xù)傳的技術(shù)原理類似于分片上傳。

當(dāng)文件已經(jīng)上傳了一部分之后,斷了需要重新開(kāi)始上傳。

那么我們的思路是這樣的:

  1. 前端先發(fā)送一個(gè)請(qǐng)求,檢查要上傳的文件在服務(wù)端是否已經(jīng)存在,如果存在,目前大小是多少。
  2. 前端根據(jù)已經(jīng)存在的大小,繼續(xù)上傳文件即可。

后端案例

先來(lái)看后端檢查的接口,如下:

@GetMapping("/check")
public ResponseEntity<Long> checkFile(@RequestParam("filename") String filename) {
    File file = new File(UPLOAD_DIR + filename);
    if (file.exists()) {
        return ResponseEntity.ok(file.length());
    } else {
        return ResponseEntity.ok(0L);
    }
}

如果文件存在,則返回已經(jīng)存在的文件大小。

如果文件不存在,則返回 0,表示前端從頭開(kāi)始上傳該文件。

前端調(diào)用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>斷點(diǎn)續(xù)傳示例</title>
</head>
<body>
<input type="file" id="fileInput"/>
<button onclick="startUpload()">開(kāi)始上傳</button>

<script>
    async function startUpload() {
        const fileInput = document.getElementById('fileInput');
        const file = fileInput.files[0];
        if (!file) {
            alert("請(qǐng)選擇文件");
            return;
        }

        const filename = file.name;
        let start = await checkFile(filename);

        uploadFile(file, start);
    }

    async function checkFile(filename) {
        const response = await fetch(`/file/check?filename=${filename}`);
        const start = await response.json();
        return start;
    }

    async function uploadFile(file, start) {
        const chunkSize = 1024 * 1024; // 每個(gè)分片1MB
        const total = Math.ceil((file.size - start) / chunkSize);

        for (let i = 0; i < total; i++) {
            const chunkStart = start + i * chunkSize;
            const chunkEnd = Math.min(chunkStart + chunkSize, file.size);
            const chunk = file.slice(chunkStart, chunkEnd);

            const formData = new FormData();
            formData.append('file', chunk);
            formData.append('start', chunkStart);
            formData.append('fileName', file.name);

            const response = await fetch('/file/upload2', {
                method: 'POST',
                body: formData
            });

            const result = await response.text();
            if (response.ok) {
                console.log(`分片 ${i + 1}/${total} 上傳成功`);
            } else {
                console.error(`分片 ${i + 1}/${total} 上傳失敗: ${result}`);
                break;
            }
        }
    }
</script>
</body>
</html>

這個(gè)案例實(shí)際上是一個(gè)斷點(diǎn)續(xù)傳+分片上傳的案例,相關(guān)知識(shí)點(diǎn)并不難,小伙伴們可以自行體會(huì)下。

六 總結(jié)

好了,以上就是關(guān)于文件上傳中秒傳、斷點(diǎn)續(xù)傳和分片上傳的實(shí)戰(zhàn)分享。通過(guò)這些技術(shù)的應(yīng)用,我們可以極大地提升文件上傳的效率和穩(wěn)定性,改善用戶體驗(yàn)。希望各位小伙伴在自己的項(xiàng)目中也能靈活運(yùn)用這些技巧,解決實(shí)際問(wèn)題。


責(zé)任編輯:武曉燕 來(lái)源: 江南一點(diǎn)雨
相關(guān)推薦

2021-01-15 11:40:44

文件Java秒傳

2022-06-15 09:01:45

大文件秒傳分片上傳

2025-10-29 04:15:00

OSS分片CDN

2025-07-03 07:41:34

2017-08-08 08:45:44

前端文件斷點(diǎn)續(xù)傳

2011-03-04 16:41:57

FileZilla

2023-06-20 19:57:13

2020-04-02 20:07:17

前端vuenote.js

2023-03-09 12:04:38

Spring文件校驗(yàn)

2021-01-18 05:19:11

數(shù)字指紋

2022-08-05 08:40:37

架構(gòu)

2013-07-22 14:02:17

iOS開(kāi)發(fā)ASIHTTPRequ

2024-07-02 10:18:18

2009-08-28 15:38:49

C#實(shí)現(xiàn)斷點(diǎn)續(xù)傳

2025-07-02 00:00:00

2025-06-27 02:32:00

2025-06-17 08:39:43

2017-12-20 15:11:48

iOS緩存文件斷點(diǎn)機(jī)制

2011-03-01 14:12:12

FreebsdProftpd

2010-04-07 11:09:53

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)