feat(leaderboard): 训练完强刷 + 下拉刷新 force

- 云函数 _fetchLatestByOpenid 支持 force 跳过实例缓存重新扫描
- 训练完成设 _lb_force_refresh 标志,下次打开排行榜强刷,新数据立即可见
- 下拉刷新带 force=true,拉取最新数据
- 平时常规打开仍走 5min 缓存 + 客户端乐观缓存,不增加无谓扫描

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-07-28 08:47:41 +08:00
parent e2becc24aa
commit b42d080826
4 changed files with 22 additions and 15 deletions
+5 -5
View File
@@ -43,11 +43,11 @@ const tsOf = (d) => {
// per openid. Uses cursor pagination (_id > lastId) instead of skip(N): // per openid. Uses cursor pagination (_id > lastId) instead of skip(N):
// skip is O(N) and degrades as the collection grows; a cursor is constant // skip is O(N) and degrades as the collection grows; a cursor is constant
// cost per page, and default get() order (_id ascending) won't drop/dup. // cost per page, and default get() order (_id ascending) won't drop/dup.
const _fetchLatestByOpenid = async () => { const _fetchLatestByOpenid = async (force) => {
if (_docsCache && (Date.now() - _docsCache.ts) < CACHE_TTL) { if (!force && _docsCache && (Date.now() - _docsCache.ts) < CACHE_TTL) {
return _docsCache.latestByOpenid return _docsCache.latestByOpenid
} }
// 缓存过期:复用 in-flight promise,避免并发请求各自全表扫描(缓存击穿) // 缓存过期或强刷(force):复用 in-flight promise,避免并发请求各自全表扫描(缓存击穿)
if (_docsCachePromise) return _docsCachePromise if (_docsCachePromise) return _docsCachePromise
_docsCachePromise = (async () => { _docsCachePromise = (async () => {
const latestByOpenid = new Map() const latestByOpenid = new Map()
@@ -81,7 +81,7 @@ const _fetchLatestByOpenid = async () => {
} }
exports.main = async (event) => { exports.main = async (event) => {
const { period, maxRank } = event || {} const { period, maxRank, force } = event || {}
const limit = Math.max(1, Math.min(parseInt(maxRank) || DEFAULT_MAX_RANK, 500)) const limit = Math.max(1, Math.min(parseInt(maxRank) || DEFAULT_MAX_RANK, 500))
if (!period) return { err: 'missing period' } if (!period) return { err: 'missing period' }
@@ -107,7 +107,7 @@ exports.main = async (event) => {
// start add() bug; only the latest updatedAt counts, else we'd sum the // start add() bug; only the latest updatedAt counts, else we'd sum the
// same records N times and inflate the board. Latest (not first) also // same records N times and inflate the board. Latest (not first) also
// gives the freshest profile.nickname before admin-dedupe runs. // gives the freshest profile.nickname before admin-dedupe runs.
const latestByOpenid = await _fetchLatestByOpenid() const latestByOpenid = await _fetchLatestByOpenid(force)
// Second pass: each openid appears exactly once, so the accumulation // Second pass: each openid appears exactly once, so the accumulation
// logic doesn't need any dedup guards. // logic doesn't need any dedup guards.
+1 -1
View File
@@ -7,7 +7,7 @@ module.exports = {
version: 'v3.01', version: 'v3.01',
/** 最后更新日期,外显在设置页「关于」 */ /** 最后更新日期,外显在设置页「关于」 */
updatedAt: '2026-07-27', updatedAt: '2026-07-28',
/** 开发者名称 / 微信号,设置页点击可复制 */ /** 开发者名称 / 微信号,设置页点击可复制 */
developer: '刘承', developer: '刘承',
+13 -8
View File
@@ -35,16 +35,20 @@ Page({
const tb = this.getTabBar() const tb = this.getTabBar()
if (tb) tb.setData({ selected: 2 }) if (tb) tb.setData({ selected: 2 })
} catch (e) {} } catch (e) {}
if (this._firstShow) { // 训练完设的强刷标志:下次进排行榜跳过云函数缓存,立刻拿到含新记录的数据
this._firstShow = false let force = false
this.fetchRank() try {
return if (wx.getStorageSync('_lb_force_refresh')) {
force = true
wx.removeStorageSync('_lb_force_refresh')
} }
this.fetchRank() } catch (e) {}
this._firstShow = false
this.fetchRank(undefined, force)
}, },
onPullDownRefresh() { onPullDownRefresh() {
this.fetchRank(() => wx.stopPullDownRefresh()) this.fetchRank(() => wx.stopPullDownRefresh(), true)
}, },
switchPeriod(e) { switchPeriod(e) {
@@ -54,7 +58,7 @@ Page({
this.fetchRank() this.fetchRank()
}, },
fetchRank(cb) { fetchRank(cb, force) {
// 序列号:快速切换周期时丢弃旧响应,避免旧 period 的数据覆盖新 period // 序列号:快速切换周期时丢弃旧响应,避免旧 period 的数据覆盖新 period
this._fetchSeq = (this._fetchSeq || 0) + 1 this._fetchSeq = (this._fetchSeq || 0) + 1
const seq = this._fetchSeq const seq = this._fetchSeq
@@ -81,7 +85,8 @@ Page({
name: 'leaderboard', name: 'leaderboard',
data: { data: {
period, period,
maxRank: config.leaderboardMaxRank maxRank: config.leaderboardMaxRank,
force: !!force
} }
}).then(res => { }).then(res => {
if (seq !== this._fetchSeq) { if (cb) cb(); return } // 过期响应,丢弃 if (seq !== this._fetchSeq) { if (cb) cb(); return } // 过期响应,丢弃
+2
View File
@@ -403,6 +403,8 @@ Page({
// Flag for the index page's "训练完成" highlight // Flag for the index page's "训练完成" highlight
try { wx.setStorageSync('_last_train_at', Date.now()) } catch (e) {} try { wx.setStorageSync('_last_train_at', Date.now()) } catch (e) {}
// 标记排行榜强刷:下次打开排行榜跳过云函数缓存,让刚训练的数据立即可见
try { wx.setStorageSync('_lb_force_refresh', true) } catch (e) {}
const finalStreak = storage.getStreak().count const finalStreak = storage.getStreak().count