diff --git a/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepository.java b/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepository.java index 55ccd53..f656e0f 100644 --- a/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepository.java +++ b/src/main/java/org/atdev/artrip/domain/exhibit/repository/ExhibitRepository.java @@ -61,4 +61,8 @@ WHERE status IN ('ONGOING', 'ENDING_SOON') long countByExhibitHall_ExhibitHallId(Long exhibitHallId); Optional findByTitleAndStartDate(String title, LocalDate startDate); + + // 패치조인 전시홀 전시관 + @Query("select e from Exhibit e join fetch e.exhibitHall where e.exhibitId in :ids") + List findAllByIdWithHall(@Param("ids") List ids); } diff --git a/src/main/java/org/atdev/artrip/domain/exhibit/service/ExhibitService.java b/src/main/java/org/atdev/artrip/domain/exhibit/service/ExhibitService.java index 5e03218..179f054 100644 --- a/src/main/java/org/atdev/artrip/domain/exhibit/service/ExhibitService.java +++ b/src/main/java/org/atdev/artrip/domain/exhibit/service/ExhibitService.java @@ -6,11 +6,13 @@ import org.atdev.artrip.domain.exhibit.repository.ExhibitRepository; import org.atdev.artrip.domain.favortie.repository.FavoriteExhibitRepository; import org.atdev.artrip.domain.home.converter.HomeConverter; +import org.atdev.artrip.domain.user.service.UserService; import org.atdev.artrip.global.apipayload.code.status.ExhibitError; import org.atdev.artrip.global.apipayload.exception.GeneralException; import org.atdev.artrip.global.s3.service.S3Service; import org.atdev.artrip.global.s3.web.dto.request.ImageResizeRequest; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -20,7 +22,9 @@ public class ExhibitService { private final HomeConverter homeConverter; private final S3Service s3Service; private final FavoriteExhibitRepository favoriteExhibitRepository; + private final UserService userService; + @Transactional public ExhibitDetailResponse getExhibitDetail(Long exhibitId, Long userId, ImageResizeRequest resize) { Exhibit exhibit = exhibitRepository.findById(exhibitId) @@ -33,6 +37,8 @@ public ExhibitDetailResponse getExhibitDetail(Long exhibitId, Long userId, Image isFavorite = favoriteExhibitRepository.existsActive(userId, exhibitId); } + userService.addRecentView(userId,exhibitId); + return homeConverter.toHomeExhibitResponse(exhibit, isFavorite, resizedPosterUrl); } diff --git a/src/main/java/org/atdev/artrip/domain/exhibit/web/dto/response/ExhibitRecentResponse.java b/src/main/java/org/atdev/artrip/domain/exhibit/web/dto/response/ExhibitRecentResponse.java new file mode 100644 index 0000000..473aa5c --- /dev/null +++ b/src/main/java/org/atdev/artrip/domain/exhibit/web/dto/response/ExhibitRecentResponse.java @@ -0,0 +1,15 @@ +package org.atdev.artrip.domain.exhibit.web.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Builder +@Getter +@AllArgsConstructor +public class ExhibitRecentResponse { + + private Long exhibitId; + private String title; + private String exhibitHallName; +} diff --git a/src/main/java/org/atdev/artrip/domain/home/converter/HomeConverter.java b/src/main/java/org/atdev/artrip/domain/home/converter/HomeConverter.java index 086c19d..2107e58 100644 --- a/src/main/java/org/atdev/artrip/domain/home/converter/HomeConverter.java +++ b/src/main/java/org/atdev/artrip/domain/home/converter/HomeConverter.java @@ -4,6 +4,7 @@ import org.atdev.artrip.domain.Enum.KeywordType; import org.atdev.artrip.domain.exhibit.data.Exhibit; import org.atdev.artrip.domain.exhibit.reponse.ExhibitDetailResponse; +import org.atdev.artrip.domain.exhibit.web.dto.response.ExhibitRecentResponse; import org.atdev.artrip.domain.home.response.FilterResponse; import org.atdev.artrip.domain.home.response.HomeListResponse; import org.atdev.artrip.domain.home.web.dto.request.*; @@ -149,5 +150,14 @@ public List toResponseList() { .collect(Collectors.toList()); } + public ExhibitRecentResponse toExhibitRecentResponse(Exhibit exhibit){ + + return ExhibitRecentResponse.builder() + .exhibitId(exhibit.getExhibitId()) + .exhibitHallName(exhibit.getExhibitHall().getName()) + .title(exhibit.getTitle()) + .build(); + } + } diff --git a/src/main/java/org/atdev/artrip/domain/user/service/UserService.java b/src/main/java/org/atdev/artrip/domain/user/service/UserService.java index 7801be8..4d807a9 100644 --- a/src/main/java/org/atdev/artrip/domain/user/service/UserService.java +++ b/src/main/java/org/atdev/artrip/domain/user/service/UserService.java @@ -4,6 +4,10 @@ import lombok.RequiredArgsConstructor; import org.atdev.artrip.domain.auth.data.User; import org.atdev.artrip.domain.auth.repository.UserRepository; +import org.atdev.artrip.domain.exhibit.data.Exhibit; +import org.atdev.artrip.domain.exhibit.repository.ExhibitRepository; +import org.atdev.artrip.domain.exhibit.web.dto.response.ExhibitRecentResponse; +import org.atdev.artrip.domain.home.converter.HomeConverter; import org.atdev.artrip.domain.keyword.data.Keyword; import org.atdev.artrip.domain.keyword.data.UserKeyword; import org.atdev.artrip.domain.keyword.repository.KeywordRepository; @@ -16,12 +20,18 @@ import org.atdev.artrip.global.apipayload.exception.GeneralException; import org.atdev.artrip.global.s3.service.S3Service; import org.atdev.artrip.global.s3.web.dto.request.ImageResizeRequest; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import java.time.Duration; import java.time.LocalDateTime; +import java.util.Comparator; import java.util.List; +import java.util.Optional; +import java.util.Set; @Service @@ -32,6 +42,14 @@ public class UserService { private final S3Service s3Service; private static final String NICKNAME_REGEX = "^[a-zA-Z0-9가-힣]+$"; + @Qualifier("recommendRedisTemplate") + private final StringRedisTemplate recommendRedisTemplate; + + private final ExhibitRepository exhibitRepository; + private final HomeConverter homeConverter; + + private static final String KEY_PREFIX = "recent:view:user:"; + @Transactional public NicknameResponse updateNickName(Long userId, NicknameRequest dto){ @@ -136,5 +154,35 @@ public MypageResponse getMypage(Long userId, ImageResizeRequest resize){ return new MypageResponse(user.getNickName(), profileImage, user.getEmail()); } + public void addRecentView(Long userId, Long exhibitId) { + String key = KEY_PREFIX + userId; + double now = System.currentTimeMillis(); + + recommendRedisTemplate.opsForZSet().add(key, String.valueOf(exhibitId), now); // key , exhibitid, time + recommendRedisTemplate.opsForZSet().removeRange(key, 0, -21);// 뒤에서 21부터 전부 삭제 + recommendRedisTemplate.expire(key, Duration.ofDays(30));// 1달 기한 + } + + // 최근 본 전시 리스트 조회 + public List getRecentViews(Long userId) { + + String key = KEY_PREFIX + userId; + Set result = recommendRedisTemplate.opsForZSet().reverseRange(key, 0, 19);//시간 역순으로 가져옴 + + if (result == null || result.isEmpty()) + return List.of(); + + List ids= result.stream() + .map(Long::valueOf) + .toList(); + + List exhibits = exhibitRepository.findAllByIdWithHall(ids); + + exhibits.sort(Comparator.comparingInt(exhibit -> ids.indexOf(exhibit.getExhibitId()))); + + return exhibits.stream() + .map(exhibit -> homeConverter.toExhibitRecentResponse(exhibit)) + .toList(); + } } diff --git a/src/main/java/org/atdev/artrip/domain/user/web/controller/UserController.java b/src/main/java/org/atdev/artrip/domain/user/web/controller/UserController.java index 9288b0f..5e16a96 100644 --- a/src/main/java/org/atdev/artrip/domain/user/web/controller/UserController.java +++ b/src/main/java/org/atdev/artrip/domain/user/web/controller/UserController.java @@ -2,12 +2,14 @@ import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; +import org.atdev.artrip.domain.exhibit.web.dto.response.ExhibitRecentResponse; import org.atdev.artrip.domain.user.service.UserService; import org.atdev.artrip.domain.user.web.dto.request.NicknameRequest; import org.atdev.artrip.domain.user.web.dto.response.MypageResponse; import org.atdev.artrip.domain.user.web.dto.response.NicknameResponse; import org.atdev.artrip.global.apipayload.CommonResponse; import org.atdev.artrip.global.apipayload.code.status.CommonError; +import org.atdev.artrip.global.apipayload.code.status.ExhibitError; import org.atdev.artrip.global.apipayload.code.status.UserError; import org.atdev.artrip.global.s3.web.dto.request.ImageResizeRequest; import org.atdev.artrip.global.swagger.ApiErrorResponses; @@ -19,6 +21,8 @@ import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; +import java.util.List; + @RestController @RequiredArgsConstructor @RequestMapping("/my") @@ -78,10 +82,10 @@ public ResponseEntity> updateNickname( @Operation(summary = "마이페이지 조회", description = "닉네임, 프로필 이미지 조회") @GetMapping("/mypage") - @ApiErrorResponses( - common = {CommonError._INTERNAL_SERVER_ERROR, CommonError._UNAUTHORIZED}, - user = {UserError._USER_NOT_FOUND} - ) +// @ApiErrorResponses( +// common = {CommonError._INTERNAL_SERVER_ERROR, CommonError._UNAUTHORIZED}, +// user = {UserError._USER_NOT_FOUND} +// ) public ResponseEntity> getMypage( @AuthenticationPrincipal UserDetails user, @ParameterObject ImageResizeRequest resize) { @@ -93,4 +97,21 @@ public ResponseEntity> getMypage( return ResponseEntity.ok(CommonResponse.onSuccess(response)); } + @Operation(summary = "최근 본 전시", description = "최근 본 전시 20개") + @GetMapping("/recent") + @ApiErrorResponses( + common = {CommonError._INTERNAL_SERVER_ERROR, CommonError._UNAUTHORIZED}, + user = {UserError._USER_NOT_FOUND}, + exhibit = {ExhibitError._EXHIBIT_NOT_FOUND} + ) + public ResponseEntity>> getRecentExhibit( + @AuthenticationPrincipal UserDetails userDetails){ + + Long userId = Long.valueOf(userDetails.getUsername()); + + List responses = userService.getRecentViews(userId); + + return ResponseEntity.ok(CommonResponse.onSuccess(responses)); + } + }