diff --git a/src/main/java/com/example/sharemind/counselor/application/CounselorServiceImpl.java b/src/main/java/com/example/sharemind/counselor/application/CounselorServiceImpl.java index f14e0028..159fdb3a 100644 --- a/src/main/java/com/example/sharemind/counselor/application/CounselorServiceImpl.java +++ b/src/main/java/com/example/sharemind/counselor/application/CounselorServiceImpl.java @@ -1,5 +1,7 @@ package com.example.sharemind.counselor.application; +import static com.example.sharemind.global.constants.Constants.REALTIME_COUNSELOR; + import com.example.sharemind.chat.domain.Chat; import com.example.sharemind.counselor.content.Bank; import com.example.sharemind.counselor.content.ConsultStyle; @@ -23,11 +25,15 @@ import com.example.sharemind.wishList.application.WishListCounselorService; import com.example.sharemind.wishList.domain.WishList; import com.example.sharemind.wishList.dto.request.WishListGetRequest; +import java.time.LocalDate; +import java.time.LocalTime; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; import org.springframework.data.domain.Page; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -40,10 +46,12 @@ public class CounselorServiceImpl implements CounselorService { private static final int COUNSELOR_PAGE = 4; + private static final String SPLIT_HOURS = "~"; private final CounselorRepository counselorRepository; private final CustomerService customerService; private final WishListCounselorService wishListCounselorService; + private final RedisTemplate> redisTemplate; @Override public Counselor getCounselorByCounselorId(Long counselorId) { @@ -107,7 +115,7 @@ public Boolean getRetryPermission(Long customerId) { @Transactional @Override public void updateCounselorProfile(CounselorUpdateProfileRequest counselorUpdateProfileRequest, - Long customerId) { + Long customerId) { Counselor counselor = getCounselorByCustomerId(customerId); if ((counselor.getIsEducated() == null) || (!counselor.getIsEducated())) { throw new CounselorException(CounselorErrorCode.COUNSELOR_NOT_EDUCATED); @@ -198,7 +206,7 @@ private List getCounselorByCategoryWithPagination( Pageable pageable = PageRequest.of(counselorGetRequest.getIndex(), COUNSELOR_PAGE, Sort.by(sortColumn).descending()); if (counselorGetRequest.getConsultCategory() == null) { - return counselorRepository.findByLevelAndStatus(pageable).getContent(); + return getRealtimeCounselors(counselorGetRequest.getIndex()); } ConsultCategory consultCategory = ConsultCategory.getConsultCategoryByName( @@ -207,6 +215,16 @@ private List getCounselorByCategoryWithPagination( .getContent(); } + private List getRealtimeCounselors(int index) { + int start = index * COUNSELOR_PAGE; + List counselorIds = redisTemplate.opsForValue().get(REALTIME_COUNSELOR); + + List counselorsSubList = + counselorIds != null && counselorIds.size() >= COUNSELOR_PAGE ? counselorIds.subList(start, start + COUNSELOR_PAGE) + : counselorIds; + return counselorRepository.findAllById(counselorsSubList); + } + @Override public List getCounselorByWordWithPagination( SearchWordCounselorFindRequest searchWordCounselorFindRequest, @@ -223,34 +241,47 @@ public List getCounselorByWordWithPagination( @Override public List getCounselorsByCategoryAndCustomer(Long customerId, - String sortType, CounselorGetRequest counselorGetRequest) { + String sortType, + CounselorGetRequest counselorGetRequest) { List counselors = getCounselorByCategoryWithPagination(counselorGetRequest, sortType); - + List counselorIds = redisTemplate.opsForValue().get(REALTIME_COUNSELOR); Customer customer = customerService.getCustomerByCustomerId(customerId); Set wishListCounselorIds = wishListCounselorService.getWishListCounselorIdsByCustomer( customer); - + if (counselorIds == null) { + return counselors.stream() + .map(counselor -> CounselorGetListResponse.of(counselor, + wishListCounselorIds.contains(counselor.getCounselorId()), false)) + .toList(); + } return counselors.stream() .map(counselor -> CounselorGetListResponse.of(counselor, - wishListCounselorIds.contains(counselor.getCounselorId()))) + wishListCounselorIds.contains(counselor.getCounselorId()), + counselorIds.contains(counselor.getCounselorId()))) .toList(); } @Override public List getAllCounselorsByCategory(String sortType, - CounselorGetRequest counselorGetRequest) { + CounselorGetRequest counselorGetRequest) { List counselors = getCounselorByCategoryWithPagination(counselorGetRequest, sortType); - + List counselorIds = redisTemplate.opsForValue().get(REALTIME_COUNSELOR); + if (counselorIds == null) { + return counselors.stream() + .map(counselor -> CounselorGetListResponse.of(counselor, false, false)) + .toList(); + } return counselors.stream() - .map(counselor -> CounselorGetListResponse.of(counselor, false)) + .map(counselor -> CounselorGetListResponse.of(counselor, false, + counselorIds.contains(counselor.getCounselorId()))) .toList(); } @Override public CounselorGetMinderProfileResponse getCounselorMinderProfileByCustomer(Long counselorId, - Long customerId) { + Long customerId) { Customer customer = customerService.getCustomerByCustomerId(customerId); Counselor counselor = getCounselorByCounselorId(counselorId); @@ -268,7 +299,7 @@ public CounselorGetMinderProfileResponse getAllCounselorMinderProfile(Long couns @Transactional @Override public void updateAccount(CounselorUpdateAccountRequest counselorUpdateAccountRequest, - Long customerId) { + Long customerId) { Counselor counselor = getCounselorByCustomerId(customerId); Bank.existsByDisplayName(counselorUpdateAccountRequest.getBank()); counselor.updateAccountInfo(counselorUpdateAccountRequest.getAccount(), @@ -302,7 +333,7 @@ public CounselorGetInfoResponse getCounselorMyInfo(Long customerId) { @Override public CounselorGetForConsultResponse getCounselorForConsultCreation(Long counselorId, - String type) { + String type) { Counselor counselor = getCounselorByCounselorId(counselorId); ConsultType consultType = ConsultType.getConsultTypeByName(type); if (!counselor.getConsultTypes().contains(consultType)) { @@ -342,4 +373,38 @@ public void checkCounselorAndCustomerSame(Customer customer, Counselor counselor customer.getCustomerId().toString()); } } + + @Scheduled(cron = "0 0 * * * *", zone = "Asia/Seoul") + public void updateRealtimeCounselors() { + List counselors = counselorRepository.findAllByIsEducatedIsTrueAndIsActivatedIsTrue(); + String currentDay = LocalDate.now().getDayOfWeek().toString().substring(0, 3).toUpperCase(); + int currentHour = LocalTime.now().getHour(); + + List realtimeCounselors = counselors.stream() + .filter(counselor -> isAvailableAtRealTime(counselor.getConsultTimes(), currentDay, currentHour)) + .sorted((c1, c2) -> Long.compare(c2.getTotalConsult(), c1.getTotalConsult())) + .toList(); + + List counselorIds = realtimeCounselors.stream() + .map(Counselor::getCounselorId) + .toList(); + + redisTemplate.opsForValue().set(REALTIME_COUNSELOR, counselorIds); + } + + private boolean isAvailableAtRealTime(Set consultTimes, String day, int hour) { + for (ConsultTime consultTime : consultTimes) { + if (Objects.equals(consultTime.getDay().toString(), day)) { + for (String timeRange : consultTime.getTimes()) { + String[] parts = timeRange.split(SPLIT_HOURS); + int startHour = Integer.parseInt(parts[0]); + int endHour = Integer.parseInt(parts[1]); + if (hour >= startHour && hour < endHour) { + return true; + } + } + } + } + return false; + } } diff --git a/src/main/java/com/example/sharemind/counselor/dto/response/CounselorGetListResponse.java b/src/main/java/com/example/sharemind/counselor/dto/response/CounselorGetListResponse.java index 06cbd33e..f9a18778 100644 --- a/src/main/java/com/example/sharemind/counselor/dto/response/CounselorGetListResponse.java +++ b/src/main/java/com/example/sharemind/counselor/dto/response/CounselorGetListResponse.java @@ -23,16 +23,24 @@ public class CounselorGetListResponse extends CounselorGetBaseResponse { @Schema(description = "찜여부", example = "true") private final Boolean isWishList; - private CounselorGetListResponse(Counselor counselor, Boolean isWishList) { + @Schema(description = "현재 접속 여부", example = "true") + private final Boolean isRealtime; + + @Schema(description = "상담 완료 횟수") + private final Long totalConsult; + + private CounselorGetListResponse(Counselor counselor, Boolean isWishList, Boolean isRealtime) { super(counselor); this.counselorId = counselor.getCounselorId(); this.level = counselor.getLevel(); this.totalReview = counselor.getTotalReview(); this.ratingAverage = counselor.getRatingAverage(); this.isWishList = isWishList; + this.isRealtime = isRealtime; + this.totalConsult = counselor.getTotalConsult(); } - public static CounselorGetListResponse of(Counselor counselor, Boolean isWishList) { - return new CounselorGetListResponse(counselor, isWishList); + public static CounselorGetListResponse of(Counselor counselor, Boolean isWishList, Boolean isRealtime) { + return new CounselorGetListResponse(counselor, isWishList, isRealtime); } } diff --git a/src/main/java/com/example/sharemind/counselor/presentation/CounselorController.java b/src/main/java/com/example/sharemind/counselor/presentation/CounselorController.java index 8c7d405e..37a8d926 100644 --- a/src/main/java/com/example/sharemind/counselor/presentation/CounselorController.java +++ b/src/main/java/com/example/sharemind/counselor/presentation/CounselorController.java @@ -50,7 +50,7 @@ public class CounselorController { }) @PostMapping("/quiz") public ResponseEntity updateIsEducated(@RequestParam Boolean isEducated, - @AuthenticationPrincipal CustomUserDetails customUserDetails) { + @AuthenticationPrincipal CustomUserDetails customUserDetails) { counselorService.updateIsEducated(isEducated, customUserDetails.getCustomer().getCustomerId()); return ResponseEntity.ok().build(); @@ -201,7 +201,7 @@ public ResponseEntity getCounselorForConsultCrea - 들을 준비가 된 마인더들(상담사 전체 리스트)조회의 경우, RequestBody에서 consultCategory를 빼고 넘겨주시면 됩니다.""") @ApiResponses({ @ApiResponse(responseCode = "200", description = "조회 성공"), - @ApiResponse(responseCode = "404", description = "1. sortType이 잘못된 경우 2. RequestBody의 카테고리가 잘못도니 경우", + @ApiResponse(responseCode = "404", description = "1. sortType이 잘못된 경우 2. RequestBody의 카테고리가 잘못된 경우", content = @Content(mediaType = "application/json", schema = @Schema(implementation = CustomExceptionResponse.class)) ) diff --git a/src/main/java/com/example/sharemind/global/constants/Constants.java b/src/main/java/com/example/sharemind/global/constants/Constants.java index 14743c3e..4e901d7b 100644 --- a/src/main/java/com/example/sharemind/global/constants/Constants.java +++ b/src/main/java/com/example/sharemind/global/constants/Constants.java @@ -8,6 +8,8 @@ public class Constants { public static final String CUSTOMER_CHATTING_PREFIX = "customer chatting: "; // 현재 접속중인 방 public static final String COUNSELOR_CHATTING_PREFIX = "counselor chatting: "; + public static final String REALTIME_COUNSELOR = "realtimeCounselors"; + public static final Integer CUSTOMER_ONGOING_CONSULT = 1; public static final Integer COUNSELOR_ONGOING_CONSULT = 3; diff --git a/src/main/java/com/example/sharemind/searchWord/application/SearchWordServiceImpl.java b/src/main/java/com/example/sharemind/searchWord/application/SearchWordServiceImpl.java index 3d427dfa..7e523cc0 100644 --- a/src/main/java/com/example/sharemind/searchWord/application/SearchWordServiceImpl.java +++ b/src/main/java/com/example/sharemind/searchWord/application/SearchWordServiceImpl.java @@ -1,5 +1,7 @@ package com.example.sharemind.searchWord.application; +import static com.example.sharemind.global.constants.Constants.REALTIME_COUNSELOR; + import com.example.sharemind.counselor.application.CounselorService; import com.example.sharemind.counselor.domain.Counselor; import com.example.sharemind.counselor.dto.response.CounselorGetListResponse; @@ -40,6 +42,7 @@ public class SearchWordServiceImpl implements SearchWordService { private final PostScrapRepository postScrapRepository; private final RedisTemplate redisTemplate; + private final RedisTemplate> counselorRedisTemplate; @Transactional @Override @@ -50,13 +53,18 @@ public List storeSearchWordAndGetCounselorsByCustomer( List counselors = counselorService.getCounselorByWordWithPagination(searchWordCounselorFindRequest, sortType); - + List counselorIds = counselorRedisTemplate.opsForValue().get(REALTIME_COUNSELOR); Customer customer = customerService.getCustomerByCustomerId(customerId); Set wishListCounselorIds = wishListCounselorService.getWishListCounselorIdsByCustomer(customer); - + if (counselorIds == null) { + return counselors.stream() + .map(counselor -> CounselorGetListResponse.of(counselor, + wishListCounselorIds.contains(counselor.getCounselorId()), false)) + .toList(); + } return counselors.stream() .map(counselor -> CounselorGetListResponse.of(counselor, - wishListCounselorIds.contains(counselor.getCounselorId()))) + wishListCounselorIds.contains(counselor.getCounselorId()), counselorIds.contains(counselor.getCounselorId()))) .toList(); } @@ -68,9 +76,14 @@ public List storeAllSearchWordAndGetCounselors(String List counselors = counselorService.getCounselorByWordWithPagination(searchWordCounselorFindRequest, sortType); - + List counselorIds = counselorRedisTemplate.opsForValue().get(REALTIME_COUNSELOR); + if (counselorIds == null) { + return counselors.stream() + .map(counselor -> CounselorGetListResponse.of(counselor, false, false)) + .toList(); + } return counselors.stream() - .map(counselor -> CounselorGetListResponse.of(counselor, false)) + .map(counselor -> CounselorGetListResponse.of(counselor, false, counselorIds.contains(counselor.getCounselorId()))) .toList(); } @@ -79,7 +92,6 @@ public List storeAllSearchWordAndGetCounselors(String public List storeAllSearchWordAndGetPosts(String sortType, SearchWordPostFindRequest searchWordPostFindRequest) { storeSearchWordInDB(searchWordPostFindRequest.getWord()); - List posts = postService.getPostByWordWithPagination(searchWordPostFindRequest, sortType); return posts.stream()