chat/internal/repository/matchmaking.go

133 lines
4.2 KiB
Go
Raw Permalink Normal View History

2025-12-31 09:36:02 +00:00
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
}