前言
在做一个chatgpt对话项目的时候,使用了localStoage保存聊天记录,但是聊天记录如果不进行清理,只会越来越多,localStoage有存储限制,所以选用indexedDB.
特点
- 非关系新数据库 没有表的概念,只有仓库(store)
数据以键值对存在 - 持久化存储
即使删除浏览器缓存,也不会被删除 - 异步
- 支持事务
- 同源策略
网页只能访问自身域名下的数据库 - 存储容量大
最终实现
-
state中定义msgList,用来存储聊天记录,v-for遍历的也是msgList
-
初始只获取30条渲染
-
发送时先push到state的msgList中,页面同步更新,然后再插入数据库中
-
接收到消息也时先插入msgList,使页面更新,再插入数据库
-
下拉到顶部,从数据库获取历史聊天记录
-
从数据库中从后往前跳过msgList.length条数据获取30条,unshift插入msgList
遇到的问题
获取的聊天记录是最开始插入的记录开始的,需要在使用游标查询时,改变游标的遍历顺序:从前往后改为从后往前, 获取到数据之后还要reverse, 使对话顺序正确.
调用例子(助记)
//发送消息
SAVEMSG(state, data) {
console.log('#插入的是', data);
state.msgList2.push(data);//仓库中先插入,页面同步更新
openDB(["msg"], "1").then((db) => {
console.log("准备插入数据", db);
addData(db, "AI-msg", data);//数据库中插入,异步,没什么大问题不会失败
}).catch((err) => {
console.log('#数据库 写入失败->', err);
});
},
// 获取历史聊天记录
REFRESHMSG(state, val) {
openDB(["msg"], "1").then((db) => {
//打开数据库成功
console.log("准备查询", db);
cursorGetDataAndPage(db, 'AI-msg', val.page, 10,state.msgList2.length).then((res) => {
//游标查询并且跳过已有的记录
console.log('#查询成功', res);
//插入头部
state.msgList2.unshift(...res.reverse()) ;
});
}).catch((err) => {
console.log('#数据库 查询失败->', err);
});
},
indexedDB 封装源码
src/indexedDB/index.js 在需要用到的地方,引入所需方法调用即可
/**
* 打开数据库
* @param {object} dbName 数据库的名字
* @param {string} storeName 仓库名称
* @param {string} version 数据库的版本
* @param {string} priKey 主键
* @param {Object} index 索引
* @return {object} 该函数会返回一个数据库实例
*/
export const openDB = function openDB(dbName, version = 1, storeName, priKey, index) {
return new Promise((resolve, reject) => {
// 兼容浏览器
let indexedDB =
window.indexedDB ||
window.mozIndexedDB ||
window.webkitIndexedDB ||
window.msIndexedDB;
let db;
// 打开数据库,若没有则会创建
const request = indexedDB.open(dbName, version);
// 数据库打开成功回调
request.onsuccess = function (event) {
db = event.target.result; // 数据库对象
// console.log("数据库打开成功");
resolve(db);
};
// 数据库打开失败的回调
request.onerror = function (event) {
console.log("数据库打开报错");
};
// 数据库有更新时候的回调
if (storeName !== undefined) {
request.onupgradeneeded = function (event) {
// 数据库创建或升级的时候会触发
console.log("创建成功!");
db = event.target.result; // 数据库对象
let objectStore;
// 创建存储库
objectStore = db.createObjectStore(storeName, {
keyPath: priKey, // 这是主键
autoIncrement: true // 实现自增
});
// 创建索引,在后面查询数据的时候可以根据索引查
// 首先创建主键对应的索引
objectStore.createIndex(priKey, priKey, {unique: true});
for (let i = 0; i < index.length; i++) {
// 然后创建主要索引
objectStore.createIndex(index[i], index[i], {unique: false});
}
};
}
});
};
/**
* 新增数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} data 数据
*/
export const addData = function addData(db, storeName, data) {
let request = db
.transaction([storeName], "readwrite") // 事务对象 指定表格名称和操作模式("只读"或"读写")
.objectStore(storeName) // 仓库对象
.add(data);
request.onsuccess = function (event) {
console.log("数据写入成功");
};
request.onerror = function (event) {
console.log("数据写入失败");
};
};
/**
* 通过主键读取数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} key 主键值
*/
export const getDataByKey = function getDataByKey(db, storeName, key) {
return new Promise((resolve, reject) => {
let transaction = db.transaction([storeName]); // 事务
let objectStore = transaction.objectStore(storeName); // 仓库对象
let request = objectStore.get(key); // 通过主键获取数据
request.onerror = function (event) {
console.log("事务失败");
};
request.onsuccess = function (event) {
console.log("主键查询结果: ", request.result);
resolve(request.result);
};
});
};
/**
* 通过游标读取数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
*/
export const cursorGetData = function cursorGetData(db, storeName) {
let list = [];
let store = db
.transaction(storeName, "readwrite") // 事务
.objectStore(storeName); // 仓库对象
let request = store.openCursor(); // 指针对象
// 游标开启成功,逐行读数据
request.onsuccess = function (e) {
let cursor = e.target.result;
if (cursor) {
// 必须要检查
list.push(cursor.value);
cursor.continue(); // 遍历了存储对象中的所有内容
} else {
console.log("游标读取的数据:", list);
}
};
};
/**
* 游标从后往前分页查询
* @param db
* @param storeName
* @param page
* @param pageSize
* @param skip
* @returns {Promise<unknown>}
*/
export const cursorGetDataAndPage = function cursorGetData(db, storeName, page, pageSize,skip=null) {
return new Promise((resolve, reject) => {
console.log('#skip', skip);
let list = [];
let counter = 0; // 计数器
let advanced = true; // 是否跳过多少条查询
let store = db
.transaction(storeName, "readwrite") // 事务
.objectStore(storeName); // 仓库对象
let request = store.openCursor(null,"prev"); // 指针对象
request.onsuccess = function (e) {
let cursor = e.target.result;
if (page > 1 && advanced) {
advanced = false;
cursor.advance(skip||(page - 1) * pageSize); // 跳过多少条
return;
}
if (cursor) {
// 必须要检查
list.push(cursor.value);
counter++;
if (counter < pageSize) {
cursor.continue(); // 遍历了存储对象中的所有内容
} else {
cursor = null;
resolve(list);
console.log("分页查询结果", list);
}
} else {
resolve(list);
console.log("分页查询结果", list);
}
};
request.onerror = function (e) {
reject('#数据库查询失败');
};
});
};
/**
* 通过索引和游标查询记录
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} indexName 索引名称
* @param {string} indexValue 索引值
*/
export const cursorGetDataByIndex = function cursorGetDataByIndex(db, storeName, indexName, indexValue) {
let list = [];
let store = db.transaction(storeName, "readwrite").objectStore(storeName); // 仓库对象
let request = store
.index(indexName) // 索引对象
.openCursor(IDBKeyRange.only(indexValue)); // 指针对象
request.onsuccess = function (e) {
let cursor = e.target.result;
if (cursor) {
// 必须要检查
list.push(cursor.value);
cursor.continue(); // 遍历了存储对象中的所有内容
} else {
console.log("游标索引查询结果:", list);
}
};
request.onerror = function (e) {
};
};
/**
* 通过索引和游标分页查询记录
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} indexName 索引名称
* @param {string} indexValue 索引值
* @param {number} page 页码
* @param {number} pageSize 查询条数
*/
export const cursorGetDataByIndexAndPagea = function cursorGetDataByIndexAndPage(
db,
storeName,
indexName,
indexValue,
page,
pageSize
) {
let list = [];
let counter = 0; // 计数器
let advanced = true; // 是否跳过多少条查询
let store = db.transaction(storeName, "readwrite").objectStore(storeName); // 仓库对象
let request = store
.index(indexName) // 索引对象
.openCursor(IDBKeyRange.only(indexValue)); // 指针对象
request.onsuccess = function (e) {
let cursor = e.target.result;
if (page > 1 && advanced) {
advanced = false;
cursor.advance((page - 1) * pageSize); // 跳过多少条
return;
}
if (cursor) {
// 必须要检查
list.push(cursor.value);
counter++;
if (counter < pageSize) {
cursor.continue(); // 遍历了存储对象中的所有内容
} else {
cursor = null;
console.log("分页查询结果", list);
}
} else {
console.log("分页查询结果", list);
}
};
request.onerror = function (e) {
};
};
/**
* 更新数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {object} data 数据
*/
export const updateDB = function updateDB(db, storeName, data) {
let request = db
.transaction([storeName], "readwrite") // 事务对象
.objectStore(storeName) // 仓库对象
.put(data);
request.onsuccess = function () {
console.log("数据更新成功");
};
request.onerror = function () {
console.log("数据更新失败");
};
};
/**
* 通过主键删除数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {object} id 主键值
*/
export const deleteDB = function deleteDB(db, storeName, id) {
let request = db
.transaction([storeName], "readwrite")
.objectStore(storeName)
.delete(id);
request.onsuccess = function () {
console.log("数据删除成功");
};
request.onerror = function () {
console.log("数据删除失败");
};
};
/**
* 通过索引和游标删除指定的数据
* @param {object} db 数据库实例
* @param {string} storeName 仓库名称
* @param {string} indexName 索引名
* @param {object} indexValue 索引值
*/
export const cursorDelete = function cursorDelete(db, storeName, indexName, indexValue) {
let store = db.transaction(storeName, "readwrite").objectStore(storeName);
let request = store
.index(indexName) // 索引对象
.openCursor(IDBKeyRange.only(indexValue)); // 指针对象
request.onsuccess = function (e) {
let cursor = e.target.result;
let deleteRequest;
if (cursor) {
deleteRequest = cursor.delete(); // 请求删除当前项
deleteRequest.onerror = function () {
console.log("游标删除该记录失败");
};
deleteRequest.onsuccess = function () {
console.log("游标删除该记录成功");
};
cursor.continue();
}
};
request.onerror = function (e) {
};
};
/**
* 关闭数据库
* @param {object} db 数据库实例
*/
export const closeDB = function closeDB(db) {
db.close();
console.log("数据库已关闭");
};
文章讲述了在实施chatgpt对话项目时,由于localStorage存储限制,转向使用indexedDB来保存聊天记录。通过indexedDB的仓库和键值对特性,实现了聊天记录的添加、查询和分页加载。在处理中,特别注意了游标查询的顺序调整以保证对话的正确显示。此外,还提供了indexedDB操作的封装代码示例。
924

被折叠的 条评论
为什么被折叠?



