161 lines
4.9 KiB
JavaScript
161 lines
4.9 KiB
JavaScript
// frontend/utils/preloadApi/storage.js
|
|
// 缓存适配器 — 用 uni.setStorage 系(SQLite 后端,不受 Android 10+ 分区存储影响)
|
|
//
|
|
// Android 10+ 适配说明(2026-07-13):
|
|
// 旧实现用 plus.io.requestFileSystem(PRIVATE_DOC=2, ...) + getDirectory + getFile +
|
|
// createWriter 写文件缓存,在某些 HBuilderX 版本 + targetSdkVersion >= 29 下,
|
|
// DCloud 内部仍把 _doc/ PRIVATE_DOC 映射到 PUBLIC_DOCUMENTS 路径(外部存储),
|
|
// 分区存储机制下报 "targetSdkVersion设置>=29后在Android10+系统设备不支持当前路径"。
|
|
// 修正:完全绕开 plus.io,改用 uni.setStorage(底层 plus.storage 是 SQLite,
|
|
// 走应用数据目录,与 Android 分区存储无关,无大小限制)。
|
|
|
|
// ── djb2 hash(与 core.js 共用逻辑,此处独立一份避免循环依赖)──
|
|
function hashStr(str) {
|
|
let hash = 5381
|
|
for (let i = 0; i < str.length; i++) {
|
|
hash = ((hash << 5) + hash + str.charCodeAt(i)) | 0
|
|
}
|
|
return (hash >>> 0).toString(16)
|
|
}
|
|
|
|
// ── key 工具 ──
|
|
// 统一前缀,方便 clearForUser 时按前缀删
|
|
const KEY_PREFIX = 'preload'
|
|
|
|
function makeKey(userId, cacheKey) {
|
|
return `${KEY_PREFIX}:${userId || 'guest'}:${hashStr(cacheKey)}`
|
|
}
|
|
|
|
function makeUserPrefix(userId) {
|
|
return `${KEY_PREFIX}:${userId || 'guest'}:`
|
|
}
|
|
|
|
function makeGlobalPrefix() {
|
|
return `${KEY_PREFIX}:`
|
|
}
|
|
|
|
// ── 公共 API ──
|
|
|
|
/**
|
|
* 读缓存条目
|
|
* @returns {Promise<{data, ts, ttl}|null>} null = 未命中
|
|
*/
|
|
export async function readEntry(userId, cacheKey) {
|
|
try {
|
|
const value = uni.getStorageSync(makeKey(userId, cacheKey))
|
|
if (!value) return null
|
|
// value 可能是字符串也可能是对象,统一处理
|
|
const obj = typeof value === 'string' ? JSON.parse(value) : value
|
|
if (!obj || typeof obj.ts !== 'number') return null
|
|
return obj
|
|
} catch (e) {
|
|
return null
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 写缓存条目(fire-and-forget,内部捕获异常)
|
|
* 与旧实现兼容:签名不变,但底层从 plus.io 改为 uni.setStorage
|
|
*/
|
|
export function writeEntry(userId, cacheKey, data, ts, ttl) {
|
|
const key = makeKey(userId, cacheKey)
|
|
const payload = { data, ts, ttl }
|
|
try {
|
|
uni.setStorageSync(key, payload)
|
|
} catch (e) {
|
|
console.warn('[preload] storage write failed:', key, e.message || e)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 删除指定用户的全部缓存条目
|
|
* 注意:uni 没有"按前缀删"接口,需要 getStorageInfo 拿所有 key 后过滤
|
|
*/
|
|
export async function clearForUser(userId) {
|
|
try {
|
|
const info = uni.getStorageInfoSync()
|
|
const prefix = makeUserPrefix(userId)
|
|
const toRemove = (info.keys || []).filter((k) => k.startsWith(prefix))
|
|
toRemove.forEach((k) => {
|
|
try { uni.removeStorageSync(k) } catch (e) { /* skip */ }
|
|
})
|
|
} catch (e) {
|
|
console.warn('[preload] clearForUser failed:', userId, e.message || e)
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 获取用户缓存总大小(字节)
|
|
* uni.getStorageInfoSync().currentSize 单位是 KB,需乘 1024
|
|
*/
|
|
export async function getTotalCacheSize(userId) {
|
|
try {
|
|
const info = uni.getStorageInfoSync()
|
|
const prefix = makeUserPrefix(userId)
|
|
const keys = (info.keys || []).filter((k) => k.startsWith(prefix))
|
|
if (keys.length === 0) return 0
|
|
// info.currentSize 是全局所有 storage 的 KB 数,无法按 user 拆。
|
|
// 退而求其次:遍历用户的每个 key,getStorageSync 估算字节大小
|
|
let totalBytes = 0
|
|
for (const k of keys) {
|
|
try {
|
|
const v = uni.getStorageSync(k)
|
|
if (v != null) {
|
|
// 粗估:JSON 序列化字节数
|
|
totalBytes += JSON.stringify(v).length
|
|
}
|
|
} catch (e) { /* skip */ }
|
|
}
|
|
return totalBytes
|
|
} catch (e) {
|
|
return 0
|
|
}
|
|
}
|
|
|
|
/**
|
|
* FIFO 淘汰最旧条目,直到总大小 < maxSize 字节
|
|
* uni 没有"按 mtime 排序"接口,改用 ts 字段(我们在写入时设置的)
|
|
*/
|
|
export async function evictOldest(userId, maxSize) {
|
|
try {
|
|
const prefix = makeUserPrefix(userId)
|
|
const info = uni.getStorageInfoSync()
|
|
const keys = (info.keys || []).filter((k) => k.startsWith(prefix))
|
|
if (keys.length === 0) return
|
|
|
|
// 收集每个 entry 的 ts
|
|
const entries = []
|
|
for (const k of keys) {
|
|
try {
|
|
const v = uni.getStorageSync(k)
|
|
const obj = typeof v === 'string' ? JSON.parse(v) : v
|
|
if (obj && typeof obj.ts === 'number') {
|
|
entries.push({ key: k, ts: obj.ts })
|
|
}
|
|
} catch (e) { /* skip corrupt entry */ }
|
|
}
|
|
|
|
// 按 ts 升序(最旧的在前)
|
|
entries.sort((a, b) => a.ts - b.ts)
|
|
|
|
// 累加当前估算大小,超出 maxSize * 0.8 就开始删
|
|
let totalSize = 0
|
|
for (const e of entries) {
|
|
try {
|
|
const v = uni.getStorageSync(e.key)
|
|
if (v != null) totalSize += JSON.stringify(v).length
|
|
} catch (err) { /* skip */ }
|
|
}
|
|
|
|
for (const e of entries) {
|
|
if (totalSize <= maxSize * 0.8) break
|
|
try {
|
|
const v = uni.getStorageSync(e.key)
|
|
if (v != null) totalSize -= JSON.stringify(v).length
|
|
uni.removeStorageSync(e.key)
|
|
} catch (err) { /* skip */ }
|
|
}
|
|
} catch (e) {
|
|
console.warn('[preload] evictOldest failed:', userId, e.message || e)
|
|
}
|
|
} |