首先介绍一下整体流程
IndexDB的基本使用
- 创建(打开)数据库
let that = this;
const dbName = "databaseName"; //数据库名称
const tablename = "tableName"; //表名
const dbVersion = 1.0; //数据库版本
//实例化IndexDB数据上下文,这边根据浏览器类型来做选择
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;
//错误信息,打印日志
H5AppDB.indexedDB.onerror = function(e) {
console.log("错误信息:", e);
};
H5AppDB.indexedDB.open = function(version) {
//初始IndexDB
var request = indexedDB.open(dbName, version || dbVersion);
request.onsuccess = function(e) {
console.log("成功打开数据库: " + dbName);
H5AppDB.indexedDB.db = e.target.result;
//这边可以做一下打开数据库后的操作
};
// 如果版本不一致,执行版本升级的操作
request.onupgradeneeded = function(e) {
console.log("开始升级数据库。");
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"
}); //NoSQL类型数据库中必须的主键,唯一性
store.createIndex("fileId", "fileId", { unique: false }); //建立查找索引
store.createIndex("fileId", ["fileId","index"], { unique: false }); //多条件索引
};
request.onfailure = H5AppDB.indexedDB.onerror;
};
- 插入/更新数据--插入数据的key如果数据表已存在则更新
H5AppDB.indexedDB.addTodo = function(data, name) {
var db = H5AppDB.indexedDB.db;
var trans = db.transaction([name || tablename], "readwrite");
var store = trans.objectStore(name || tablename);
//数据以对象形式保存,体现NoSQL类型数据库的灵活性
var request = store.put(data); //保存数据
request.onsuccess = function() {
console.log("添加成功");
};
request.onerror = function(e) {
console.log("添加出错: ", e);
};
};
3.删除数据
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); //根据主键来删除
request.onsuccess = function() {
console.log("删除成功");
};
request.onerror = function(e) {
console.log("删除出错: ", e);
};
};
H5AppDB.indexedDB.open(1.0);
},
4.查找数据
H5AppDB.indexedDB.getAllTodoItems = function(
name,
key,
callback,
...value
) {
//let todos = "";
var db = H5AppDB.indexedDB.db;
var trans = db.transaction([name || tablename], "readwrite"); //通过事物开启对象
var store = trans.objectStore(name || tablename); //获取到对象的值
var index = store.index(key);
// Get everything in the store;
var keyRange = IDBKeyRange.only(value.length > 1 ? value : value[0]);//如果是单条件查询需要传入具体数据值,如果是多条件则传入数据值数组
var cursorRequest = index.openCursor(keyRange); //开启索引为0的表
let res = [];
cursorRequest.onsuccess = function(e) {
let result = e.target.result;
if (!!result === false) return;
res.push(result.value);
result.continue(); //这边执行轮询读取
};
cursorRequest.onerror = H5AppDB.indexedDB.onerror;
trans.oncomplete = () => {//事物执行完成后执行
callback(res);//由于indexDB是异步执行的,为了确保查询完数据再对数据进行进一步操作,可以传入回调函数在这边执行
};
};
断点续传
文件分片
这边未采用动态分片,动态分片可根据前面上传分片所花费的时间确定下一分片的大小,暂未实现;
// 文件开始分片,push分片到fileChunkedList数组中
const chunkSize=2 * 1024 * 1024;//分片大小
for (let i = 0; i < optionFile.size; i = i + chunkSize) {
const tmp = optionFile.slice(
i,
Math.min(i + chunkSize, optionFile.size)
);
fileChunkedList.push(tmp);
}
处理文件分块信息
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]);
});
}
// 看后端需要哪些,就传哪些,也可以自己追加额外参数
formData.append(
"multipartFile",
that.isPurse ? item.formData : item
); // 文件,isPurse===true说明文件是刷新后继续上传的
formData.append("key", option.file.name); // 文件名
ans.formData = formData;
ans.index = index;
return ans;
});
分片进行MD5摘要签名
为了不阻塞上传进度,这里利用浏览器间歇requestIdleCallback方法进行MD5加密;
//间歇计算分片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 => {
// 有任务,并且当前帧还没结束
while (count < chunks.length && deadline.timeRemaining() > 1) {
if (!chunks[count]) {
return;
}
await appendToSpark(chunks[count]);
chunks[count].md5 = spark.end();
H5AppDB.indexedDB.addTodo({//因为分块数据表的key为分块MD5,这里执行插入可以确保该分块的MD5已生成
...chunks[count],
fileId
formData
});
count++;
resolve(chunks[count]);
}
window.requestIdleCallback(workLoop);
};
window.requestIdleCallback(workLoop);
});
};
控制并发上传
function sendRequest(chunks, limit = 3) {
return new Promise((resolve, reject) => {
const len = chunks.length;
let counter = 0;
let isStop = false;//是否停止上传
const start = async () => {
if (isStop) {
return;
}
const item = chunks.shift();
if (!item) {
return;
}
if (!item.md5) {//因为前面是采用requestIdleCallback执行MD5加密,所以有可能浏览器特别忙,当前块还未来得及加密;
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);//处理进度条
},
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) {//说明最后一个分块上传成功
resolve(response.data);
H5AppDB.indexedDB.deleteTodo(//删除file表
fileId,
"fileCollection"
);
} else {
counter++;
H5AppDB.indexedDB.addTodo(//更新file表,主要为了更新上传进度
{
...data
},
"fileCollection"
);
start();
}
H5AppDB.indexedDB.deleteTodo(//删除已上传成功的分块
response.config.headers.md5
);
}
}
})
.catch(err => {
that.$message({
message: "网络请求发生错误!",
type: "error",
showClose: true
});
reject(err);
});
}
};
while (limit > 0) {//控制并发
setTimeout(() => {
start();
}, Math.random() * 1000);
limit -= 1;
}
});
}
处理文件上传进度条
// 更新上传进度条百分比的方法
const updataPercentage = e => {
let loaded = (that.uploaded / 100) * optionFile.size; // 当前已经上传文件的总大小
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));
};
刷新后初始化界面信息
const getDataCallback = res => {
if (res.length) {
this.fileOption = {
file: {
size: res[0].fileSize,//数据总大小
name: res[0].fileName
}
};
res.forEach(item => {
that.fileList[item.fileType] = [//展示文件信息
{
name: item.fileName,
type: item.fileType,
fileId: item.fileId
}
];
});
this.value = res[0].uploaded;//展示进度条进度
this.uploaded = res[0].uploaded;//已上传的数据百分比
this.isPurse = true;
}
};
H5AppDB.indexedDB.getAllTodoItems(
"fileCollection",//查询的数据表
"union",//查询的索引
getDataCallback,//查询完成后执行的毁掉函数
data//查询条件,可传多个
);