diff --git a/src/main/java/avengers/lion/auth/controller/AuthController.java b/src/main/java/avengers/lion/auth/controller/AuthController.java index ff675b9..43c4431 100644 --- a/src/main/java/avengers/lion/auth/controller/AuthController.java +++ b/src/main/java/avengers/lion/auth/controller/AuthController.java @@ -22,8 +22,6 @@ public class AuthController implements AuthApi { public ResponseEntity> exchangeToken(@RequestBody CodeExchangeRequest request) { // Redis에서 코드로 토큰 조회 String accessToken = redisTemplate.opsForValue().get("auth_code:" + request.getCode()); - log.info("request = {}", request.getCode()); - log.info("accessToken = {}", accessToken); if (accessToken == null) { throw new IllegalArgumentException("Invalid or expired code"); } diff --git a/src/main/java/avengers/lion/global/OAuth2SuccessHandler.java b/src/main/java/avengers/lion/global/OAuth2SuccessHandler.java index e40ad16..e2cd044 100644 --- a/src/main/java/avengers/lion/global/OAuth2SuccessHandler.java +++ b/src/main/java/avengers/lion/global/OAuth2SuccessHandler.java @@ -1,20 +1,16 @@ package avengers.lion.global; import avengers.lion.auth.domain.KakaoUserInfo; -import avengers.lion.global.jwt.TokenDto; import avengers.lion.global.jwt.TokenProvider; -import avengers.lion.global.response.SuccessResponseBody; import avengers.lion.member.domain.Member; import avengers.lion.member.domain.MemberRole; import avengers.lion.member.repository.MemberRepository; -import com.fasterxml.jackson.databind.ObjectMapper; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; -import org.springframework.http.MediaType; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.user.OAuth2User; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; @@ -44,15 +40,12 @@ public class OAuth2SuccessHandler extends SimpleUrlAuthenticationSuccessHandler @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException{ - log.info("🟢 OAuth2SuccessHandler 시작 - 카카오 로그인 성공"); // 카카오에서 내려준 사용자 정보를 꺼냄 OAuth2User oAuth2User = (OAuth2User) authentication.getPrincipal(); - log.info("🔵 OAuth2User 정보: {}", oAuth2User.getAttributes()); // 파싱하기 위한 래퍼 클래스 KakaoUserInfo kakaoUserInfo = new KakaoUserInfo(oAuth2User.getAttributes()); - log.info("🔵 KakaoUserInfo - email: {}, nickname: {}", kakaoUserInfo.getEmail(), kakaoUserInfo.getNickname()); // DB에 이메일로 가입된 유저가 있는지 확인, 없으면 신규 회원 생성 Member member = memberRepository.findByEmail(kakaoUserInfo.getEmail()) @@ -66,23 +59,20 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo .build() )); - log.info("🔵 Member 조회/생성 완료 - ID: {}, Email: {}", member.getId(), member.getEmail()); // 사용자 식별 정보를 이용해 토큰 발급 String accessToken = tokenProvider.createAccessToken(member.getId(), member.getRole().name()); - log.info("🔵 AccessToken 생성 완료 - Length: {}", accessToken.length()); // 임시 코드 생성 (UUID) String authCode = UUID.randomUUID().toString(); - log.info("🔵 임시 코드 생성: {}", authCode); // Redis에 임시 코드-토큰 매핑 저장 (5분 TTL) redisTemplate.opsForValue().set("auth_code:" + authCode, accessToken, 5, TimeUnit.MINUTES); - log.info("🔵 Redis에 임시 코드 저장 완료"); + // 프론트엔드로 임시 코드와 함께 리다이렉트 String frontendUrl = "http://localhost:5174/auth/callback?code=" + authCode; getRedirectStrategy().sendRedirect(request, response, frontendUrl); - log.info("🟢 OAuth2SuccessHandler 완료"); + } } diff --git a/src/main/java/avengers/lion/mission/api/MissionApi.java b/src/main/java/avengers/lion/mission/api/MissionApi.java index 0d8098c..3e45490 100644 --- a/src/main/java/avengers/lion/mission/api/MissionApi.java +++ b/src/main/java/avengers/lion/mission/api/MissionApi.java @@ -137,7 +137,7 @@ ResponseEntity> getMissionReviews( ) @GetMapping("/{missionId}/reviews/scroll") @PreAuthorize("hasAuthority('ROLE_USER')") - ResponseEntity>> getMissionReviewsScroll( + ResponseEntity>> getMissionReviewsWithCursor( @PathVariable Long missionId, @RequestParam(required = false) Long lastReviewId, @RequestParam(required = false, defaultValue = "3") @Min(1) @Max(50) int limit, diff --git a/src/main/java/avengers/lion/mission/controller/MissionController.java b/src/main/java/avengers/lion/mission/controller/MissionController.java index 52b2dd1..1efdcc0 100644 --- a/src/main/java/avengers/lion/mission/controller/MissionController.java +++ b/src/main/java/avengers/lion/mission/controller/MissionController.java @@ -51,7 +51,7 @@ public ResponseEntity> getMissionReviews( */ @GetMapping("/{missionId}/reviews/scroll") @PreAuthorize( "hasAuthority('ROLE_USER')") - public ResponseEntity>> getMissionReviewsScroll( + public ResponseEntity>> getMissionReviewsWithCursor( @PathVariable Long missionId, @RequestParam("lastReviewId") Long lastReviewId, @RequestParam(required = false, defaultValue = "3") @Min(1) @Max(50) int limit, diff --git a/src/main/java/avengers/lion/mission/domain/Mission.java b/src/main/java/avengers/lion/mission/domain/Mission.java index 80e5ffa..209fe03 100644 --- a/src/main/java/avengers/lion/mission/domain/Mission.java +++ b/src/main/java/avengers/lion/mission/domain/Mission.java @@ -44,6 +44,9 @@ public class Mission extends BaseEntity { @Column(name = "longitude", columnDefinition = "DECIMAL(11,8)") private Double longitude; + @Column(name = "tip") + private String tip; + @OneToMany(mappedBy = "mission") private List completedMissions = new ArrayList<>(); diff --git a/src/main/java/avengers/lion/mission/dto/response/MissionResponse.java b/src/main/java/avengers/lion/mission/dto/response/MissionResponse.java index f193577..9b09bae 100644 --- a/src/main/java/avengers/lion/mission/dto/response/MissionResponse.java +++ b/src/main/java/avengers/lion/mission/dto/response/MissionResponse.java @@ -4,7 +4,7 @@ import avengers.lion.place.domain.PlaceCategory; -public record MissionResponse(Long missionId, String placeName, String description, Double latitude, Double longitude, PlaceCategory placeCategory, String address, Boolean isCompleted) { +public record MissionResponse(Long missionId, String placeName, String description, Double latitude, Double longitude, PlaceCategory placeCategory, String address, String tip, Boolean isCompleted) { public static MissionResponse of(Mission mission, Boolean isCompleted){ return new MissionResponse( @@ -15,6 +15,7 @@ public static MissionResponse of(Mission mission, Boolean isCompleted){ mission.getLongitude(), mission.getPlaceCategory(), mission.getAddress(), + mission.getTip(), isCompleted ); }; diff --git a/src/main/java/avengers/lion/mission/repository/MissionRepository.java b/src/main/java/avengers/lion/mission/repository/MissionRepository.java index 457c403..b94865d 100644 --- a/src/main/java/avengers/lion/mission/repository/MissionRepository.java +++ b/src/main/java/avengers/lion/mission/repository/MissionRepository.java @@ -8,12 +8,14 @@ import org.springframework.stereotype.Repository; import java.util.List; +import java.util.Set; @Repository public interface MissionRepository extends JpaRepository { - @Query("SELECT DISTINCT m FROM Mission m LEFT JOIN FETCH m.completedMissions cm " + - "LEFT JOIN FETCH cm.member " + - "WHERE m.status = :missionStatus") - List findAllByMissionStatusWithCompletedMissions(@Param("missionStatus") MissionStatus missionStatus); + @Query("SELECT m FROM Mission m WHERE m.status = :status") + List findAllByStatus(@Param("status") MissionStatus status); + + @Query("SELECT cm.mission.id FROM CompletedMission cm WHERE cm.member.id = :memberId") + Set findCompletedMissionIdsByMember(@Param("memberId") Long memberId); } diff --git a/src/main/java/avengers/lion/mission/service/CallbackService.java b/src/main/java/avengers/lion/mission/service/CallbackService.java index 88782ee..04e6a5d 100644 --- a/src/main/java/avengers/lion/mission/service/CallbackService.java +++ b/src/main/java/avengers/lion/mission/service/CallbackService.java @@ -48,28 +48,14 @@ public class CallbackService { */ public void verifySignatureOrThrow(String jobId, FastApiCallbackRequest request, String signature) { if (signature == null || signature.trim().isEmpty()) { - log.warn("Missing callback signature for jobId: {}", jobId); throw new BusinessException(ExceptionType.FAST_API_DENIED); } - log.info("request={}",request); try { // 페이로드 생성: jobId + requestBody (JSON) String requestJson = objectMapper.writeValueAsString(request); String payload = jobId + requestJson; - log.warn("Signature verification for jobId: {}", jobId); - log.warn("Request JSON: {}", requestJson); - log.warn("Payload: {}", payload); - log.warn("Received signature: {}", signature); - log.warn("Secret key: {}", secretKey); - - log.debug("Signature verification for jobId: {}", jobId); - log.debug("Request JSON: {}", requestJson); - log.debug("Payload: {}", payload); - log.debug("Received signature: {}", signature); - log.debug("Secret key: {}", secretKey); - // HMAC-SHA256 서명 생성 Mac mac = Mac.getInstance("HmacSHA256"); SecretKeySpec signingKey = new SecretKeySpec(secretKey.getBytes(StandardCharsets.UTF_8), "HmacSHA256"); @@ -78,20 +64,12 @@ public void verifySignatureOrThrow(String jobId, FastApiCallbackRequest request, byte[] rawHmac = mac.doFinal(payload.getBytes(StandardCharsets.UTF_8)); String expectedSignature = HexFormat.of().formatHex(rawHmac); - log.warn("Expected signature: {}", expectedSignature); - - log.debug("Expected signature: {}", expectedSignature); - // 서명 비교 (타이밍 공격 방지) if (!constantTimeEquals(signature, expectedSignature)) { - log.warn("Invalid callback signature for jobId: {}", jobId); throw new BusinessException(ExceptionType.FAST_API_DENIED); } - log.debug("Callback signature verified successfully for jobId: {}", jobId); - } catch (NoSuchAlgorithmException | InvalidKeyException e) { - log.error("Failed to verify callback signature for jobId: {}", jobId, e); throw new BusinessException(ExceptionType.FAST_API_DENIED); } catch (JsonProcessingException e) { throw new RuntimeException(e); @@ -135,7 +113,6 @@ public void processCallback(String jobId, FastApiCallbackRequest request) { 진행 중 콜백 */ private void handleProgressCallback(String jobId) { - log.info("진행중 콜백"); eventService.sendEvent(jobId, VerificationEvent.progress(jobId)); } @@ -143,7 +120,6 @@ private void handleProgressCallback(String jobId) { 완료 콜백 */ private void handleCompletedCallback(String jobId, FastApiCallbackRequest request) { - log.info("완료 콜백"); MetadataCacheService.VerificationMetadata metadata = metadataCacheService.getMetadata(jobId); if (metadata == null) { eventService.sendEvent(jobId, VerificationEvent.error(jobId)); @@ -170,7 +146,6 @@ private void handleCompletedCallback(String jobId, FastApiCallbackRequest reques } private void handleFailedCallback(String jobId, FastApiCallbackRequest request) { - log.info("실패 콜백"); cleanupService.cleanupFailedVerification(jobId); eventService.sendEvent(jobId, VerificationEvent.failed(jobId)); } diff --git a/src/main/java/avengers/lion/mission/service/MetadataCacheService.java b/src/main/java/avengers/lion/mission/service/MetadataCacheService.java index 248e520..7763a65 100644 --- a/src/main/java/avengers/lion/mission/service/MetadataCacheService.java +++ b/src/main/java/avengers/lion/mission/service/MetadataCacheService.java @@ -28,10 +28,8 @@ public void cacheMetadata(String jobId, String imageUrl, String publicId, Long m String key = CACHE_PREFIX + jobId; redisTemplate.opsForValue().set(key, metadata, CACHE_TTL); - log.debug("Cached metadata for jobId: {}", jobId); } catch (Exception e) { - log.error("Failed to cache metadata for jobId: {}", jobId, e); throw new RuntimeException("메타데이터 캐싱에 실패했습니다.", e); } } @@ -44,12 +42,10 @@ public VerificationMetadata getMetadata(String jobId) { if (cached instanceof VerificationMetadata metadata) { return metadata; } - - log.debug("No metadata found for jobId: {}", jobId); + return null; } catch (Exception e) { - log.error("Failed to retrieve metadata for jobId: {}", jobId, e); return null; } } @@ -58,9 +54,7 @@ public void removeMetadata(String jobId) { try { String key = CACHE_PREFIX + jobId; redisTemplate.delete(key); - log.debug("Removed metadata for jobId: {}", jobId); } catch (Exception e) { - log.error("Failed to remove metadata for jobId: {}", jobId, e); } } diff --git a/src/main/java/avengers/lion/mission/service/MissionService.java b/src/main/java/avengers/lion/mission/service/MissionService.java index 5ac4cdc..1709824 100644 --- a/src/main/java/avengers/lion/mission/service/MissionService.java +++ b/src/main/java/avengers/lion/mission/service/MissionService.java @@ -24,6 +24,12 @@ @Service @RequiredArgsConstructor + +/* +Mission 서비스 +사용자가 미션을 수행하기 위해 미션 페이지에 진입했을 때, +미션 조회, 미션에 관한 리뷰 조회를 수행하는 서비스 + */ public class MissionService { private static final double THRESHOLD_METERS = 100.0; @@ -35,14 +41,8 @@ public class MissionService { 미션 전체조회 프리뷰 -> 사용자별 완료 상태 포함 */ public List getAllMissions(Long memberId){ - List activeMissions = missionRepository.findAllByMissionStatusWithCompletedMissions(MissionStatus.ACTIVE); - - // 완료된 미션 ID 집합을 미리 추출하여 O(1) 조회 최적화 - Set completedMissionIds = activeMissions.stream() - .flatMap(mission -> mission.getCompletedMissions().stream()) - .filter(completed -> completed.getMember().getId().equals(memberId)) - .map(completed -> completed.getMission().getId()) - .collect(Collectors.toSet()); + List activeMissions = missionRepository.findAllByStatus(MissionStatus.ACTIVE); + Set completedMissionIds = missionRepository.findCompletedMissionIdsByMember(memberId); return activeMissions.stream() .map(mission -> { @@ -55,19 +55,20 @@ public List getAllMissions(Long memberId){ public MissionPreReviewResponse getMissionPreReviews(Long missionId, SortType sortType){ List reviews = reviewRepository.findAllReviewByMissionId(missionId, null, 4, sortType); Long count = reviewRepository.countReviewByMissionId(missionId); + // 다음 페이지가 있는지 boolean hasNext = reviews.size() > 3; if (hasNext) reviews = reviews.subList(0, 3); List data = reviews.stream() .map(MissionReviewResponse::of) .toList(); - LocalDateTime nextAt = null; Long nextId = null; - if (!reviews.isEmpty()) { Review last = reviews.getLast(); nextId = last.getId(); } + Long nextId = null; + // 다음 페이지 요청 시 전달할 마지막 id + if (!reviews.isEmpty()) nextId = reviews.getLast().getId(); return new MissionPreReviewResponse(count, data, new PageMeta(hasNext, null, nextId)); } - /* 미션 리뷰조회 스크롤 */ @@ -81,7 +82,7 @@ public PageResult getMissionReviews(Long missionId, Long .toList(); Long nextId = null; - if (!reviews.isEmpty()) { Review last = reviews.getLast(); nextId = last.getId(); } + if (!reviews.isEmpty()) {nextId = reviews.getLast().getId(); } return new PageResult<>(data, hasNext, null, nextId); } @@ -100,6 +101,7 @@ public String gpsAuthentication(Long missionId, GpsAuthenticationRequest gpsAuth return mission.getImageUrl(); } + private double calcDistanceMeters( double lat1, double lng1, double lat2, double lng2 ) { diff --git a/src/main/java/avengers/lion/mission/service/MissionVerifyService.java b/src/main/java/avengers/lion/mission/service/MissionVerifyService.java index 890a82d..6deb37d 100644 --- a/src/main/java/avengers/lion/mission/service/MissionVerifyService.java +++ b/src/main/java/avengers/lion/mission/service/MissionVerifyService.java @@ -18,13 +18,17 @@ @Slf4j @Service @RequiredArgsConstructor + +/* +Mission 인증 서비스 +사용자가 촬영 미션을 수행할 때 처리하는 서비스 코드 + */ public class MissionVerifyService { private final MetadataCacheService metadataCacheService; private final CloudinaryPreSignedService cloudinaryPreSignedService; private final VerificationCleanupService cleanupService; private final WebClient webClient; - private final MissionRepository missionRepository; @Value("${app.fast-api.url}") private String fastApiUrl; @@ -37,10 +41,8 @@ public class MissionVerifyService { */ public UploadUrlResponse generateUploadUrl(Long missionId) { try { - log.info("Generating upload URL for missionId: {}", missionId); // 임시 키 생성 (실제 인증 시 새로 생성됨) String tempKey = "temp-" + metadataCacheService.generateJobId(); - log.info("Temp key generated: {}", tempKey); // Cloudinary Pre-signed URL 생성 CloudinaryPreSignedService.CloudinaryUploadInfo uploadInfo = cloudinaryPreSignedService.generatePreSignedUrl(tempKey); @@ -94,6 +96,9 @@ public VerifyImageResponse startVerification(Long missionId, VerifyImageRequest + /* + FastApi 콜백 메서드 -> 분석 요청을 보냄 + */ @Async public void callFastApiAsync(String jobId, String imageUrl) { try { diff --git a/src/main/java/avengers/lion/mission/service/VerificationCleanupService.java b/src/main/java/avengers/lion/mission/service/VerificationCleanupService.java index 15434ff..f8430c3 100644 --- a/src/main/java/avengers/lion/mission/service/VerificationCleanupService.java +++ b/src/main/java/avengers/lion/mission/service/VerificationCleanupService.java @@ -18,8 +18,6 @@ public class VerificationCleanupService { public void cleanupFailedVerification(String jobId) { MetadataCacheService.VerificationMetadata metadata = metadataCacheService.getMetadata(jobId); if (metadata != null) { - log.info("Cleaning up failed verification: jobId={}, publicId={}", jobId, metadata.publicId()); - // 1. Cloudinary에서 이미지 삭제 try { cloudinaryService.deleteImage(metadata.publicId()); diff --git a/src/main/java/avengers/lion/mission/service/VerificationEventService.java b/src/main/java/avengers/lion/mission/service/VerificationEventService.java index ce1f78f..715ba0a 100644 --- a/src/main/java/avengers/lion/mission/service/VerificationEventService.java +++ b/src/main/java/avengers/lion/mission/service/VerificationEventService.java @@ -60,7 +60,6 @@ public SseEmitter createEventStream(String jobId) { public void sendEvent(String jobId, VerificationEvent event) { SseEmitter emitter = emitters.get(jobId); if (emitter == null) { - log.warn("No SSE emitter found for jobId: {}", jobId); return; } @@ -82,7 +81,6 @@ public void sendEvent(String jobId, VerificationEvent event) { } } catch (IOException e) { - log.error("Failed to send SSE event for jobId: {}", jobId, e); emitter.completeWithError(e); emitters.remove(jobId); } diff --git a/src/main/java/avengers/lion/review/repository/ReviewQueryRepositoryImpl.java b/src/main/java/avengers/lion/review/repository/ReviewQueryRepositoryImpl.java index 8e2d4ca..f946eaf 100644 --- a/src/main/java/avengers/lion/review/repository/ReviewQueryRepositoryImpl.java +++ b/src/main/java/avengers/lion/review/repository/ReviewQueryRepositoryImpl.java @@ -27,7 +27,6 @@ public List findAllReviewByMissionId(Long missionId, Long cursorId, int QReview r = QReview.review; QMember member = QMember.member; 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));