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)
|
|||
|
|
}
|