176 lines
4.9 KiB
Go
176 lines
4.9 KiB
Go
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)
|
||
}
|