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() // 設置雙方的房間ID,TTL 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 }