133 lines
4.2 KiB
Go
133 lines
4.2 KiB
Go
|
|
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
|
|||
|
|
}
|