feat(leaderboard): 训练完强刷 + 下拉刷新 force
- 云函数 _fetchLatestByOpenid 支持 force 跳过实例缓存重新扫描 - 训练完成设 _lb_force_refresh 标志,下次打开排行榜强刷,新数据立即可见 - 下拉刷新带 force=true,拉取最新数据 - 平时常规打开仍走 5min 缓存 + 客户端乐观缓存,不增加无谓扫描 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -43,11 +43,11 @@ const tsOf = (d) => {
|
||||
// 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
|
||||
// cost per page, and default get() order (_id ascending) won't drop/dup.
|
||||
const _fetchLatestByOpenid = async () => {
|
||||
if (_docsCache && (Date.now() - _docsCache.ts) < CACHE_TTL) {
|
||||
const _fetchLatestByOpenid = async (force) => {
|
||||
if (!force && _docsCache && (Date.now() - _docsCache.ts) < CACHE_TTL) {
|
||||
return _docsCache.latestByOpenid
|
||||
}
|
||||
// 缓存过期:复用 in-flight promise,避免并发请求各自全表扫描(缓存击穿)
|
||||
// 缓存过期或强刷(force):复用 in-flight promise,避免并发请求各自全表扫描(缓存击穿)
|
||||
if (_docsCachePromise) return _docsCachePromise
|
||||
_docsCachePromise = (async () => {
|
||||
const latestByOpenid = new Map()
|
||||
@@ -81,7 +81,7 @@ const _fetchLatestByOpenid = async () => {
|
||||
}
|
||||
|
||||
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))
|
||||
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
|
||||
// same records N times and inflate the board. Latest (not first) also
|
||||
// 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
|
||||
// logic doesn't need any dedup guards.
|
||||
|
||||
@@ -7,7 +7,7 @@ module.exports = {
|
||||
version: 'v3.01',
|
||||
|
||||
/** 最后更新日期,外显在设置页「关于」 */
|
||||
updatedAt: '2026-07-27',
|
||||
updatedAt: '2026-07-28',
|
||||
|
||||
/** 开发者名称 / 微信号,设置页点击可复制 */
|
||||
developer: '刘承',
|
||||
|
||||
@@ -35,16 +35,20 @@ Page({
|
||||
const tb = this.getTabBar()
|
||||
if (tb) tb.setData({ selected: 2 })
|
||||
} catch (e) {}
|
||||
if (this._firstShow) {
|
||||
this._firstShow = false
|
||||
this.fetchRank()
|
||||
return
|
||||
}
|
||||
this.fetchRank()
|
||||
// 训练完设的强刷标志:下次进排行榜跳过云函数缓存,立刻拿到含新记录的数据
|
||||
let force = false
|
||||
try {
|
||||
if (wx.getStorageSync('_lb_force_refresh')) {
|
||||
force = true
|
||||
wx.removeStorageSync('_lb_force_refresh')
|
||||
}
|
||||
} catch (e) {}
|
||||
this._firstShow = false
|
||||
this.fetchRank(undefined, force)
|
||||
},
|
||||
|
||||
onPullDownRefresh() {
|
||||
this.fetchRank(() => wx.stopPullDownRefresh())
|
||||
this.fetchRank(() => wx.stopPullDownRefresh(), true)
|
||||
},
|
||||
|
||||
switchPeriod(e) {
|
||||
@@ -54,7 +58,7 @@ Page({
|
||||
this.fetchRank()
|
||||
},
|
||||
|
||||
fetchRank(cb) {
|
||||
fetchRank(cb, force) {
|
||||
// 序列号:快速切换周期时丢弃旧响应,避免旧 period 的数据覆盖新 period
|
||||
this._fetchSeq = (this._fetchSeq || 0) + 1
|
||||
const seq = this._fetchSeq
|
||||
@@ -81,7 +85,8 @@ Page({
|
||||
name: 'leaderboard',
|
||||
data: {
|
||||
period,
|
||||
maxRank: config.leaderboardMaxRank
|
||||
maxRank: config.leaderboardMaxRank,
|
||||
force: !!force
|
||||
}
|
||||
}).then(res => {
|
||||
if (seq !== this._fetchSeq) { if (cb) cb(); return } // 过期响应,丢弃
|
||||
|
||||
@@ -403,6 +403,8 @@ Page({
|
||||
|
||||
// Flag for the index page's "训练完成" highlight
|
||||
try { wx.setStorageSync('_last_train_at', Date.now()) } catch (e) {}
|
||||
// 标记排行榜强刷:下次打开排行榜跳过云函数缓存,让刚训练的数据立即可见
|
||||
try { wx.setStorageSync('_lb_force_refresh', true) } catch (e) {}
|
||||
|
||||
const finalStreak = storage.getStreak().count
|
||||
|
||||
|
||||
Reference in New Issue
Block a user