- 新增界面截图与 CONTRIBUTING.md - README 以三栏布局、暗色主题、移动端等视觉亮点为主 - go module 迁移至 git.iioio.com/freefire/jiang13-forum Co-authored-by: Cursor <cursoragent@cursor.com>
134 lines
2.5 KiB
Go
134 lines
2.5 KiB
Go
package service
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"git.iioio.com/freefire/jiang13-forum/model"
|
|
)
|
|
|
|
const onlineTTL = 5 * time.Minute
|
|
|
|
// OnlineService 在线浏览追踪(内存):登录会员 + 游客
|
|
type OnlineService struct {
|
|
mu sync.RWMutex
|
|
seen map[uint]time.Time // 登录用户
|
|
guests map[string]time.Time // 游客访客标识
|
|
}
|
|
|
|
func NewOnlineService() *OnlineService {
|
|
s := &OnlineService{
|
|
seen: make(map[uint]time.Time),
|
|
guests: make(map[string]time.Time),
|
|
}
|
|
go s.cleanup()
|
|
return s
|
|
}
|
|
|
|
func (s *OnlineService) Ping(userID uint) {
|
|
if userID == 0 {
|
|
return
|
|
}
|
|
s.mu.Lock()
|
|
s.seen[userID] = time.Now()
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
func (s *OnlineService) PingGuest(visitorID string) {
|
|
if visitorID == "" {
|
|
return
|
|
}
|
|
s.mu.Lock()
|
|
s.guests[visitorID] = time.Now()
|
|
s.mu.Unlock()
|
|
}
|
|
|
|
type OnlineUser struct {
|
|
ID uint `json:"id"`
|
|
Nickname string `json:"nickname"`
|
|
Avatar string `json:"avatar"`
|
|
}
|
|
|
|
func (s *OnlineService) List(limit int) []OnlineUser {
|
|
if limit <= 0 {
|
|
limit = 20
|
|
}
|
|
cutoff := time.Now().Add(-onlineTTL)
|
|
s.mu.RLock()
|
|
var ids []uint
|
|
for id, t := range s.seen {
|
|
if t.After(cutoff) {
|
|
ids = append(ids, id)
|
|
}
|
|
}
|
|
s.mu.RUnlock()
|
|
if len(ids) == 0 {
|
|
return nil
|
|
}
|
|
var users []model.User
|
|
model.DB.Where("id IN ?", ids).Limit(limit).Find(&users)
|
|
out := make([]OnlineUser, 0, len(users))
|
|
for _, u := range users {
|
|
out = append(out, OnlineUser{ID: u.ID, Nickname: u.Nickname, Avatar: u.Avatar})
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (s *OnlineService) CountMembers() int {
|
|
return s.countSeen(s.seen)
|
|
}
|
|
|
|
func (s *OnlineService) CountGuests() int {
|
|
return s.countSeenString(s.guests)
|
|
}
|
|
|
|
// Count 当前浏览总人数(会员 + 游客)
|
|
func (s *OnlineService) Count() int {
|
|
return s.CountMembers() + s.CountGuests()
|
|
}
|
|
|
|
func (s *OnlineService) countSeen(m map[uint]time.Time) int {
|
|
cutoff := time.Now().Add(-onlineTTL)
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
n := 0
|
|
for _, t := range m {
|
|
if t.After(cutoff) {
|
|
n++
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
func (s *OnlineService) countSeenString(m map[string]time.Time) int {
|
|
cutoff := time.Now().Add(-onlineTTL)
|
|
s.mu.RLock()
|
|
defer s.mu.RUnlock()
|
|
n := 0
|
|
for _, t := range m {
|
|
if t.After(cutoff) {
|
|
n++
|
|
}
|
|
}
|
|
return n
|
|
}
|
|
|
|
func (s *OnlineService) cleanup() {
|
|
ticker := time.NewTicker(time.Minute)
|
|
for range ticker.C {
|
|
cutoff := time.Now().Add(-onlineTTL * 2)
|
|
s.mu.Lock()
|
|
for id, t := range s.seen {
|
|
if t.Before(cutoff) {
|
|
delete(s.seen, id)
|
|
}
|
|
}
|
|
for id, t := range s.guests {
|
|
if t.Before(cutoff) {
|
|
delete(s.guests, id)
|
|
}
|
|
}
|
|
s.mu.Unlock()
|
|
}
|
|
}
|