chat/internal/repository/matchmaking.go

133 lines
4.2 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 repository
import (
"chat/internal/domain/const"
redisKey "chat/internal/domain/redis"
domainRepo "chat/internal/domain/repository"
"chat/internal/utils"
"context"
"errors"
"fmt"
"time"
"github.com/redis/go-redis/v9"
)
type matchmakingRepository struct {
client *redis.Client
}
// NewMatchmakingRepository 創建新的配對 Repository
func NewMatchmakingRepository(client *redis.Client) domainRepo.MatchmakingRepository {
return &matchmakingRepository{
client: client,
}
}
// JoinQueue 加入配對佇列
func (r *matchmakingRepository) JoinQueue(ctx context.Context, uid string) (status string, roomID string, err error) {
// 檢查使用者是否已經在配對中或已配對
userKey := redisKey.MatchUserKey(uid)
currentStatus, err := r.client.Get(ctx, userKey).Result()
if err != nil && err != redis.Nil {
return "", "", fmt.Errorf("failed to check user status: %w", err)
}
// 如果已經配對返回房間ID
if currentStatus != "" && currentStatus != consts.StatusWaiting {
return consts.StatusMatched, currentStatus, nil
}
// 如果正在等待,返回等待狀態
if currentStatus == consts.StatusWaiting {
return consts.StatusWaiting, "", nil
}
// 嘗試從佇列中取出一個使用者進行配對
queueKey := redisKey.MatchQueueKey()
otherUID, err := r.client.LPop(ctx, queueKey).Result()
if errors.Is(err, redis.Nil) {
// 佇列為空,將自己加入佇列
if err := r.client.RPush(ctx, queueKey, uid).Err(); err != nil {
return "", "", fmt.Errorf("failed to join queue: %w", err)
}
// 設置等待狀態TTL 5 分鐘
if err := r.client.Set(ctx, userKey, consts.StatusWaiting, 5*time.Minute).Err(); err != nil {
return "", "", fmt.Errorf("failed to set waiting status: %w", err)
}
return consts.StatusWaiting, "", nil
}
if err != nil {
return "", "", fmt.Errorf("failed to pop from queue: %w", err)
}
// 配對成功生成房間ID
roomID = utils.GenerateRoomID()
// 設置雙方的房間IDTTL 1 小時
roomTTL := 1 * time.Hour
if err := r.client.Set(ctx, userKey, roomID, roomTTL).Err(); err != nil {
return "", "", fmt.Errorf("failed to set room for user: %w", err)
}
otherUserKey := redisKey.MatchUserKey(otherUID)
if err := r.client.Set(ctx, otherUserKey, roomID, roomTTL).Err(); err != nil {
return "", "", fmt.Errorf("failed to set room for other user: %w", err)
}
// 建立房間成員 Set
membersKey := redisKey.RoomMembersKey(roomID)
if err := r.client.SAdd(ctx, membersKey, uid, otherUID).Err(); err != nil {
return "", "", fmt.Errorf("failed to create room members: %w", err)
}
if err := r.client.Expire(ctx, membersKey, roomTTL).Err(); err != nil {
return "", "", fmt.Errorf("failed to set room TTL: %w", err)
}
return consts.StatusMatched, roomID, nil
}
// GetMatchStatus 查詢使用者的配對狀態
func (r *matchmakingRepository) GetMatchStatus(ctx context.Context, uid string) (status string, roomID string, err error) {
userKey := redisKey.MatchUserKey(uid)
value, err := r.client.Get(ctx, userKey).Result()
if err == redis.Nil {
// 沒有配對記錄
return "", "", nil
}
if err != nil {
return "", "", fmt.Errorf("failed to get match status: %w", err)
}
if value == consts.StatusWaiting {
return consts.StatusWaiting, "", nil
}
// value 是 roomID
return consts.StatusMatched, value, nil
}
// CreateRoom 建立房間並添加成員
func (r *matchmakingRepository) CreateRoom(ctx context.Context, roomID string, members []string) error {
membersKey := redisKey.RoomMembersKey(roomID)
if err := r.client.SAdd(ctx, membersKey, members).Err(); err != nil {
return fmt.Errorf("failed to add room members: %w", err)
}
// 設置 TTL 1 小時
if err := r.client.Expire(ctx, membersKey, 1*time.Hour).Err(); err != nil {
return fmt.Errorf("failed to set room TTL: %w", err)
}
return nil
}
// IsRoomMember 檢查使用者是否為房間成員
func (r *matchmakingRepository) IsRoomMember(ctx context.Context, roomID string, uid string) (bool, error) {
membersKey := redisKey.RoomMembersKey(roomID)
isMember, err := r.client.SIsMember(ctx, membersKey, uid).Result()
if err != nil {
return false, fmt.Errorf("failed to check room membership: %w", err)
}
return isMember, nil
}