backend/pkg/library/centrifugo/online.go

176 lines
4.9 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 centrifugo
import (
"context"
"fmt"
"time"
)
// OnlineStatus 在線狀態
type OnlineStatus struct {
UserID string `json:"user_id"`
IsOnline bool `json:"is_online"`
LastSeenAt time.Time `json:"last_seen_at,omitempty"`
Clients int `json:"clients,omitempty"` // 連線數(可能多個設備)
}
// OnlineStore 在線狀態存儲介面
// 可以用 Redis、Memory 或其他存儲實作
type OnlineStore interface {
// SetOnline 設置用戶在線
SetOnline(ctx context.Context, userID string, ttl time.Duration) error
// SetOffline 設置用戶離線
SetOffline(ctx context.Context, userID string) error
// IsOnline 檢查用戶是否在線
IsOnline(ctx context.Context, userID string) (bool, error)
// GetOnlineUsers 獲取在線用戶列表
GetOnlineUsers(ctx context.Context, userIDs []string) (map[string]bool, error)
// IncrClient 增加用戶連線數
IncrClient(ctx context.Context, userID string) (int64, error)
// DecrClient 減少用戶連線數
DecrClient(ctx context.Context, userID string) (int64, error)
}
// OnlineManager 在線狀態管理器
// 結合 Redis 存儲和 Centrifugo Presence API 提供在線狀態追蹤
type OnlineManager struct {
client *Client
store OnlineStore
ttl time.Duration
}
// NewOnlineManager 創建在線狀態管理器
// store 可以為 nil此時只使用 Centrifugo Presence API
func NewOnlineManager(client *Client, store OnlineStore) *OnlineManager {
return &OnlineManager{
client: client,
store: store,
ttl: 5 * time.Minute, // 預設 5 分鐘過期
}
}
// NewOnlineManagerWithTTL 創建帶 TTL 的在線狀態管理器
func NewOnlineManagerWithTTL(client *Client, store OnlineStore, ttl time.Duration) *OnlineManager {
return &OnlineManager{
client: client,
store: store,
ttl: ttl,
}
}
// ==================== 連線事件處理 ====================
// HandleConnect 處理用戶連線事件(用於 Centrifugo Connect Proxy
func (m *OnlineManager) HandleConnect(ctx context.Context, userID string) error {
if m.store == nil {
return nil
}
// 增加連線數
count, err := m.store.IncrClient(ctx, userID)
if err != nil {
return fmt.Errorf("failed to incr client: %w", err)
}
// 如果是第一個連線,設置在線狀態
if count == 1 {
if err := m.store.SetOnline(ctx, userID, m.ttl); err != nil {
return fmt.Errorf("failed to set online: %w", err)
}
}
return nil
}
// HandleDisconnect 處理用戶斷線事件(用於 Centrifugo Disconnect Proxy
func (m *OnlineManager) HandleDisconnect(ctx context.Context, userID string) error {
if m.store == nil {
return nil
}
// 減少連線數
count, err := m.store.DecrClient(ctx, userID)
if err != nil {
return fmt.Errorf("failed to decr client: %w", err)
}
// 如果沒有連線了,設置離線狀態
if count <= 0 {
if err := m.store.SetOffline(ctx, userID); err != nil {
return fmt.Errorf("failed to set offline: %w", err)
}
}
return nil
}
// ==================== 在線狀態查詢 ====================
// IsUserOnline 檢查用戶是否在線(使用 Store
func (m *OnlineManager) IsUserOnline(ctx context.Context, userID string) (bool, error) {
if m.store == nil {
return false, ErrOnlineStoreNotConfigured
}
return m.store.IsOnline(ctx, userID)
}
// GetUsersOnlineStatus 批量獲取用戶在線狀態
func (m *OnlineManager) GetUsersOnlineStatus(ctx context.Context, userIDs []string) (map[string]bool, error) {
if m.store == nil {
return nil, ErrOnlineStoreNotConfigured
}
return m.store.GetOnlineUsers(ctx, userIDs)
}
// RefreshOnline 刷新用戶在線狀態(用於心跳)
func (m *OnlineManager) RefreshOnline(ctx context.Context, userID string) error {
if m.store == nil {
return nil
}
return m.store.SetOnline(ctx, userID, m.ttl)
}
// ==================== Centrifugo Presence API ====================
// IsUserInChannel 檢查用戶是否在指定頻道中(使用 Centrifugo Presence
func (m *OnlineManager) IsUserInChannel(ctx context.Context, userID, channel string) (bool, error) {
presence, err := m.client.Presence(ctx, channel)
if err != nil {
return false, err
}
for _, info := range presence.Presence {
if info.User == userID {
return true, nil
}
}
return false, nil
}
// GetChannelOnlineUsers 獲取頻道中的在線用戶(使用 Centrifugo Presence
func (m *OnlineManager) GetChannelOnlineUsers(ctx context.Context, channel string) ([]string, error) {
presence, err := m.client.Presence(ctx, channel)
if err != nil {
return nil, err
}
// 去重(一個用戶可能有多個連線)
userMap := make(map[string]bool)
for _, info := range presence.Presence {
userMap[info.User] = true
}
users := make([]string, 0, len(userMap))
for userID := range userMap {
users = append(users, userID)
}
return users, nil
}
// GetChannelStats 獲取頻道在線統計
func (m *OnlineManager) GetChannelStats(ctx context.Context, channel string) (*PresenceStatsResult, error) {
return m.client.PresenceStats(ctx, channel)
}