From 983ae1b9cbb17b63662410f7d1b40545702551aa Mon Sep 17 00:00:00 2001 From: liucheng Date: Sat, 25 Jul 2026 15:01:51 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E7=BC=93=E5=AD=98=E5=87=BB=E7=A9=BF/pla?= =?UTF-8?q?y=E7=AB=9E=E6=80=81/onUnload=E6=B3=84=E6=BC=8F/=E6=B8=85?= =?UTF-8?q?=E9=99=A4=E6=95=B0=E6=8D=AEUI/=E7=BB=9F=E8=AE=A1NaN?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - leaderboard 云函数: _fetchLatestByOpenid 加 in-flight promise,并发请求复用同一次扫描,防缓存击穿 - voice: play 加序列号,丢弃过期播放,避免旧语音截断新语音 - timer: onUnload 补取消 _completionCountUp,系统返回手势不再泄漏动画定时器 - settings: 清除数据 setData 补 voiceGender/vibrateIntensity 重置,避免 UI 与存储不一致 - storage: getTotalStats 对 r.duration 防御(Number||0),避免脏数据产生 NaN 传染 Co-Authored-By: Claude --- cloudfunctions/leaderboard/index.js | 6 ++++++ pages/settings/settings.js | 2 ++ pages/timer/timer.js | 4 ++++ utils/storage.js | 5 +++-- utils/voice.js | 4 ++++ 5 files changed, 19 insertions(+), 2 deletions(-) diff --git a/cloudfunctions/leaderboard/index.js b/cloudfunctions/leaderboard/index.js index 7a07f69..310bde8 100644 --- a/cloudfunctions/leaderboard/index.js +++ b/cloudfunctions/leaderboard/index.js @@ -22,6 +22,7 @@ const TZ_OFFSET_MS = 8 * 60 * 60 * 1000 // across instances, which is fine - the board needn't be real-time. const CACHE_TTL = 60 * 1000 let _docsCache = null // { latestByOpenid: Map, ts } +let _docsCachePromise = null // in-flight 扫描 promise,防缓存击穿 const tsOf = (d) => { if (!d.updatedAt) return 0 @@ -40,6 +41,9 @@ const _fetchLatestByOpenid = async () => { if (_docsCache && (Date.now() - _docsCache.ts) < CACHE_TTL) { return _docsCache.latestByOpenid } + // 缓存过期:复用 in-flight promise,避免并发请求各自全表扫描(缓存击穿) + if (_docsCachePromise) return _docsCachePromise + _docsCachePromise = (async () => { const latestByOpenid = new Map() // Exclude accounts that haven't synced in 90+ days to bound the scan. // Docs without an updatedAt field (pre-fix legacy) are included as well. @@ -66,6 +70,8 @@ const _fetchLatestByOpenid = async () => { } _docsCache = { latestByOpenid, ts: Date.now() } return latestByOpenid + })() + try { return await _docsCachePromise } finally { _docsCachePromise = null } } exports.main = async (event) => { diff --git a/pages/settings/settings.js b/pages/settings/settings.js index e734997..bf10f1f 100644 --- a/pages/settings/settings.js +++ b/pages/settings/settings.js @@ -371,6 +371,8 @@ Page({ currentPlanId: 'beginner', voiceGuide: true, vibrate: true, + voiceGender: 'female', + vibrateIntensity: 'heavy', currentThemeId: 'orange', nickName: '', avatarUrl: '' diff --git a/pages/timer/timer.js b/pages/timer/timer.js index 56b092c..971f9e3 100644 --- a/pages/timer/timer.js +++ b/pages/timer/timer.js @@ -292,6 +292,10 @@ Page({ this._countUpTask.cancel() this._countUpTask = null } + if (this._completionCountUp) { + this._completionCountUp.cancel() + this._completionCountUp = null + } }, onStart() { diff --git a/utils/storage.js b/utils/storage.js index 6567c01..de0ee96 100644 --- a/utils/storage.js +++ b/utils/storage.js @@ -156,9 +156,10 @@ const getTotalStats = () => { let maxDuration = 0 Object.keys(records).forEach((key) => { records[key].forEach((r) => { - totalDuration += r.duration + const d = Number(r && r.duration) || 0 + totalDuration += d totalSessions++ - if (r.duration > maxDuration) maxDuration = r.duration + if (d > maxDuration) maxDuration = d }) }) return { totalDuration, totalSessions, maxDuration } diff --git a/utils/voice.js b/utils/voice.js index eca3ecd..4f086d4 100644 --- a/utils/voice.js +++ b/utils/voice.js @@ -32,6 +32,7 @@ const FILEID_CACHE_KEY = 'tts_fileid_cache' const LOCAL_CACHE_KEY = 'tts_local_cache' // { cacheKey: savedFilePath } - 持久本地文件,跨重启 let _ctx = null +let _playSeq = 0 // play 序列号,丢弃过期播放避免乱序 let _localCache = null // { cacheKey: savedFilePath } - persisted, lazy-loaded const _urlCache = {} // { promptKey: audioUrl } — per-session, volatile let _fileIDCache = null // { promptKey: fileID } — persisted, lazy-loaded @@ -204,8 +205,11 @@ const preload = (keys) => { */ const play = async (promptKey) => { if (!PROMPTS[promptKey]) return + const seq = ++_playSeq const src = await _resolveSrc(promptKey) if (!src) return + // 期间若有新的 play 调用,放弃本次,避免旧语音截断新语音 + if (seq !== _playSeq) return const ctx = _getCtx() try { ctx.stop()