自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

聊聊大文件分片上傳和分片下載

開(kāi)發(fā) 前端
在上傳大文件時(shí),需要考慮服務(wù)器的處理能力和存儲(chǔ)空間,以及安全問(wèn)題。同時(shí),避免并發(fā)上傳相同文件以確保續(xù)傳的準(zhǔn)確性。可以使用唯一的文件標(biāo)識(shí)符或用戶會(huì)話標(biāo)識(shí)符來(lái)區(qū)分。

1. 文件流操作

在軟件開(kāi)發(fā)中,我們會(huì)看到各種形形色色的文件/資源(pdf/word/音頻/視頻),其實(shí)它們歸根到底就是不同數(shù)據(jù)格式的以滿足自身規(guī)則的情況下展示。說(shuō)的更淺顯易懂點(diǎn),它們都是數(shù)據(jù),并且最終都會(huì)以二進(jìn)制形式展示。也就是說(shuō),我們的各種操作都是在處理數(shù)據(jù)。那么處理文件也是如此。

在前端開(kāi)發(fā)中,文件流操作允許我們通過(guò)數(shù)據(jù)流來(lái)處理文件,執(zhí)行諸如讀取、寫(xiě)入和刪除文件的操作。

在前端開(kāi)發(fā)中,文件可以作為數(shù)據(jù)流來(lái)處理。數(shù)據(jù)流是從一個(gè)源到另一個(gè)目的地傳輸?shù)臄?shù)據(jù)序列。

Blob 對(duì)象和 ArrayBuffer:處理二進(jìn)制數(shù)據(jù)

在前端處理二進(jìn)制數(shù)據(jù)時(shí),有兩個(gè)對(duì)象是繞不開(kāi)的。

  • Blob 對(duì)象[1](Binary Large Object)對(duì)象是一種可以在 JavaScript 中存儲(chǔ)大量二進(jìn)制數(shù)據(jù)的對(duì)象??梢酝ㄟ^(guò)構(gòu)造函數(shù)創(chuàng)建 Blob 對(duì)象,或者通過(guò)其他 API(如 FormData 對(duì)象[2])生成。
  • ArrayBuffer[3] 是 JavaScript 中的另一種對(duì)象類(lèi)型,它們可以存儲(chǔ)二進(jìn)制數(shù)據(jù)。ArrayBuffers 通常用于較低級(jí)別的操作,如直接操作和處理二進(jìn)制數(shù)據(jù)。

使用 FileReader 讀取文件

FileReader 是一個(gè)前端瀏覽器 API,允許我們異步讀取文件內(nèi)容并將其轉(zhuǎn)換為可用的數(shù)據(jù)格式,如文本或二進(jìn)制數(shù)據(jù)。

它提供了如 readAsText()[4] 和 readAsArrayBuffer()[5] 等方法,可以根據(jù)我們的需要進(jìn)行選擇。

圖片圖片

使用案例

下面,我們來(lái)用一個(gè)例子來(lái)簡(jiǎn)單說(shuō)明一下FileReader的使用方式。

import { ChangeEvent, useState } from 'react';

function FileInput() {
  // 讀取文件內(nèi)容到 ArrayBuffer
  function readFileToArrayBuffer(file: File): Promise<ArrayBuffer> {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();

      // 注冊(cè)文件讀取完成后的回調(diào)函數(shù)
      reader.onload = function (event) {
        const arrayBuffer = event.target?.result as ArrayBuffer;
        resolve(arrayBuffer);
      };

      // 讀取文件內(nèi)容到 ArrayBuffer
      reader.readAsArrayBuffer(file);

      // 處理文件讀取錯(cuò)誤
      reader.onerror = function (error) {
        reject(error);
      };
    });
  }

 
  // 處理文件選擇事件
  function handleFileChange(event: ChangeEvent<HTMLInputElement>) {
    const file = event.target.files?.[0]; // 獲取選擇的文件

    if (file) {
      readFileToArrayBuffer(file)
        .then((arrayBuffer) => {
         // 此處已經(jīng)能拿到文件的`arrayBuffer`信息,也就是Blob數(shù)據(jù)
        })
        .catch((error) => {
          console.error('文件讀取失敗:', error);
        });
    } 
  }

  return (
    <div>
      <input type="file" notallow={handleFileChange} />
    </div>
  );
}

export default FileInput;

在上面的代碼中,我創(chuàng)建了一個(gè)名為 FileInput 的函數(shù)組件。該組件有一個(gè)文件選擇框。當(dāng)用戶選擇一個(gè)文件時(shí),文件內(nèi)容會(huì)使用 FileReader[6] 讀取到 ArrayBuffer。然后在對(duì)應(yīng)的回調(diào)中就可以處理對(duì)應(yīng)的Blob信息了。

當(dāng)然,我們這里是利用FileReader的readAsArrayBuffer將文件內(nèi)容轉(zhuǎn)換成(ArrayBuffer)。這樣我們可以更好的進(jìn)行分片處理(這個(gè)后面會(huì)講)。其實(shí),我們還可以使用例如readAsDataURL()將資源變成一個(gè)url,然后在頁(yè)面中顯示。

具體的顯示方法取決于文件類(lèi)型。例如,可以將文本文件直接顯示在文本框或區(qū)域中,圖片文件使用 img 標(biāo)簽顯示,音頻和視頻文件使用 audio 或 video 標(biāo)簽顯示。通過(guò)在前端頁(yè)面上顯示文件流,可以在線預(yù)覽和查看文件內(nèi)容。

FileReader 工作流程和事件觸發(fā)

  • 初始化 FileReader 對(duì)象:
const reader = new FileReader();
  • 設(shè)置 onload 事件處理程序:
reader.onload = function(event) {
  // 讀取操作成功完成時(shí)執(zhí)行的代碼
  const result = event.target.result;
  console.log('文件內(nèi)容:', result);
};
  • 調(diào)用讀取方法:
const file = ...; // 獲取的文件對(duì)象
reader.readAsArrayBuffer(file); // 或者使用其他讀取方法

當(dāng)調(diào)用 readAsArrayBuffer, readAsDataURL 或 readAsText 方法時(shí),F(xiàn)ileReader 會(huì)開(kāi)始讀取文件。當(dāng)讀取操作成功完成后,onload 事件會(huì)被觸發(fā),并且 FileReader 對(duì)象的 result 屬性包含了讀取到的數(shù)據(jù)。

事件順序

FileReader 觸發(fā)的事件按以下順序發(fā)生:

  1. onloadstart:讀取操作開(kāi)始時(shí)觸發(fā)。
  2. onprogress:讀取過(guò)程中持續(xù)觸發(fā),可以用于顯示進(jìn)度信息。
  3. onload:讀取操作成功完成時(shí)觸發(fā)。
  4. onloadend:讀取操作完成(無(wú)論成功還是失?。r(shí)觸發(fā)。
  5. onerror:讀取操作失敗時(shí)觸發(fā)。
  6. onabort:讀取操作被中止時(shí)觸發(fā)。

下面的示例代碼展示了如何在讀取文件時(shí)顯示讀取進(jìn)度:

document.getElementById('fileInput').addEventListener('change', function(event) {
      const file = event.target.files[0];
      const reader = new FileReader();

      // 進(jìn)度事件
      reader.onprogress = function(e) {
        if (e.lengthComputable) {
          const percentLoaded = (e.loaded / e.total) * 100;
          document.getElementById('progressBar').value = percentLoaded;
        }
      };

      // 定義 onload 事件處理程序
      reader.onload = function(e) {
        const content = e.target.result;
        document.getElementById('fileContent').textContent = content;
      };

      // 讀取文件為文本
      reader.readAsText(file);
    });

2. 文件分片

其實(shí)呢,無(wú)論是分片上傳和分片下載最核心的點(diǎn)就是需要對(duì)文件資源進(jìn)行分片處理。

并且有很多現(xiàn)成的庫(kù)或者框架都會(huì)為我們來(lái)實(shí)現(xiàn)該部分,但是呢本著探索知識(shí)的本質(zhì),我們還是對(duì)其內(nèi)部比較核心的部分做一次講解。

在前端范圍內(nèi),我們使用JavaScript中的File API[7]獲取文件對(duì)象,并使用Blob.prototype.slice()[8]方法將文件切成多個(gè)分片,從而實(shí)現(xiàn)分片上傳。

讓我們將第一節(jié)中的代碼在稍加改造。

改造readFileToArrayBuffer

/**
 * 將文件讀取為 ArrayBuffer 并分片
 * @param file 要讀取的文件
 * @returns 返回包含分片 Blob 數(shù)組的 Promise
 */
function readFileToArrayBuffer(file: File): Promise<{ chunkList: Blob[] }> {
  return new Promise((resolve, reject) => {
    let currentChunk = 0; // 當(dāng)前分片的索引
    const chunkSize = 1024 * 1024; // 設(shè)置分片大小為 1MB
    const chunks = Math.ceil(file.size / chunkSize); // 計(jì)算總分片數(shù)
    const fileReader = new FileReader(); // 創(chuàng)建 FileReader 對(duì)象
    const chunkList: Blob[] = []; // 存儲(chǔ)分片的數(shù)組

    // 文件讀取完成后的回調(diào)函數(shù)
    fileReader.onload = function (e) {
      currentChunk++; // 增加當(dāng)前分片索引

      // 如果還有分片需要讀取,繼續(xù)讀取下一個(gè)分片
      if (currentChunk < chunks) {
        loadNextChunk();
      } else {
        // 所有分片讀取完成,resolve Promise 并返回分片數(shù)組
        resolve({ chunkList });
      }
    };

    // 文件讀取出錯(cuò)時(shí)的回調(diào)函數(shù)
    fileReader.onerror = function (e) {
      console.warn('讀取文件出錯(cuò)', e);
      reject(e); // reject Promise 并傳遞錯(cuò)誤信息
    };

    // 讀取下一個(gè)分片的函數(shù)
    function loadNextChunk() {
      const start = currentChunk * chunkSize; // 當(dāng)前分片的起始字節(jié)
      const end = start + chunkSize >= file.size ? file.size : start + chunkSize; // 當(dāng)前分片的結(jié)束字節(jié)

      const chunk = file.slice(start, end); // 切割文件得到當(dāng)前分片
      chunkList.push(chunk); // 將當(dāng)前分片添加到分片數(shù)組中
      fileReader.readAsArrayBuffer(chunk); // 讀取當(dāng)前分片為 ArrayBuffer
    }

    // 開(kāi)始讀取第一個(gè)分片
    loadNextChunk();
  });
}

?

當(dāng)然,在進(jìn)行文件上傳時(shí),有時(shí)候需要用到md5加密等。計(jì)算文件的md5是為了檢查上傳到服務(wù)器的文件是否與用戶所傳的文件一致,由于行文限制,這里我們不做介紹。(其實(shí)在分片完成,就可以執(zhí)行加密處理)

然后,我們就可以在readFileToArrayBuffer的調(diào)用處,獲取到對(duì)應(yīng)文件的分片信息。

function handleFileChange(event: ChangeEvent<HTMLInputElement>) {
    const file = event.target.files?.[0]; // 獲取選擇的文件
    if (file) {
      readFileToArrayBuffer(file)
        .then(({ chunkList }) => {
          for (let i = 0; i < chunkList.length; i++) {
            const chunk = chunkList[i];
            console.log('chunk', chunk);
          }
        })
        .catch((error) => {
          console.error('文件讀取失敗:', error);
        });
    }
  }

然后,我們就可以在for循環(huán)中執(zhí)行后續(xù)的操作了。

3. 分片上傳

大文件上傳可能會(huì)很慢、效率低并且不可靠,但有一些解決方案可以改善上傳過(guò)程的性能和穩(wěn)定性。

傳統(tǒng)上傳 VS 分片上傳

傳統(tǒng)上傳方法的問(wèn)題

分片上傳的優(yōu)點(diǎn)

大文件上傳耗時(shí)長(zhǎng),容易導(dǎo)致超時(shí)。

將大文件拆分成較小的分片,更快更可靠地上傳。

占用服務(wù)器和網(wǎng)絡(luò)帶寬資源,可能影響其他用戶的訪問(wèn)速度。

監(jiān)控并顯示上傳進(jìn)度,提高用戶體驗(yàn)。

如果上傳中斷,需要重新上傳整個(gè)文件,效率低下。

充分利用瀏覽器的并發(fā)上傳能力,減輕服務(wù)器負(fù)載。

難以顯示和控制上傳進(jìn)度。

實(shí)現(xiàn)斷點(diǎn)續(xù)傳功能,避免重新上傳已上傳的分片。

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

在前一節(jié)中,我們不是已經(jīng)能夠獲取到chunklist信息了嗎。此時(shí),我們就可以在for循環(huán)中執(zhí)行上傳操作。

而實(shí)現(xiàn)前端分片上傳的主要步驟如下

  1. 通過(guò)FormData對(duì)象和AJAX或Fetch API[9]發(fā)送分片到服務(wù)器。
  2. 服務(wù)器接收分片并暫存,所有分片接收完成后合并為完整文件。
  3. 客戶端可以監(jiān)聽(tīng)上傳進(jìn)度事件并在進(jìn)度條或提示中顯示進(jìn)度。

下面,我們主要講講前端范圍的邏輯實(shí)現(xiàn)。

readFileToArrayBuffer(file)
    .then(async ({ chunkList }) => {
      for (let i = 0; i < chunkList.length; i++) {
        const chunk = chunkList[i];
        await upChunk(chunk, i);
      }
    })
    .catch((error) => {
      console.error('文件讀取失敗:', error);
    });

我們將chunk上傳的邏輯,封裝成一個(gè)函數(shù)upChunk,其主要的邏輯如下:

/**
 * 異步上傳文件分片
 *
 * @param chunk - 當(dāng)前需要上傳的文件分片 (Blob 對(duì)象)
 * @param index - 當(dāng)前文件分片的索引
 */
const upChunk = async (chunk: Blob, index: number) => {
  const formData = new FormData();
  // 上傳的唯一標(biāo)識(shí)符,用于區(qū)分不同的文件上傳,前后端約定的值
  formData.append('uploadId', 'front789');
  formData.append('partIndex', index.toString());
  formData.append('partFile', chunk);

  try {
    // 發(fā)送 POST 請(qǐng)求上傳當(dāng)前分片
    await axios.post('上傳地址', formData, {
      headers: { 'Content-Type': 'multipart/form-data' },
      onUploadProgress: (progressEvent) => {
        // 檢查進(jìn)度事件的總大小是否存在
        if (progressEvent.total) {
          // 計(jì)算已上傳的百分比
          const percentCompleted = Math.round((progressEvent.loaded / progressEvent.total) * 100);
          // 在這里添加更新進(jìn)度條的邏輯
        }
      },
    });
  } catch (error) {
    // 如果上傳失敗,打印錯(cuò)誤信息
    console.error(`Chunk ${index + 1} upload failed:`, error);
  }
  // 打印分片上傳完成的信息
  console.log(`上傳分片 ${index}完成`);
};

當(dāng)我們把所有的chunklist都上傳成功后,后端服務(wù)會(huì)將上傳的分片組裝成完整的文件。

我們使用了axios_onUploadProgress[10]來(lái)處理文件上傳進(jìn)度問(wèn)題,然后我們可以在特定的位置改變一下state的值,這樣就可以實(shí)時(shí)顯示文檔上傳進(jìn)度了。

4. 分片下載

傳統(tǒng)文件下載 VS 文件分片下載

文件分片下載是一種通過(guò)將大文件拆分成較小的片段(分片)并同時(shí)下載它們來(lái)提高文件下載效率的技術(shù)。

問(wèn)題/技術(shù)

傳統(tǒng)文件下載

文件分片下載

長(zhǎng)時(shí)間等待

用戶可能需要等待很長(zhǎng)時(shí)間才能開(kāi)始使用大文件

只需下載第一個(gè)分片,客戶端就可以開(kāi)始使用文件

網(wǎng)絡(luò)擁堵

如果網(wǎng)絡(luò)帶寬被大文件下載占用,其他用戶可能會(huì)遇到下載速度慢的問(wèn)題

可以使用多個(gè)并行請(qǐng)求來(lái)下載分片,充分利用帶寬并提高整體下載速度

難以恢復(fù)下載

如果網(wǎng)絡(luò)故障或用戶中斷,整個(gè)文件必須重新下載

如果下載被中斷,只需重新下載未完成的分片,而不是整個(gè)文件

下載效率

下載速度較慢,特別是在網(wǎng)絡(luò)不穩(wěn)定或速度較慢的情況下

通過(guò)將大文件拆分成較小的片段并同時(shí)下載,提高文件下載效率

并行下載

不支持

支持,可以使用多個(gè)并行請(qǐng)求來(lái)下載分片

下載管理

整個(gè)文件作為一個(gè)整體進(jìn)行下載

每個(gè)分片可以單獨(dú)管理和下載,提供更好的靈活性

分片下載的實(shí)現(xiàn)步驟

實(shí)現(xiàn)客戶端分片下載的基本解決方案如下:

  1. 服務(wù)器端將大文件切割成多個(gè)分片,并為每個(gè)分片生成唯一標(biāo)識(shí)符。
  2. 客戶端發(fā)送請(qǐng)求以獲取分片列表并開(kāi)始下載第一個(gè)分片。
  3. 在下載過(guò)程中,客戶端基于分片列表發(fā)起并發(fā)請(qǐng)求以下載其他分片,并逐漸拼接和合并下載的數(shù)據(jù)。
  4. 當(dāng)所有分片下載完成后,客戶端將下載的數(shù)據(jù)合并為一個(gè)完整的文件。

示例代碼

async function downloadable() {
  try {
    // 發(fā)送文件下載請(qǐng)求,獲取文件的總大小和總分片數(shù)
    const response = await fetch('/download', {
      method: 'GET',
      headers: {
        'Content-Type': 'application/json',
      },
    });

    // 解析響應(yīng)數(shù)據(jù)
    const data = await response.json();
    const totalSize = data.totalSize;
    const totalChunks = data.totalChunks;

    // 初始化變量
    let downloadedChunks = 0;
    const chunks: Blob[] = [];

    // 下載每個(gè)分片
    for (let chunkNumber = 0; chunkNumber < totalChunks; chunkNumber++) {
      try {
        const chunkResponse = await fetch(`/download/${chunkNumber}`, {
          method: 'GET',
        });

        const chunk = await chunkResponse.blob();
        downloadedChunks++;
        chunks.push(chunk);

        // 當(dāng)所有分片下載完成時(shí)
        if (downloadedChunks === totalChunks) {
          // 合并分片
          const mergedBlob = new Blob(chunks);

          // 創(chuàng)建對(duì)象 URL 以生成下載鏈接
          const downloadUrl = window.URL.createObjectURL(mergedBlob);

          // 創(chuàng)建一個(gè) <a> 元素并設(shè)置屬性
          const link = document.createElement('a');
          link.href = downloadUrl;
          link.setAttribute('download', 'file.txt');

          // 模擬點(diǎn)擊下載
          link.click();

          // 釋放資源
          window.URL.revokeObjectURL(downloadUrl);
        }
      } catch (chunkError) {
        console.error(`Chunk ${chunkNumber} download failed:`, chunkError);
      }
    }
  } catch (error) {
    console.error('文件下載失敗:', error);
  }
}

我們先使用 Blob 對(duì)象創(chuàng)建一個(gè)總對(duì)象 URL,用于生成下載連接。然后創(chuàng)建一個(gè)標(biāo)簽,并將 href 屬性設(shè)置為剛創(chuàng)建的對(duì)象 URL。繼續(xù)設(shè)置標(biāo)簽的屬性以下載文件名,這樣在點(diǎn)擊時(shí)可以自動(dòng)下載文件。

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

在前端,可以使用localStorage或sessionStorage存儲(chǔ)已上傳分片的信息,包括已上傳的分片索引和分片大小。

每次上傳前,檢查本地存儲(chǔ)中是否存在已上傳分片信息。如果存在,則從斷點(diǎn)處繼續(xù)上傳。

在后端,可以使用臨時(shí)文件夾或數(shù)據(jù)庫(kù)記錄已接收的分片信息,包括已上傳的分片索引和分片大小。

上傳完成前,保存上傳狀態(tài),以便在上傳中斷時(shí)能夠恢復(fù)上傳進(jìn)度。

import axios from 'axios';
import React, { useState, useEffect, ChangeEvent } from 'react';

function FileUp() {
  const [file, setFile] = useState(null); // 本地上傳的文件
  const [uploadedChunks, setUploadedChunks] = useState([]); // 已上傳的分片列表
  const [uploading, setUploading] = useState(false); // 上傳是否進(jìn)行中

  function handleFileChange(event: ChangeEvent<HTMLInputElement>) {
    setFile(event.target.files?.[0]);
  }

  // 處理文件選擇事件
  async function upload() {
    if (!file) {
      alert('請(qǐng)選擇要上傳的文件!');
      return;
    }
    const chunkSize = 1024 * 1024; // 1MB
    const totalChunks = Math.ceil(file.size / chunkSize);

    let start = 0;
    let end = Math.min(chunkSize, file.size);
    setUploading(true);

    for (let i = 0; i < totalChunks; i++) {
      const chunk = file.slice(start, end);
      const uploadedChunkIndex = uploadedChunks.indexOf(i);

      if (uploadedChunkIndex === -1) {
        try {
          const response = await upChunk(chunk, i);
          setUploadedChunks((prevChunks) => [...prevChunks, i]);

          // 將已上傳的分片列表保存到本地存儲(chǔ)
          localStorage.setItem('uploadedChunks', JSON.stringify(uploadedChunks));
        } catch (error) {
          console.error(error); // 處理錯(cuò)誤
        }
      }

      start = end;
      end = Math.min(start + chunkSize, file.size);
    }

    setUploading(false);

    // 上傳完成,清除本地存儲(chǔ)中的分片信息
    localStorage.removeItem('uploadedChunks');
  }

  const upChunk = async (chunk: Blob, index: number) => {
    const formData = new FormData();
    // 這應(yīng)該是一個(gè)隨機(jī)值,用于標(biāo)識(shí)當(dāng)前上傳的文件,這是和后端做約定的值
    formData.append('uploadId', 'front789');
    formData.append('partIndex', index.toString());
    formData.append('partFile', chunk);

    try {
      return await axios.post(`https://Front789/api/uploadChunk`, formData, {
        headers: { 'Content-Type': 'multipart/form-data' },
      });
    } catch (error) {
      console.error(`Chunk ${index + 1} upload failed:`, error);
    }

    console.log(`上傳分片 ${index}完成`);
  };

  useEffect(() => {
    const storedUploadedChunks = localStorage.getItem('uploadedChunks');

    if (storedUploadedChunks) {
      setUploadedChunks(JSON.parse(storedUploadedChunks));
    }
  }, []);

  return (
    <div>
      <input type="file" onChange={handleFileChange} />
      <button onClick={upload} disabled={uploading}>
        {uploading ? `上傳中..` : '上傳'}
      </button>
    </div>
  );
}

export default FileUp;

該FileUp函數(shù)組件使用React的useState鉤子創(chuàng)建uploadedChunks狀態(tài)來(lái)保存已上傳的分片索引數(shù)組。

當(dāng)用戶選擇要上傳的文件時(shí),handleFileChange()函數(shù)會(huì)更file狀態(tài)。

upChunk()函數(shù)將分片發(fā)送到服務(wù)器并返回一個(gè)Promise對(duì)象來(lái)處理響應(yīng)。

upload()函數(shù)通過(guò)獲取總分片數(shù)并將uploading狀態(tài)設(shè)置為true來(lái)禁用上傳按鈕,從斷點(diǎn)處繼續(xù)上傳。它遍歷所有分片并檢查分片索引是否已包含在uploadedChunks數(shù)組中。如果沒(méi)有,該函數(shù)會(huì)上傳分片并將已上傳的分片索引添加到uploadedChunks數(shù)組中。然后使用localStorage保存已上傳的分片信息。最后,上傳完成后,函數(shù)會(huì)將uploading狀態(tài)設(shè)置為false并清除本地存儲(chǔ)中的分片信息。

在上傳大文件時(shí),需要考慮服務(wù)器的處理能力和存儲(chǔ)空間,以及安全問(wèn)題。同時(shí),避免并發(fā)上傳相同文件以確保續(xù)傳的準(zhǔn)確性。可以使用唯一的文件標(biāo)識(shí)符或用戶會(huì)話標(biāo)識(shí)符來(lái)區(qū)分。

責(zé)任編輯:武曉燕 來(lái)源: 前端柒八九
相關(guān)推薦

2021-01-15 11:40:44

文件Java秒傳

2022-06-15 09:01:45

大文件秒傳分片上傳

2021-01-18 05:19:11

數(shù)字指紋

2024-11-12 09:54:23

2025-03-28 05:10:00

Spring上傳大文件

2013-03-22 14:42:01

OSS開(kāi)放存儲(chǔ)服務(wù)云計(jì)算

2017-04-01 17:30:36

MongoDB分片實(shí)現(xiàn)

2009-11-16 11:41:19

PHP上傳大文件

2022-06-13 14:06:33

大文件上傳前端

2015-08-07 15:35:42

ios短點(diǎn)下載源碼

2013-11-28 09:48:55

MongoDBSharding分片

2013-11-25 10:45:04

MongoDB

2022-05-09 13:36:27

加密貨幣區(qū)塊鏈區(qū)塊鏈分片

2019-11-12 09:32:39

分布式elastic-job分片

2019-02-19 10:12:41

Redis分片數(shù)據(jù)

2009-07-21 15:38:31

2024-12-20 12:12:19

Redis負(fù)載均衡節(jié)點(diǎn)

2015-05-07 15:00:41

MongoDB分片與集群NoSQL

2023-02-14 08:01:32

2020-04-02 20:07:17

前端vuenote.js
點(diǎn)贊
收藏

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