First introduce the overall process

Basic use of IndexDB

  1. Create (open) the database
  let that = this;
  const dbName = "databaseName"; // Database name
  const tablename = "tableName"; / / the name of the table
  const dbVersion = 1.0; // Database version
  // Instantiate the IndexDB data context based on the browser type
  let indexedDB =
    window.indexedDB || window.webkitIndexedDB || window.mozIndexedDB;
  if ("webkitIndexedDB" in window) {
    window.IDBTransaction = window.webkitIDBTransaction;
    window.IDBKeyRange = window.webkitIDBKeyRange;
  }
  H5AppDB.indexedDB = {};
  H5AppDB.indexedDB.db = null;
  // Error information is displayed
  H5AppDB.indexedDB.onerror = function(e) {
    console.log(Error message:, e);
  };
 H5AppDB.indexedDB.open = function(version) {
        / / the initial IndexDB
        var request = indexedDB.open(dbName, version || dbVersion);
        request.onsuccess = function(e) {
          console.log("Database opened successfully:" + dbName);
          H5AppDB.indexedDB.db = e.target.result;
          // Open the database
        };
        // If the versions are inconsistent, upgrade the versions
        request.onupgradeneeded = function(e) {
          console.log("Start upgrading database.");
          H5AppDB.indexedDB.db = e.target.result;
          var db = H5AppDB.indexedDB.db;
          if (db.objectStoreNames.contains(tableName)) {
            db.deleteObjectStore(tableName);
          }
          let store = db.createObjectStore(tableName, {
            keyPath: "md5"
          }); // Primary key required in NoSQL type database, unique
          store.createIndex("fileId"."fileId", { unique: false }); // create a lookup index
           store.createIndex("fileId"["fileId"."index"] and {unique: false }); // Multi-conditional index
        };
        request.onfailure = H5AppDB.indexedDB.onerror;
      };
Copy the code
  1. Insert/update data – The key to insert data is updated if the table already exists
H5AppDB.indexedDB.addTodo = function(data, name) {
        var db = H5AppDB.indexedDB.db;
        var trans = db.transaction([name || tablename], "readwrite");
        var store = trans.objectStore(name || tablename);
        // Data is stored as objects, reflecting the flexibility of noSQL-type databases
        var request = store.put(data); // Save data
        request.onsuccess = function() {
          console.log("Added successfully");
        };
        request.onerror = function(e) {
          console.log("Error adding:", e);
        };
      };
Copy the code

3. Delete data

H5AppDB.indexedDB.deleteTodo = function(id, name) {
        var db = H5AppDB.indexedDB.db;
        var trans = db.transaction([name || tablename], "readwrite");
        var store = trans.objectStore(name || tablename);
        var request = store.delete(id); // Delete by primary key
        request.onsuccess = function() {
          console.log("Deleted successfully");
        };
        request.onerror = function(e) {
          console.log("Error deleting:", e);
        };
      };
      H5AppDB.indexedDB.open(1.0);
    },
Copy the code

4. Find the data

H5AppDB.indexedDB.getAllTodoItems = function(name, key, callback, ... value) {
        //let todos = "";
        var db = H5AppDB.indexedDB.db;
        var trans = db.transaction([name || tablename], "readwrite"); // Open objects with things
        var store = trans.objectStore(name || tablename); // Get the value of the object
        var index = store.index(key);
        // Get everything in the store;
        var keyRange = IDBKeyRange.only(value.length > 1 ? value : value[0]);// If it is a single condition query, you need to pass in a specific data value. If it is a multi-condition query, you need to pass in an array of data values
        var cursorRequest = index.openCursor(keyRange); // open the table with index 0
        let res = [];
        cursorRequest.onsuccess = function(e) {
          let result = e.target.result;
          if(!!!!! result ===false) return;
          res.push(result.value);
          result.continue(); // polling reads are performed here
        };
        cursorRequest.onerror = H5AppDB.indexedDB.onerror;
        trans.oncomplete = () = > {// Execute after the transaction is executed
          callback(res);// Since indexDB is executed asynchronously, a callback function can be passed to ensure that the data is queried before further operations are performed on the data
        };
      };
Copy the code

Breakpoint continuingly

File fragmentation

Dynamic sharding is not adopted here. Dynamic sharding can determine the size of the next sharding according to the time it takes to upload the previous sharding.

 // The file is sharded and pushed into the fileChunkedList array
 const chunkSize=2 * 1024 * 1024;// Fragment size
  for (let i = 0; i < optionFile.size; i = i + chunkSize) {
    const tmp = optionFile.slice(
      i,
      Math.min(i + chunkSize, optionFile.size)
    );
    fileChunkedList.push(tmp);
  }
Copy the code

Process file partitioning information

  fileChunkedList = fileChunkedList.map((item, index) = > {
    const formData = new FormData();
    const ans = {};
    if (option.data) {
      Object.keys(option.data).forEach(key= > {
        formData.append(key, option.data[key]);
      });
    }
    // See what the backend needs to pass, or you can append additional parameters
    formData.append(
      "multipartFile",
      that.isPurse ? item.formData : item
    ); // file,isPurse===true
    formData.append("key", option.file.name); / / file name
    ans.formData = formData;
    ans.index = index;
    return ans;
  });
Copy the code

Fragment indicates the MD5 digest signature

In order not to block the upload progress, the browser intermittent requestIdleCallback method is used for MD5 encryption.

 // Intermittently calculates fragment MD5
const calculateHashIdle = async chunks => {
  return new Promise(resolve= > {
    const spark = new SparkMD5.ArrayBuffer();
    let count = 0;
    const appendToSpark = async file => {
      return new Promise(resolve= > {
        const reader = new FileReader();
        reader.readAsArrayBuffer(file);
        reader.onload = e= > {
          const md5 = spark.append(e.target.result);
          resolve(md5);
        };
      });
    };
    const workLoop = async deadline => {
      // There is a task, and the current frame is not finished
      while (count < chunks.length && deadline.timeRemaining() > 1) {
        if(! chunks[count]) {return;
        }
        await appendToSpark(chunks[count]);
        chunks[count].md5 = spark.end();
        H5AppDB.indexedDB.addTodo({// Since the key of the partitioned table is partitioned MD5, perform insert here to ensure that the MD5 of the partitioned table has been generated. chunks[count], fileId formData }); count++; resolve(chunks[count]); }window.requestIdleCallback(workLoop);
    };
    window.requestIdleCallback(workLoop);
  });
};
Copy the code

Control concurrent upload

 function sendRequest(chunks, limit = 3) {
    return new Promise((resolve, reject) = > {
      const len = chunks.length;
      let counter = 0;
      let isStop = false;// Whether to stop uploading
      const start = async() = > {if (isStop) {
          return;
        }
        const item = chunks.shift();
        if(! item) {return;
        }
        if(! item.md5) {RequestIdleCallback is used for MD5 encryption, so it is possible that the browser is very busy and the current block has not been encrypted.
          await calculateHashIdle([item]);
        }
        if (item)
          if (item) {
            let config = {
              method: "POST".url: url,
              data: item.formData,
              onUploadProgress: e= > {
                percentage[item.index] = e.loaded;
                updataPercentage(e);// Process the progress bar
              },
              headers: {
                "Content-Type": "multipart/form-data".md5: item.md5
              },
              withCredentials: true
            };
            axios(config)
              .then(response= > {
                if (response.data.code) {
                  if(response.data.code ! = ="0") {                
                      isStop = true;
                      reject(response);             
                  } else {
                    if (counter === len - 1) {// Indicates that the last block is uploaded successfully
                      resolve(response.data);
                      H5AppDB.indexedDB.deleteTodo(// drop the file table
                        fileId,
                         "fileCollection"
                      );
                    } else {
                      counter++;
                      H5AppDB.indexedDB.addTodo(// Update the file table to update the upload progress
                        {
                          ...data
                        },
                        "fileCollection"
                      );
                      start();
                    }
                    H5AppDB.indexedDB.deleteTodo(// Delete the uploaded blocks
                      response.config.headers.md5
                    );
                  }
                }
              })
              .catch(err= > {
                that.$message({
                  message: "Network request error!".type: "error".showClose: true}); reject(err); }); }};while (limit > 0) {// Control concurrency
        setTimeout(() = > {
          start();
        }, Math.random() * 1000);
        limit -= 1; }}); }Copy the code

Process the file upload progress bar

// Update the upload progress bar percentage method
const updataPercentage = e= > {
let loaded = (that.uploaded / 100) * optionFile.size; // Total size of uploaded files
console.log(loaded);
percentage.forEach(item= > {
  loaded += item;
});
e.percent = (loaded / optionFile.size) * 100;
if (that.value >= parseFloat(Number(e.percent).toFixed(0))) return;
that.value = parseFloat(Number(e.percent).toFixed(0));
};
Copy the code

The interface information is initialized after being refreshed

  const getDataCallback = res= > {
    if (res.length) {
      this.fileOption = {
        file: {
          size: res[0].fileSize,// Total size of data
          name: res[0].fileName
        }
      };
      res.forEach(item= > {
        that.fileList[item.fileType] = [// Display file information
          {
            name: item.fileName,
            type: item.fileType,
            fileId: item.fileId
          }
        ];
      });
      this.value = res[0].uploaded;// Displays the progress bar
      this.uploaded = res[0].uploaded;// Percentage of uploaded data
      this.isPurse = true; }}; H5AppDB.indexedDB.getAllTodoItems("fileCollection".// Query the data table
    "union".// The index of the query
    getDataCallback,// The ruin function executed after the query is complete
    data// Multiple query conditions can be sent
  );
Copy the code