import { Level } from 'level' import { type GroupNotify, RawMessage } from '../ntqqapi/types' import { DATA_DIR } from './utils' import { selfInfo } from './data' import { FileCache } from './types' import { log } from './utils/log' type ReceiveTempUinMap = Record class DBUtil { public readonly DB_KEY_PREFIX_MSG_ID = 'msg_id_' public readonly DB_KEY_PREFIX_MSG_SHORT_ID = 'msg_short_id_' public readonly DB_KEY_PREFIX_MSG_SEQ_ID = 'msg_seq_id_' public readonly DB_KEY_PREFIX_FILE = 'file_' public readonly DB_KEY_PREFIX_GROUP_NOTIFY = 'group_notify_' private readonly DB_KEY_RECEIVED_TEMP_UIN_MAP = 'received_temp_uin_map' public db: Level public cache: Record = {} // : RawMessage private currentShortId: number /* * 数据库结构 * msg_id_101231230999: {} // 长id: RawMessage * msg_short_id_1: 101231230999 // 短id: 长id * msg_seq_id_1: 101231230999 // 序列id: 长id * file_7827DBAFJFW2323.png: {} // 文件名: FileCache * */ constructor() { let initCount = 0 new Promise((resolve, reject) => { const initDB = () => { initCount++ // if (initCount > 50) { // return reject("init db fail") // } try { if (!selfInfo.uin) { setTimeout(initDB, 300) return } const DB_PATH = DATA_DIR + `/msg_${selfInfo.uin}` this.db = new Level(DB_PATH, { valueEncoding: 'json' }) console.log('llonebot init db success') resolve(null) } catch (e) { console.log('init db fail', e.stack.toString()) setTimeout(initDB, 300) } } setTimeout(initDB) }).then() const expiredMilliSecond = 1000 * 60 * 60 setInterval(() => { // this.cache = {} // 清理时间较久的缓存 const now = Date.now() for (let key in this.cache) { let message: RawMessage = this.cache[key] as RawMessage if (message?.msgTime) { if (now - parseInt(message.msgTime) * 1000 > expiredMilliSecond) { delete this.cache[key] // log("clear cache", key, message.msgTime); } } } }, expiredMilliSecond) } public async getReceivedTempUinMap(): Promise { try { this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = JSON.parse(await this.db.get(this.DB_KEY_RECEIVED_TEMP_UIN_MAP)) } catch (e) {} return (this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] || {}) as ReceiveTempUinMap } public setReceivedTempUinMap(data: ReceiveTempUinMap) { this.cache[this.DB_KEY_RECEIVED_TEMP_UIN_MAP] = data this.db.put(this.DB_KEY_RECEIVED_TEMP_UIN_MAP, JSON.stringify(data)).then() } private addCache(msg: RawMessage) { const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + msg.msgShortId const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq this.cache[longIdKey] = this.cache[shortIdKey] = msg } public clearCache() { this.cache = {} } async getMsgByShortId(shortMsgId: number): Promise { const shortMsgIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + shortMsgId if (this.cache[shortMsgIdKey]) { // log("getMsgByShortId cache", shortMsgIdKey, this.cache[shortMsgIdKey]) return this.cache[shortMsgIdKey] as RawMessage } try { const longId = await this.db.get(shortMsgIdKey) const msg = await this.getMsgByLongId(longId) this.addCache(msg) return msg } catch (e) { log('getMsgByShortId db error', e.stack.toString()) } } async getMsgByLongId(longId: string): Promise { const longIdKey = this.DB_KEY_PREFIX_MSG_ID + longId if (this.cache[longIdKey]) { return this.cache[longIdKey] as RawMessage } try { const data = await this.db.get(longIdKey) const msg = JSON.parse(data) this.addCache(msg) return msg } catch (e) { // log("getMsgByLongId db error", e.stack.toString()) } } async getMsgBySeqId(seqId: string): Promise { const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + seqId if (this.cache[seqIdKey]) { return this.cache[seqIdKey] as RawMessage } try { const longId = await this.db.get(seqIdKey) const msg = await this.getMsgByLongId(longId) this.addCache(msg) return msg } catch (e) { log('getMsgBySeqId db error', e.stack.toString()) } } async addMsg(msg: RawMessage) { // 有则更新,无则添加 // log("addMsg", msg.msgId, msg.msgSeq, msg.msgShortId); const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId let existMsg = this.cache[longIdKey] as RawMessage if (!existMsg) { try { existMsg = await this.getMsgByLongId(msg.msgId) } catch (e) { // log("addMsg getMsgByLongId error", e.stack.toString()) } } if (existMsg) { // log("消息已存在", existMsg.msgSeq, existMsg.msgShortId, existMsg.msgId) this.updateMsg(msg).then() return existMsg.msgShortId } this.addCache(msg) const shortMsgId = await this.genMsgShortId() const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + shortMsgId const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq msg.msgShortId = shortMsgId // log("新增消息记录", msg.msgId) this.db.put(shortIdKey, msg.msgId).then().catch() this.db.put(longIdKey, JSON.stringify(msg)).then().catch() try { await this.db.get(seqIdKey) } catch (e) { // log("新的seqId", seqIdKey) this.db.put(seqIdKey, msg.msgId).then().catch() } if (!this.cache[seqIdKey]) { this.cache[seqIdKey] = msg } return shortMsgId // log(`消息入库 ${seqIdKey}: ${msg.msgId}, ${shortMsgId}: ${msg.msgId}`); } async updateMsg(msg: RawMessage) { const longIdKey = this.DB_KEY_PREFIX_MSG_ID + msg.msgId let existMsg = this.cache[longIdKey] as RawMessage if (!existMsg) { try { existMsg = await this.getMsgByLongId(msg.msgId) } catch (e) { existMsg = msg } } Object.assign(existMsg, msg) this.db.put(longIdKey, JSON.stringify(existMsg)).then().catch() const shortIdKey = this.DB_KEY_PREFIX_MSG_SHORT_ID + existMsg.msgShortId const seqIdKey = this.DB_KEY_PREFIX_MSG_SEQ_ID + msg.msgSeq if (!this.cache[seqIdKey]) { this.cache[seqIdKey] = existMsg } this.db.put(shortIdKey, msg.msgId).then().catch() try { await this.db.get(seqIdKey) } catch (e) { this.db.put(seqIdKey, msg.msgId).then().catch() // log("更新seqId error", e.stack, seqIdKey); } // log("更新消息", existMsg.msgSeq, existMsg.msgShortId, existMsg.msgId); } private async genMsgShortId(): Promise { const key = 'msg_current_short_id' if (this.currentShortId === undefined) { try { let id: string = await this.db.get(key) this.currentShortId = parseInt(id) } catch (e) { this.currentShortId = -2147483640 } } this.currentShortId++ this.db.put(key, this.currentShortId.toString()).then().catch() return this.currentShortId } async addFileCache(fileNameOrUuid: string, data: FileCache) { const key = this.DB_KEY_PREFIX_FILE + fileNameOrUuid if (this.cache[key]) { return } let cacheDBData = { ...data } delete cacheDBData['downloadFunc'] this.cache[fileNameOrUuid] = data try { await this.db.put(key, JSON.stringify(cacheDBData)) } catch (e) { log('addFileCache db error', e.stack.toString()) } } async getFileCache(fileNameOrUuid: string): Promise { const key = this.DB_KEY_PREFIX_FILE + fileNameOrUuid if (this.cache[key]) { return this.cache[key] as FileCache } try { let data = await this.db.get(key) return JSON.parse(data) } catch (e) { // log("getFileCache db error", e.stack.toString()) } } async addGroupNotify(notify: GroupNotify) { const key = this.DB_KEY_PREFIX_GROUP_NOTIFY + notify.seq let existNotify = this.cache[key] as GroupNotify if (existNotify) { return } this.cache[key] = notify this.db.put(key, JSON.stringify(notify)).then().catch() } async getGroupNotify(seq: string): Promise { const key = this.DB_KEY_PREFIX_GROUP_NOTIFY + seq if (this.cache[key]) { return this.cache[key] as GroupNotify } try { let data = await this.db.get(key) return JSON.parse(data) } catch (e) { // log("getGroupNotify db error", e.stack.toString()) } } } export const dbUtil = new DBUtil()