From 605ad3bf3e42000ae08340f44106abf7cbbe0215 Mon Sep 17 00:00:00 2001 From: Juhye0k Date: Wed, 24 Sep 2025 15:07:06 +0900 Subject: [PATCH 1/7] =?UTF-8?q?refactor=20:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?= =?UTF-8?q?=20=EA=B1=B0=EB=9E=98=EB=82=B4=EC=97=AD=20=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=84=B0=20=EC=9D=BC=EA=B4=80=EC=84=B1=20=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lion/wallet/controller/WalletController.java | 4 +++- .../avengers/lion/wallet/service/WalletService.java | 10 +++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) 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..601819f 100644 --- a/src/main/java/avengers/lion/wallet/service/WalletService.java +++ b/src/main/java/avengers/lion/wallet/service/WalletService.java @@ -24,6 +24,8 @@ @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,7 +44,7 @@ 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); } @@ -70,7 +72,7 @@ public List getMyPartnerCards(Long memberId){ /* 사용한 상품 조회 */ - public List getMyUsedItems(Long memberId){ + public List getMyConsumedItems(Long memberId){ List orders = orderService.getConsumedItem(memberId); return orders.stream() .flatMap(order -> order.getOrderItems().stream()) @@ -110,9 +112,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) From 68902cc7b087142f8181ea8de2310d2b83e8b9f8 Mon Sep 17 00:00:00 2001 From: Juhye0k Date: Wed, 24 Sep 2025 15:07:23 +0900 Subject: [PATCH 2/7] =?UTF-8?q?refactor=20:=20=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EB=A9=94=EC=86=8C=EB=93=9C=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lion/review/service/ReviewService.java | 21 ++----------------- 1 file changed, 2 insertions(+), 19 deletions(-) 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); } + + } From ea3b97501813b37868d884dcd9b3f4dd038b5c42 Mon Sep 17 00:00:00 2001 From: Juhye0k Date: Wed, 24 Sep 2025 15:07:38 +0900 Subject: [PATCH 3/7] =?UTF-8?q?refactor=20:=20=EB=A1=9C=EA=B7=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lion/mission/controller/VerificationController.java | 1 - .../java/avengers/lion/weather/service/WeatherService.java | 6 ++++++ 2 files changed, 6 insertions(+), 1 deletion(-) 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/weather/service/WeatherService.java b/src/main/java/avengers/lion/weather/service/WeatherService.java index 8a68038..81f11a6 100644 --- a/src/main/java/avengers/lion/weather/service/WeatherService.java +++ b/src/main/java/avengers/lion/weather/service/WeatherService.java @@ -46,6 +46,9 @@ public class WeatherService { private static final ZoneId KST = ZoneId.of("Asia/Seoul"); private static final DateTimeFormatter HOUR_FMT = DateTimeFormatter.ofPattern("a h시", Locale.KOREAN); + /* + 현재 시간을 기반으로 Redis 키 생성 + */ private String cacheKey() { ZonedDateTime hour = ZonedDateTime.now(KST).truncatedTo(ChronoUnit.HOURS); return "weather:gumi:basic:" +hour.toEpochSecond(); @@ -57,11 +60,14 @@ private Duration ttlUntilNextHour() { public WeatherBasic oneCall() { String key = cacheKey(); + // 캐시된 날씨 데이터가 있으면 바로 가져오기 Object hit = redis.opsForValue().get(key); if(hit instanceof WeatherBasic cached) { return cached; } + // OpenWeatherApi를 호출하여 날씨 데이터 가져오기 OpenWeatherDto dto = requestOpenWeather(); + OpenWeatherDto.Current current = dto.current(); List hourly = dto.hourly(); List daily = dto.daily(); From 670c15877dd1364d95e82dae90a3ef61233e7c4b Mon Sep 17 00:00:00 2001 From: Juhye0k Date: Wed, 24 Sep 2025 15:25:51 +0900 Subject: [PATCH 4/7] =?UTF-8?q?refactor=20:=20OneCall=20=EB=A9=94=EC=86=8C?= =?UTF-8?q?=EB=93=9C=20=EB=B6=84=EB=A6=AC=20+=20=EC=83=81=EC=88=98=20?= =?UTF-8?q?=ED=95=98=EB=93=9C=EC=BD=94=EB=94=A9=20final=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lion/weather/service/WeatherService.java | 67 +++++++++++-------- 1 file changed, 38 insertions(+), 29 deletions(-) diff --git a/src/main/java/avengers/lion/weather/service/WeatherService.java b/src/main/java/avengers/lion/weather/service/WeatherService.java index 81f11a6..0d3820c 100644 --- a/src/main/java/avengers/lion/weather/service/WeatherService.java +++ b/src/main/java/avengers/lion/weather/service/WeatherService.java @@ -45,59 +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; } // OpenWeatherApi를 호출하여 날씨 데이터 가져오기 - OpenWeatherDto dto = requestOpenWeather(); - + 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) @@ -105,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 From 4a4b9a1f24fd660a608bdaca1c1d470cc8542573 Mon Sep 17 00:00:00 2001 From: Juhye0k Date: Thu, 25 Sep 2025 22:57:49 +0900 Subject: [PATCH 5/7] =?UTF-8?q?refactor=20:=20=EB=B0=98=EB=B3=B5=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EC=A0=9C=EB=84=A4=EB=A6=AD=EC=9D=84=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=98=EC=97=AC=20=EB=8B=A8=EC=88=9C?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lion/wallet/service/WalletService.java | 47 +++++++------------ 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/src/main/java/avengers/lion/wallet/service/WalletService.java b/src/main/java/avengers/lion/wallet/service/WalletService.java index 601819f..c34dc69 100644 --- a/src/main/java/avengers/lion/wallet/service/WalletService.java +++ b/src/main/java/avengers/lion/wallet/service/WalletService.java @@ -18,6 +18,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.function.Function; @Service @@ -48,41 +49,23 @@ public MyWalletResponse getMyWallet(Long 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 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() @@ -90,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); @@ -124,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); @@ -135,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(); + } } From ab3eb481a1eb3e564431123f8856a8d92e6525db Mon Sep 17 00:00:00 2001 From: Juhye0k Date: Thu, 25 Sep 2025 22:58:17 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor=20:=20=ED=8F=AC=EC=9D=B8=ED=8A=B8?= =?UTF-8?q?=20=EA=B5=AC=EB=A7=A4=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/avengers/lion/item/service/ItemService.java | 5 +++-- .../avengers/lion/member/controller/MemberController.java | 4 ++++ .../avengers/lion/member/repository/MemberRepository.java | 1 - .../java/avengers/lion/member/service/MemberService.java | 5 +++++ 4 files changed, 12 insertions(+), 3 deletions(-) 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) From ec2a7dff6d58540dacd712349feb6264a9024cbc Mon Sep 17 00:00:00 2001 From: Juhye0k Date: Thu, 25 Sep 2025 22:58:28 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor=20:=20=EB=AC=B4=ED=95=9C=20?= =?UTF-8?q?=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EC=A0=95=EB=A0=AC=20=EC=98=A4?= =?UTF-8?q?=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lion/review/repository/ReviewQueryRepositoryImpl.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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();