Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import java.util.List;

import com.nowait.domaincorerdb.order.entity.OrderItem;
import com.nowait.domaincorerdb.order.entity.UserOrder;
import com.nowait.domaincorerdb.order.entity.OrderStatus;
import com.nowait.domaincorerdb.order.entity.UserOrder;

import lombok.AllArgsConstructor;
import lombok.Builder;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.nowait.applicationuser.reservation.controller;

import java.util.List;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
Expand All @@ -11,6 +13,7 @@
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.nowait.applicationuser.reservation.dto.MyWaitingQueueDto;
import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto;
import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto;
import com.nowait.applicationuser.reservation.dto.WaitingResponseDto;
Expand Down Expand Up @@ -67,7 +70,7 @@ public ResponseEntity<?> createQueue(
}

@GetMapping("/get/queue/redis/{storeId}")
@Operation(summary = "본인 대기열 조회", description = "특정 주점에 대한 본인 대기열 조회")
@Operation(summary = "특정 주점의 본인 대기열 조회", description = "특정 주점에 대한 본인 대기열 조회")
@ApiResponse(responseCode = "200", description = "본인 대기열 조회")
public ResponseEntity<?> getQueue(
@PathVariable Long storeId,
Expand Down Expand Up @@ -98,4 +101,13 @@ public ResponseEntity<?> deleteQueue(
)
);
}

@GetMapping("/my/waitings")
@Operation(summary = "내 모든 대기열 리스트 확인", description = "내가 신청한 모든 대기열 리스트 확인")
@ApiResponse(responseCode = "200", description = "대기열 리스트 조회")
public ResponseEntity<?> getAllMyWaitings(@AuthenticationPrincipal CustomOAuth2User customOAuth2User) {
List<MyWaitingQueueDto> response = reservationService.getAllMyWaitings(customOAuth2User);
return ResponseEntity.ok(ApiUtils.success(response));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.nowait.applicationuser.reservation.dto;

import java.time.LocalDateTime;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Schema(description = "내 대기 큐 정보 DTO")
public class MyWaitingQueueDto {
@Schema(description = "주점 ID", example = "1")
private Long storeId;
@Schema(description = "주점 이름", example = "비어파티")
private String storeName;
@Schema(description = "학과 이름", example = "경영학과")
private String departmentName;
@Schema(description = "대기 순번", example = "1")
private Integer rank;
@Schema(description = "내 앞의 대기 팀 수", example = "3")
private Integer teamsAhead;
@Schema(description = "대기 인원(파티 사이즈)", example = "4")
private Integer partySize; // 파티 인원
@Schema(description = "대기 상태", example = "WAITING")
private String status;
@Schema(description = "대기 등록 일시", example = "2024-07-20T18:00:00")
private LocalDateTime registeredAt;
@Schema(description = "주점 위치", example = "학생회관 1층 104호")
private String location;
@Schema(description = "프로필 이미지 URL", example = "https://cdn.gtable.com/profile/user1.jpg")
private String profileImageUrl;
}

Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
package com.nowait.applicationuser.reservation.repository;

import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;

Expand All @@ -20,6 +25,9 @@ public boolean addToWaitingQueue(Long storeId, String userId, Integer partySize,
Boolean added = redisTemplate.opsForZSet().addIfAbsent(queueKey, userId, timestamp);
if (Boolean.TRUE.equals(added)) {
redisTemplate.opsForHash().put(partyKey, userId, partySize.toString());
// TTL 12시간(43200초) 설정
redisTemplate.expire(queueKey, Duration.ofHours(12));
redisTemplate.expire(partyKey, Duration.ofHours(12));
}
return Boolean.TRUE.equals(added);
}
Expand Down Expand Up @@ -47,6 +55,36 @@ public boolean removeWaiting(Long storeId, String userId) {
redisTemplate.opsForHash().delete(partyKey, userId);
return true;
}

public Long getWaitingTimestamp(Long storeId, String userId) {
String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId;
Double score = redisTemplate.opsForZSet().score(key, userId);
return score == null ? null : score.longValue();
}

// 사용자 대기중인 매장 목록
public List<Long> getUserWaitingStoreIds(String userId) {
// key pattern으로 모든 매장 대기열 조회 (keys: waiting:*)
Set<String> keys = redisTemplate.keys(RedisKeyUtils.buildWaitingKeyPrefix() + "*");
if (keys == null) return List.of();

List<Long> result = new ArrayList<>();
for (String key : keys) {
// ZSet만 필터링
String type = redisTemplate.type(key).code();
if (!"zset".equals(type)) {
continue; // hash 등은 스킵!
}
// waiting:{storeId}만 추출 (waiting:party:7 등은 통과 안 됨)
Long storeId = Long.valueOf(key.substring(key.lastIndexOf(":") + 1));
if (redisTemplate.opsForZSet().rank(key, userId) != null) {
result.add(storeId);
}
}
return result;
}


}


Original file line number Diff line number Diff line change
@@ -1,17 +1,27 @@
package com.nowait.applicationuser.reservation.service;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.nowait.applicationuser.reservation.dto.MyWaitingQueueDto;
import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto;
import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto;
import com.nowait.applicationuser.reservation.dto.WaitingResponseDto;
import com.nowait.applicationuser.reservation.repository.WaitingRedisRepository;
import com.nowait.common.enums.ReservationStatus;
import com.nowait.common.enums.Role;
import com.nowait.domaincorerdb.department.entity.Department;
import com.nowait.domaincorerdb.department.repository.DepartmentRepository;
import com.nowait.domaincorerdb.reservation.entity.Reservation;
import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException;
import com.nowait.domaincorerdb.reservation.repository.ReservationRepository;
Expand All @@ -34,6 +44,7 @@ public class ReservationService {
private final StoreRepository storeRepository;
private final UserRepository userRepository;
private final WaitingRedisRepository waitingRedisRepository;
private final DepartmentRepository departmentRepository;

public WaitingResponseDto registerWaiting(
Long storeId,CustomOAuth2User customOAuth2User,ReservationCreateRequestDto requestDto
Expand Down Expand Up @@ -92,6 +103,53 @@ public boolean cancelWaiting(Long storeId, CustomOAuth2User customOAuth2User) {
}
return removed;
}
//TODO 성능 개선 필요
public List<MyWaitingQueueDto> getAllMyWaitings(CustomOAuth2User customOAuth2User) {
String userId = customOAuth2User.getUserId().toString();
List<Long> userWaitingStoreIds = waitingRedisRepository.getUserWaitingStoreIds(userId);

List<MyWaitingQueueDto> result = new ArrayList<>();
if (!userWaitingStoreIds.isEmpty()) {
// Store, Department 배치 조회
List<Store> stores = storeRepository.findAllById(userWaitingStoreIds);
Map<Long, Store> storeMap = stores.stream()
.collect(Collectors.toMap(Store::getStoreId, Function.identity()));

Set<Long> departmentIds = stores.stream()
.map(Store::getDepartmentId)
.collect(Collectors.toSet());
Map<Long, String> departmentNameMap = departmentRepository.findAllById(departmentIds).stream()
.collect(Collectors.toMap(Department::getId, Department::getName));

for (Long storeId : userWaitingStoreIds) {
Store store = storeMap.get(storeId);
if (store == null) continue;

Long rank = waitingRedisRepository.getRank(storeId, userId);
Integer partySize = waitingRedisRepository.getPartySize(storeId, userId);
Long timestamp = waitingRedisRepository.getWaitingTimestamp(storeId, userId);

LocalDateTime registeredAt = timestamp != null
? LocalDateTime.ofInstant(Instant.ofEpochMilli(timestamp), ZoneId.of("Asia/Seoul"))
: null;

result.add(MyWaitingQueueDto.builder()
.storeId(storeId)
.storeName(store.getName())
.departmentName(departmentNameMap.get(store.getDepartmentId()))
.rank(rank != null ? rank.intValue() + 1 : 0)
.teamsAhead(rank != null ? rank.intValue() : 0)
.partySize(partySize != null ? partySize : 0)
.status("WAITING") // 필요시 redis에 상태값이 있으면 조회해서 세팅
.registeredAt(registeredAt)
.location(store.getLocation())
.profileImageUrl(customOAuth2User.getUser().getProfileImage())
.build());
}
}
return result;
}


@Transactional
public ReservationCreateResponseDto create(Long storeId, CustomOAuth2User customOAuth2User,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.nowait.domaincorerdb.department.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface DepartmentRepository extends JpaRepository<com.nowait.domaincorerdb.department.entity.Department, Long> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import com.nowait.domaincorerdb.store.entity.Store;
Expand All @@ -21,4 +22,8 @@ public interface StoreRepository extends JpaRepository<Store, Long> {
List<Store> findByNameContainingIgnoreCaseAndDeletedFalse(String name);

Slice<Store> findAllByDeletedFalseOrderByStoreIdAsc(Pageable pageable);

// TODO queryDSL으로 전환?
@Query("select s.storeId from Store s where s.isActive = true and s.deleted = false")
List<Long> findAllActiveStoreIds();
}