admin-vue3/src/views/im/utils/storage.ts

77 lines
3.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

import localforage from 'localforage'
import { toRaw } from 'vue'
import { CACHE_KEY, useCache } from '@/hooks/web/useCache'
/**
* IM 模块的 IndexedDB 实例localforage 优先 IndexedDB自动降级到 WebSQL / localStorage
*
* 为什么不用 localStorage 直接存:
* 1. 配额localStorage 整体上限 5~10MB多会话长历史很容易撑爆
* 2. 写放大localStorage 必须按 key 整体写入,单次写就是 MB 级序列化阻塞主线程
*
* 配套策略:会话与消息按 key 分桶(见 StorageKeys让单次变更只重写最小粒度的 key
* IndexedDB 默认配额一般是浏览器可用空间的 ~50%,远大于 localStorage配合分桶才发挥效果
*/
export const imStorage = localforage.createInstance({
name: 'im',
storeName: 'conversation',
description: 'IM 会话索引与消息缓存'
})
/**
* 存储 key 统一在此生成
*
* - 会话 / 好友 / 群相关业务数据走 imStorageIndexedDBkey 都按 userId 分桶
* - 轻量 UI 状态(侧边栏宽度)仍走 localStorage体积小、跨 Tab 同步天然,没必要走 IndexedDB
*
* 所有业务 key 都注入 userId多账号切换按用户隔离避免数据互串账号切换时只清 in-memory、IDB 数据保留——回切旧账号能秒开,不浪费已下载好友 / 群 / 成员快照
*/
export const StorageKeys = {
/**
* 会话索引:游标 + 会话元数据(不含 messages对应 ConversationStoreMeta
*
* 任何会话级元数据变更top / muted / unread / 列表增删 / 排序)都会重写这一个 key由于 messages 已剥离到独立 key单次写体积稳定仅元数据量级 KB 级)
*/
conversationMeta: (userId: number | string) => `conversation:meta:${userId}`,
/**
* 单会话消息:按 (type, targetId) 分桶,存 Message[]
*
* - type私聊 / 群聊(对齐 ImConversationType
* - targetId私聊的对方 userId / 群聊的 groupId
*
* 每条消息变更只重写当前会话这一个 key避免老方案"全量写所有会话所有消息"的写放大;软删除会话时由 conversationStore.removeConversationMessages 物理删除该 key避免 orphan 残留
*/
conversationMessages: (userId: number | string, type: number, targetId: number) =>
`conversation:messages:${userId}:${type}:${targetId}`,
/** 好友列表整桶(含 DISABLE 软删记录);好友量级有限,不维护增量 */
friends: (userId: number | string) => `friends:${userId}`,
/** 群列表整桶(不含 members剥离到独立 key保证整桶写不带成员爆量 */
groups: (userId: number | string) => `groups:${userId}`,
/** 单群成员,按 groupId 分桶——单群可上百-千级,跟懒加载粒度对齐;群解散时物理删 */
groupMembers: (userId: number | string, groupId: number) =>
`groupMembers:${userId}:${groupId}`,
/** 侧边栏宽度localStorage三个 Tab 共用一份记忆,对齐微信(拖一次到处一致)。 */
asideWidth: 'im:aside'
} as const
/** 取当前登录用户编号;返回 0 表示未登录,调用方一律早 return 不写无主 key */
export function getCurrentUserId(): number {
const { wsCache } = useCache()
const user = wsCache.get(CACHE_KEY.USER)?.user
return Number(user?.id) || 0
}
/** IDB 写入fire-and-forget */
export function setQuietly(key: string, value: unknown, errorLabel: string): void {
// toRaw 拆 Vue / Pinia reactive Proxy——structuredClone 不接 Proxy 会抛 DataCloneError 静默丢盘
const raw = value && typeof value === 'object' ? toRaw(value) : value
void imStorage.setItem(key, raw).catch((e) => console.warn(errorLabel, e))
}
export function removeQuietly(key: string, errorLabel: string): void {
void imStorage.removeItem(key).catch((e) => console.warn(errorLabel, e))
}