const cloud = require('wx-server-sdk') cloud.init({ env: cloud.DYNAMIC_CURRENT_ENV }) const db = cloud.database() const _ = db.command const COLLECTION = 'plank_data' const PAGE_SIZE = 100 const MAX_RANK = 100 const pad = (n) => String(n).padStart(2, '0') exports.main = async (event) => { const { period } = event if (!period) return { err: 'missing period' } const now = new Date() const today = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}` const thisMonth = today.substring(0, 7) const thisYear = String(now.getFullYear()) const myOpenid = cloud.getWXContext().OPENID let prefix, exact if (period === 'day') { exact = today; prefix = null } else if (period === 'month') { prefix = thisMonth; exact = null } else if (period === 'year') { prefix = thisYear; exact = null } else return { err: 'invalid period' } const userMap = new Map() let skip = 0 while (true) { const res = await db.collection(COLLECTION).skip(skip).limit(PAGE_SIZE).get() if (!res.data || res.data.length === 0) break for (const doc of res.data) { const records = doc.records || {} let duration = 0 let sessions = 0 for (const monthKey of Object.keys(records)) { if (prefix && !monthKey.startsWith(prefix)) continue for (const r of records[monthKey]) { if (period === 'day') { if (r.date === exact) { duration += r.duration; sessions++ } } else { if (r.date.startsWith(prefix)) { duration += r.duration; sessions++ } } } } if (duration > 0) { const openid = doc._openid || 'unknown' const entry = userMap.get(openid) if (entry) { entry.duration += duration entry.sessions += sessions } else { userMap.set(openid, { openid, duration, sessions }) } } } if (res.data.length < PAGE_SIZE) break skip += PAGE_SIZE } // Full sorted list (for ranking) const allSorted = Array.from(userMap.values()) .sort((a, b) => b.duration - a.duration) // My entry always included, even if outside top N const myEntry = userMap.get(myOpenid) const myRank = myEntry ? allSorted.findIndex(e => e.openid === myOpenid) + 1 : 0 // Top N for the board const ranked = allSorted.slice(0, MAX_RANK).map((item, i) => ({ rank: i + 1, openid: item.openid, name: maskOpenid(item.openid), duration: item.duration, sessions: item.sessions })) return { period, ranked, myOpenid, myEntry: myEntry ? { rank: myRank, openid: myEntry.openid, name: maskOpenid(myEntry.openid), duration: myEntry.duration, sessions: myEntry.sessions } : null, updatedAt: now.toISOString() } } function maskOpenid(openid) { if (!openid || openid === 'unknown') return '未知用户' if (openid.length <= 4) return '****' + openid return '****' + openid.slice(-4) }