188 lines
5.3 KiB
Go
188 lines
5.3 KiB
Go
// Package centrifugo 提供 Centrifugo 即時訊息服務的完整 Go 客戶端
|
||
//
|
||
// 功能包含:
|
||
// - HTTP API 客戶端(發布訊息、訂閱管理、在線狀態等)
|
||
// - JWT Token 生成(連線認證、私有頻道訂閱)
|
||
// - Token 黑名單管理(撤銷單一 Token、撤銷用戶所有 Token)
|
||
// - 在線狀態追蹤(Redis 或記憶體存儲)
|
||
//
|
||
// 基本使用:
|
||
//
|
||
// // 創建服務實例
|
||
// svc := centrifugo.NewService(centrifugo.ServiceConfig{
|
||
// APIURL: "http://localhost:8000",
|
||
// APIKey: "your-api-key",
|
||
// TokenSecret: "your-jwt-secret",
|
||
// Redis: redisClient, // 可選,用於黑名單和在線狀態
|
||
// })
|
||
//
|
||
// // 發布訊息
|
||
// svc.Client().PublishJSON(ctx, "chat:room-1", data)
|
||
//
|
||
// // 生成 Token
|
||
// token, _ := svc.Token().QuickConnectionToken("user-123")
|
||
//
|
||
// // 撤銷用戶所有 Token 並踢出
|
||
// svc.InvalidateUser(ctx, "user-123")
|
||
package centrifugo
|
||
|
||
import (
|
||
"context"
|
||
"time"
|
||
|
||
"github.com/zeromicro/go-zero/core/stores/redis"
|
||
)
|
||
|
||
// Service Centrifugo 服務整合介面
|
||
// 提供 HTTP API、Token 生成、黑名單管理、在線狀態追蹤的統一入口
|
||
type Service struct {
|
||
client *Client
|
||
token *TokenGenerator
|
||
blacklist *TokenBlacklist
|
||
online *OnlineManager
|
||
}
|
||
|
||
// ServiceConfig 服務配置
|
||
type ServiceConfig struct {
|
||
// APIURL Centrifugo HTTP API 地址(必填)
|
||
APIURL string
|
||
// APIKey Centrifugo API 密鑰(必填)
|
||
APIKey string
|
||
// TokenSecret JWT Token 簽名密鑰(必填)
|
||
TokenSecret string
|
||
// TokenExpire Token 過期時間(預設 1 小時)
|
||
TokenExpire time.Duration
|
||
// Redis 客戶端(可選,用於黑名單和在線狀態)
|
||
Redis *redis.Redis
|
||
// ClientConfig HTTP 客戶端配置(可選)
|
||
ClientConfig *ClientConfig
|
||
// OnlineTTL 在線狀態過期時間(預設 5 分鐘)
|
||
OnlineTTL time.Duration
|
||
// KeyPrefix Redis key 前綴(預設 "centrifugo:")
|
||
KeyPrefix string
|
||
}
|
||
|
||
// NewService 創建 Centrifugo 服務實例
|
||
func NewService(cfg ServiceConfig) *Service {
|
||
// 設定預設值
|
||
if cfg.TokenExpire == 0 {
|
||
cfg.TokenExpire = time.Hour
|
||
}
|
||
if cfg.OnlineTTL == 0 {
|
||
cfg.OnlineTTL = 5 * time.Minute
|
||
}
|
||
if cfg.KeyPrefix == "" {
|
||
cfg.KeyPrefix = "centrifugo:"
|
||
}
|
||
|
||
// 創建 HTTP 客戶端
|
||
var client *Client
|
||
if cfg.ClientConfig != nil {
|
||
client = NewClientWithConfig(*cfg.ClientConfig)
|
||
} else {
|
||
client = NewClient(cfg.APIURL, cfg.APIKey)
|
||
}
|
||
|
||
// 創建 Token 生成器
|
||
token := NewTokenGeneratorWithConfig(TokenConfig{
|
||
Secret: cfg.TokenSecret,
|
||
ExpireIn: cfg.TokenExpire,
|
||
})
|
||
|
||
svc := &Service{
|
||
client: client,
|
||
token: token,
|
||
}
|
||
|
||
// 如果有 Redis,創建黑名單管理器和在線狀態管理器
|
||
if cfg.Redis != nil {
|
||
svc.blacklist = NewTokenBlacklistWithPrefix(cfg.Redis, cfg.KeyPrefix+"blacklist:")
|
||
store := NewRedisOnlineStoreWithPrefix(cfg.Redis, cfg.KeyPrefix+"online:")
|
||
svc.online = NewOnlineManagerWithTTL(client, store, cfg.OnlineTTL)
|
||
}
|
||
|
||
return svc
|
||
}
|
||
|
||
// Client 返回 HTTP API 客戶端
|
||
func (s *Service) Client() *Client {
|
||
return s.client
|
||
}
|
||
|
||
// Token 返回 Token 生成器
|
||
func (s *Service) Token() *TokenGenerator {
|
||
return s.token
|
||
}
|
||
|
||
// Blacklist 返回黑名單管理器(可能為 nil)
|
||
func (s *Service) Blacklist() *TokenBlacklist {
|
||
return s.blacklist
|
||
}
|
||
|
||
// Online 返回在線狀態管理器(可能為 nil)
|
||
func (s *Service) Online() *OnlineManager {
|
||
return s.online
|
||
}
|
||
|
||
// ==================== 便捷方法 ====================
|
||
|
||
// PublishJSON 發布 JSON 訊息到頻道
|
||
func (s *Service) PublishJSON(ctx context.Context, channel string, data interface{}) (*PublishResult, error) {
|
||
return s.client.PublishJSON(ctx, channel, data)
|
||
}
|
||
|
||
// BroadcastJSON 批量發布 JSON 訊息到多個頻道
|
||
func (s *Service) BroadcastJSON(ctx context.Context, channels []string, data interface{}) error {
|
||
return s.client.BroadcastJSON(ctx, channels, data)
|
||
}
|
||
|
||
// Disconnect 斷開用戶連線
|
||
func (s *Service) Disconnect(ctx context.Context, userID string) error {
|
||
return s.client.Disconnect(ctx, userID)
|
||
}
|
||
|
||
// GenerateToken 快速生成連線 Token
|
||
func (s *Service) GenerateToken(userID string) (string, error) {
|
||
return s.token.QuickConnectionToken(userID)
|
||
}
|
||
|
||
// GenerateTokenWithInfo 生成帶用戶資訊的連線 Token
|
||
func (s *Service) GenerateTokenWithInfo(userID string, info map[string]interface{}) (string, error) {
|
||
return s.token.GenerateConnectionToken(ConnectionTokenOptions{
|
||
UserID: userID,
|
||
Info: info,
|
||
})
|
||
}
|
||
|
||
// InvalidateUser 撤銷用戶所有 Token 並斷開連線
|
||
// 這是最常用的「踢人」方法,適用於:
|
||
// - 用戶被封禁
|
||
// - 密碼變更
|
||
// - 用戶登出(全設備)
|
||
func (s *Service) InvalidateUser(ctx context.Context, userID string) error {
|
||
// 撤銷所有 Token
|
||
if s.blacklist != nil {
|
||
if err := s.blacklist.RevokeUserTokens(ctx, userID); err != nil {
|
||
return err
|
||
}
|
||
}
|
||
// 斷開連線
|
||
return s.client.Disconnect(ctx, userID)
|
||
}
|
||
|
||
// IsUserOnline 檢查用戶是否在線
|
||
func (s *Service) IsUserOnline(ctx context.Context, userID string) (bool, error) {
|
||
if s.online == nil {
|
||
return false, ErrOnlineStoreNotConfigured
|
||
}
|
||
return s.online.IsUserOnline(ctx, userID)
|
||
}
|
||
|
||
// GetUsersOnlineStatus 批量獲取用戶在線狀態
|
||
func (s *Service) GetUsersOnlineStatus(ctx context.Context, userIDs []string) (map[string]bool, error) {
|
||
if s.online == nil {
|
||
return nil, ErrOnlineStoreNotConfigured
|
||
}
|
||
return s.online.GetUsersOnlineStatus(ctx, userIDs)
|
||
}
|