Files
jiang13-bbs/backend/service/presence.go

51 lines
1.4 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package service
import (
"sync"
"time"
)
// touchInterval 进程内与 SQL 侧一致的限频窗口:每用户最多真正写库一次。
const touchInterval = time.Minute
// touchState 单用户心跳限频状态(进程内)。
type touchState struct {
mu sync.Mutex
last time.Time
}
// lastSeenTouch 按 userID 缓存上次尝试写库时间,避免热路径反复发 0 行 UPDATE。
var lastSeenTouch sync.Map // uint -> *touchState
// TouchLastSeen 更新用户最近活跃时间(在线统计)。
//
// 双层限频:
// 1. 进程内:60s 窗口内直接返回,不发 SQL(消掉 HTTP 并行 + WS ping 的风暴);
// 2. SQL WHERE:跨进程 / 重启后仍保证全局约 1 次/分钟真正写入。
//
// mutex 只保护「要不要写」的判定,不横跨 SQL;判定通过后乐观记下 last,
// 再异步 Exec,避免卡住 WS readPump。写库失败时 last_seen 最多偏旧 60s,
// 远小于 5 分钟在线窗口。
func (s *AuthService) TouchLastSeen(userID uint) {
v, _ := lastSeenTouch.LoadOrStore(userID, &touchState{})
st := v.(*touchState)
st.mu.Lock()
now := time.Now()
if !st.last.IsZero() && now.Sub(st.last) < touchInterval {
st.mu.Unlock()
return
}
st.last = now
st.mu.Unlock()
threshold := now.Add(-touchInterval)
go func() {
_ = s.db.Exec(
`UPDATE users SET last_seen_at = NOW()
WHERE id = ? AND (last_seen_at IS NULL OR last_seen_at < ?)`,
userID, threshold,
).Error
}()
}