feat(课堂看板): 标出谁在线、谁今天没上过线,请假框里没上线的人分在前面一堆
机房有两人共用一台电脑的,同桌只有一个登着,只看「此刻在不在线」会把旁边的人 当成没来。所以每次记在线时按天再记一笔「今天最后一次上线」: - 看板「在线」列(原「在写」):开着题的照旧写题号,其余写 在线 / N 分钟前在 / 今天没上线 - 还没交过的人里,今天没上过线的排最前 - 请假框分「今天没上线」「今天上过线」两堆,前一堆可以一键全勾上 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
395be1a19b
commit
e7b6e27b85
4 files changed
+141
-16
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import type { ChainableCommander } from "ioredis"
|
||||
|
||||
import { redis } from "../redis"
|
||||
import { calendarDay } from "../time"
|
||||
|
||||
/**
|
||||
* 「谁现在在线」。member = userId,score = 最后一次活动的毫秒时间戳。
|
||||
@@ -18,9 +19,24 @@ const PRESENCE_KEY = "online-users"
|
||||
*/
|
||||
const ONLINE_WINDOW_MS = 5 * 60 * 1000
|
||||
|
||||
/**
|
||||
* 「今天谁上过线、最后一次是几点」,一天一个 key,同样是 userId → 毫秒时间戳。
|
||||
*
|
||||
* 课堂看板拿它判断谁今天没来:机房有两人共用一台电脑的,同一时刻只有一个人登着,
|
||||
* 只看「此刻在不在线」会把坐在旁边的同桌当成没来。上过线的人不管现在登没登着都算来过。
|
||||
* 上面那张会被修剪到最近 5 分钟,留不住这个,所以单记一张,放两天自己过期。
|
||||
*/
|
||||
const seenTodayKey = (day: string) => `online-users:${day}`
|
||||
const SEEN_TODAY_TTL_SECONDS = 2 * 24 * 60 * 60
|
||||
|
||||
/** 记一笔活动。传 pipeline 而不是自己发命令:调用点都在热路径上 */
|
||||
export function markOnline(pipeline: ChainableCommander, userId: number) {
|
||||
pipeline.zadd(PRESENCE_KEY, Date.now(), String(userId))
|
||||
const now = Date.now()
|
||||
const seenKey = seenTodayKey(calendarDay(now))
|
||||
pipeline
|
||||
.zadd(PRESENCE_KEY, now, String(userId))
|
||||
.zadd(seenKey, now, String(userId))
|
||||
.expire(seenKey, SEEN_TODAY_TTL_SECONDS)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,3 +67,21 @@ export async function isUserOnline(userId: number) {
|
||||
const score = await redis.zscore(PRESENCE_KEY, String(userId))
|
||||
return score !== null && Number(score) >= Date.now() - ONLINE_WINDOW_MS
|
||||
}
|
||||
|
||||
/**
|
||||
* 这批人今天最后一次上线的时刻(毫秒),今天没上过线的不在结果里。
|
||||
* `online` 和 onlineUserIds 同一个窗口
|
||||
*/
|
||||
export async function seenToday(userIds: number[]) {
|
||||
const seen = new Map<number, { at: number; online: boolean }>()
|
||||
if (!userIds.length) return seen
|
||||
const scores = await redis.zmscore(seenTodayKey(calendarDay()), ...userIds.map(String))
|
||||
const cutoff = Date.now() - ONLINE_WINDOW_MS
|
||||
userIds.forEach((userId, i) => {
|
||||
const score = scores[i]
|
||||
if (score === null || score === undefined) return
|
||||
const at = Number(score)
|
||||
seen.set(userId, { at, online: at >= cutoff })
|
||||
})
|
||||
return seen
|
||||
}
|
||||
Reference in new issue
Block a user