diff --git a/src/main/java/avengers/lion/item/service/ItemService.java b/src/main/java/avengers/lion/item/service/ItemService.java index 9b4847c..cc9636f 100644 --- a/src/main/java/avengers/lion/item/service/ItemService.java +++ b/src/main/java/avengers/lion/item/service/ItemService.java @@ -46,12 +46,13 @@ public void exchangeItem (Long memberId, Long itemId, ExchangeItemRequest exchan .orElseThrow(()-> new BusinessException(ExceptionType.ITEM_NOT_FOUND)); Member member = memberRepository.findById(memberId) .orElseThrow(()-> new BusinessException(ExceptionType.MEMBER_NOT_FOUND)); + // 총 비용 = 아이템 가격 * 개수 int price = Math.multiplyExact(item.getPrice(), count); - // 구매하고자 하는 수량보다 재고가 적다 -> 에러 발생 + // 구매하고자 하는 수량보다 재고가 적을 때 if(item.getStockCount()> getMyProfile(@AuthenticationPrincipal Long memberId) { return ResponseEntity.ok(ResponseUtil.createSuccessResponse(memberService.getMyProfile(memberId))); } + + /* + 마이페이지 프로필 업데이트 + */ @PutMapping("/my-profile") @PreAuthorize("hasAuthority('ROLE_USER')") public ResponseEntity> updateMyProfile(@AuthenticationPrincipal Long memberId, @RequestBody UpdateNicknameRequest updateNicknameRequest) { diff --git a/src/main/java/avengers/lion/member/repository/MemberRepository.java b/src/main/java/avengers/lion/member/repository/MemberRepository.java index ad29d33..12686ae 100644 --- a/src/main/java/avengers/lion/member/repository/MemberRepository.java +++ b/src/main/java/avengers/lion/member/repository/MemberRepository.java @@ -9,7 +9,6 @@ @Repository public interface MemberRepository extends JpaRepository { Optional findByEmail(String email); - boolean existsByEmail(String email); boolean existsByNicknameAndIdNot(String nickname, Long id); } diff --git a/src/main/java/avengers/lion/member/service/MemberService.java b/src/main/java/avengers/lion/member/service/MemberService.java index 5bc6ac3..2ba344f 100644 --- a/src/main/java/avengers/lion/member/service/MemberService.java +++ b/src/main/java/avengers/lion/member/service/MemberService.java @@ -34,9 +34,14 @@ public Long getMyPoint(Long memberId){ return member.getPoint(); } + + /* + 프로필 업데이트 + */ @Transactional public void updateNickname(Long memberId, UpdateNicknameRequest request){ String nickname = request.nickname(); + // 닉네임이 이미 존재하는지 조회 if(memberRepository.existsByNicknameAndIdNot(nickname, memberId)) throw new BusinessException(ExceptionType.NICKNAME_ALREADY_EXISTS); Member member = memberRepository.findById(memberId) diff --git a/src/main/java/avengers/lion/mission/controller/VerificationController.java b/src/main/java/avengers/lion/mission/controller/VerificationController.java index 62b0ddc..411825d 100644 --- a/src/main/java/avengers/lion/mission/controller/VerificationController.java +++ b/src/main/java/avengers/lion/mission/controller/VerificationController.java @@ -62,7 +62,6 @@ public ResponseEntity> startVerification( @PathVariable Long missionId, @Valid @RequestBody VerifyImageRequest request, @AuthenticationPrincipal Long memberId) { - VerifyImageResponse response = missionVerifyService.startVerification(missionId, request, memberId); return ResponseEntity.ok(ResponseUtil.createSuccessResponse(response)); } diff --git a/src/main/java/avengers/lion/review/repository/ReviewQueryRepositoryImpl.java b/src/main/java/avengers/lion/review/repository/ReviewQueryRepositoryImpl.java index f946eaf..dfaabf2 100644 --- a/src/main/java/avengers/lion/review/repository/ReviewQueryRepositoryImpl.java +++ b/src/main/java/avengers/lion/review/repository/ReviewQueryRepositoryImpl.java @@ -29,7 +29,7 @@ public List findAllReviewByMissionId(Long missionId, Long cursorId, int BooleanBuilder where = new BooleanBuilder() .and(r.completedMission.mission.id.eq(missionId)); if (cursorId != null) { - where.and(sortType.equals(SortType.ASC) ? r.id.lt(cursorId) : r.id.gt(cursorId)); + where.and(sortType.equals(SortType.ASC) ? r.id.gt(cursorId) : r.id.lt(cursorId)); } OrderSpecifier orderSpecifier = sortType.equals(SortType.ASC) ? r.id.asc() : r.id.desc(); diff --git a/src/main/java/avengers/lion/review/service/ReviewService.java b/src/main/java/avengers/lion/review/service/ReviewService.java index 0375857..41e1e5e 100644 --- a/src/main/java/avengers/lion/review/service/ReviewService.java +++ b/src/main/java/avengers/lion/review/service/ReviewService.java @@ -103,24 +103,6 @@ public PageResult getWrittenPage(Long memberId, return new PageResult<>(data, hasNext, null, nextId); } - /** 작성한 리뷰 상세조회 (ASC/DESC) */ - public PageResult getWrittenDetailPage(Long memberId, - Long cursorId, - int limit, - SortType sort) { - - List rows = reviewRepository.findWrittenPage(memberId, cursorId, limit+1, sort); - - boolean hasNext = rows.size() > limit; - if (hasNext) rows = rows.subList(0, limit); - - List data = WrittenReviewResponse.of(rows); - Long nextId = null; - if (!rows.isEmpty()) { Review last = rows.getLast(); nextId = last.getId(); } - - return new PageResult<>(data, hasNext, null, nextId); - } - /** 작성 가능한 리뷰 작성 */ @Transactional public void writeUnWrittenReview(Long memberId, WriteReviewRequest req) { @@ -139,9 +121,10 @@ public void writeUnWrittenReview(Long memberId, WriteReviewRequest req) { .member(cm.getMember()) .completedMission(cm) .build(); - member.addPointByReview(); walletService.addPointTransactionForReview(member); reviewRepository.save(review); cm.updateReviewStatus(ReviewStatus.ACTIVE); } + + } diff --git a/src/main/java/avengers/lion/wallet/controller/WalletController.java b/src/main/java/avengers/lion/wallet/controller/WalletController.java index 6b04d3e..dd83e39 100644 --- a/src/main/java/avengers/lion/wallet/controller/WalletController.java +++ b/src/main/java/avengers/lion/wallet/controller/WalletController.java @@ -22,7 +22,6 @@ public class WalletController implements WalletApi { private final WalletService walletService; - /* 내 포인트 조회 */ @@ -32,6 +31,9 @@ public ResponseEntity> getMyPoint(@AuthenticationP return ResponseEntity.ok(ResponseUtil.createSuccessResponse(walletService.getMyPoint(memberId))); } + /* + 내 지갑 조회 + */ @GetMapping("/my-wallet") @PreAuthorize("hasAuthority('ROLE_USER')") public ResponseEntity> getMyWallet(@AuthenticationPrincipal Long memberId){ diff --git a/src/main/java/avengers/lion/wallet/service/WalletService.java b/src/main/java/avengers/lion/wallet/service/WalletService.java index a6c5993..c34dc69 100644 --- a/src/main/java/avengers/lion/wallet/service/WalletService.java +++ b/src/main/java/avengers/lion/wallet/service/WalletService.java @@ -18,12 +18,15 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.function.Function; @Service @RequiredArgsConstructor public class WalletService { + private static final int REVIEW_REWARD_POINTS = 100; + private final PointTransactionRepository pointTransactionRepository; private final OrderService orderService; private final MemberService memberService; @@ -42,45 +45,27 @@ public MyWalletResponse getMyWallet(Long memberId){ Long consumedItemCount = orderService.myConsumedItemCount(memberId); List giftCards = getMyGiftCards(memberId); List partnerCards = getMyPartnerCards(memberId); - List usedItems = getMyUsedItems(memberId); + List usedItems = getMyConsumedItems(memberId); return new MyWalletResponse(giftCardCount, partnerCardCount, consumedItemCount, giftCards, partnerCards, usedItems); } - /* - 내 상품권 조회 - */ public List getMyGiftCards(Long memberId){ List orders = orderService.getGiftCards(memberId); - return orders.stream() - .flatMap(order -> order.getOrderItems().stream()) - .map(GiftCardResponse::of) - .toList(); + return convertOrdersToResponse(orders, GiftCardResponse::of); } - /* - 내 제휴상품 조회 - */ + public List getMyPartnerCards(Long memberId){ List orders = orderService.getPartnerCards(memberId); - return orders.stream() - .flatMap(order -> order.getOrderItems().stream()) - .map(ParentItemResponse::of) - .toList(); + return convertOrdersToResponse(orders, ParentItemResponse::of); } - /* - 사용한 상품 조회 - */ - public List getMyUsedItems(Long memberId){ + + public List getMyConsumedItems(Long memberId){ List orders = orderService.getConsumedItem(memberId); - return orders.stream() - .flatMap(order -> order.getOrderItems().stream()) - .map(ConsumedItemResponse::of) - .toList(); + return convertOrdersToResponse(orders, ConsumedItemResponse::of); } - /* - 리워드 내역 조회 - */ + public List getRewardHistory(Long memberId){ List pointTransactions = pointTransactionRepository.findAllByMemberId(memberId); return pointTransactions.stream() @@ -88,10 +73,10 @@ public List getRewardHistory(Long memberId){ .toList(); } - /* - 리워드 거래 내역 추가 (아이템 구매용) - */ + + @Transactional public void addPointTransaction(long price, Member member, ItemCategory itemCategory){ + member.buyItemByPoint(price); if(price<=0) throw new BusinessException(ExceptionType.PRICE_IS_POSITIVE); @@ -110,9 +95,11 @@ public void addPointTransaction(long price, Member member, ItemCategory itemCate /* 포인트 적립 거래 내역 추가 (리뷰 작성용) */ + @Transactional public void addPointTransactionForReview(Member member) { + member.addPointByReview(); PointTransaction pointTransaction = PointTransaction.builder() - .changeAmount(100) // 리뷰 적립 포인트 + .changeAmount(REVIEW_REWARD_POINTS) .balanceAfter(Math.toIntExact(member.getPoint())) .pointType(PointType.REVIEW) .member(member) @@ -120,9 +107,6 @@ public void addPointTransactionForReview(Member member) { pointTransactionRepository.save(pointTransaction); } - /* - 상품 사용 기능 - */ @Transactional public void useItem(Long memberId, Long orderItemId){ OrderItem orderItem = orderService.getOrderItem(memberId, orderItemId); @@ -131,4 +115,11 @@ public void useItem(Long memberId, Long orderItemId){ } orderItem.useItem(); } + + private List convertOrdersToResponse(List orders, Function mapper){ + return orders.stream() + .flatMap(order -> order.getOrderItems().stream()) + .map(mapper) + .toList(); + } } diff --git a/src/main/java/avengers/lion/weather/service/WeatherService.java b/src/main/java/avengers/lion/weather/service/WeatherService.java index 8a68038..0d3820c 100644 --- a/src/main/java/avengers/lion/weather/service/WeatherService.java +++ b/src/main/java/avengers/lion/weather/service/WeatherService.java @@ -45,53 +45,46 @@ public class WeatherService { private static final ZoneId KST = ZoneId.of("Asia/Seoul"); private static final DateTimeFormatter HOUR_FMT = DateTimeFormatter.ofPattern("a h시", Locale.KOREAN); + private static final String CACHE_KEY_PREFIX = "weather:gumi:basic:"; + private static final String API_EXCLUDE_PARAMS = "minutely,alerts"; + private static final int SKIP_CURRENT_HOUR = 1; + private static final int HOURLY_FORECAST_COUNT = 6; + private static final int CACHE_BUFFER_SECONDS = 5; + /* + 현재 시간을 기반으로 Redis 키 생성 + */ private String cacheKey() { ZonedDateTime hour = ZonedDateTime.now(KST).truncatedTo(ChronoUnit.HOURS); - return "weather:gumi:basic:" +hour.toEpochSecond(); + return CACHE_KEY_PREFIX + hour.toEpochSecond(); } + private Duration ttlUntilNextHour() { ZonedDateTime now = ZonedDateTime.now(KST); - return Duration.between(now, now.truncatedTo(ChronoUnit.HOURS).plusHours(1)).plusSeconds(5); + return Duration.between(now, now.truncatedTo(ChronoUnit.HOURS).plusHours(1)).plusSeconds(CACHE_BUFFER_SECONDS); } public WeatherBasic oneCall() { String key = cacheKey(); + // 캐시된 날씨 데이터가 있으면 바로 가져오기 Object hit = redis.opsForValue().get(key); - if(hit instanceof WeatherBasic cached) { + if (hit instanceof WeatherBasic cached) { return cached; } - OpenWeatherDto dto = requestOpenWeather(); + // OpenWeatherApi를 호출하여 날씨 데이터 가져오기 + OpenWeatherDto dto = requestOpenWeather(); OpenWeatherDto.Current current = dto.current(); List hourly = dto.hourly(); List daily = dto.daily(); - // 현재 - WeatherBasic.Now now = new WeatherBasic.Now( - current.temp(), - current.weather().getFirst().icon(), - daily.getFirst().temp().max(), - daily.getFirst().temp().min()); - + WeatherBasic.Now now = convertToCurrentWeather(current, daily); // 6시간 - List hourList = hourly.stream() - .skip(1) - .limit(6) - .map(hour -> { - ZonedDateTime kstTime = Instant.ofEpochSecond(hour.dt()) - .atZone(ZoneId.of("Asia/Seoul")); - - DateTimeFormatter formatter = DateTimeFormatter.ofPattern("a h시", Locale.KOREAN); - String formattedTime = kstTime.format(formatter); - return new WeatherBasic.Hour(formattedTime, hour.temp(), hour.weather().getFirst().icon()); - }) - .toList(); - - // 오늘 제외 + List hourList = convertToHourWeather(hourly); WeatherBasic view = new WeatherBasic(now, hourList); redis.opsForValue().set(key, view, ttlUntilNextHour()); return view; } + public OpenWeatherDto requestOpenWeather() { URI uri = UriComponentsBuilder.fromUriString(baseUrl + "/data/3.0/onecall") .queryParam("lat", lat) @@ -99,16 +92,38 @@ public OpenWeatherDto requestOpenWeather() { .queryParam("appid", apiKey) .queryParam("lang", lang) .queryParam("units", units) - .queryParam("exclude", "minutely,alerts") + .queryParam("exclude", API_EXCLUDE_PARAMS) .build() .toUri(); log.info("onecall uri = {}", uri); - try{ + try { return rest.getForObject(uri, OpenWeatherDto.class); - } catch(HttpClientErrorException | HttpServerErrorException | UnknownContentTypeException | ResourceAccessException e) { + } catch (HttpClientErrorException | HttpServerErrorException | UnknownContentTypeException | + ResourceAccessException e) { log.error("OpenWeather API 호출 실패: {}", e.getMessage()); throw new BusinessException(ExceptionType.WEATHER_API_ERROR); } + } + public WeatherBasic.Now convertToCurrentWeather(OpenWeatherDto.Current current, List daily) { + return new WeatherBasic.Now( + current.temp(), + current.weather().getFirst().icon(), + daily.getFirst().temp().max(), + daily.getFirst().temp().min()); + } + + public List convertToHourWeather(List hourly){ + return hourly.stream() + .skip(SKIP_CURRENT_HOUR) + .limit(HOURLY_FORECAST_COUNT) + .map(hour -> { + ZonedDateTime kstTime = Instant.ofEpochSecond(hour.dt()) + .atZone(ZoneId.of("Asia/Seoul")); + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("a h시", Locale.KOREAN); + String formattedTime = kstTime.format(formatter); + return new WeatherBasic.Hour(formattedTime, hour.temp(), hour.weather().getFirst().icon()); + }) + .toList(); } } \ No newline at end of file