Conversation
- PageableInfo Dto - NewsArticleResponse Dto - NewsPageResponse Dto
Walkthrough뉴스 기사 조회 기능이 도입되었습니다. 뉴스 크롤링, 저장, 조회를 위한 도메인, 서비스, API, DTO, 스케줄러, 리포지토리, 에러/성공 코드, 스웨거 응답, 보안 예외 경로, 테스트 코드 등이 추가 및 수정되었습니다. 일부 패키지 구조도 변경되었습니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant NewsApi
participant NewsService
participant NewsRepository
Client->>NewsApi: GET /api/news?page=&size=&sort=&category=
NewsApi->>NewsService: getNewsList(page, size, sort, category)
NewsService->>NewsRepository: findAllByIsFilteredOutFalse(...) / findByCategoryAndIsFilteredOutFalse(...)
NewsRepository-->>NewsService: Page<NewsArticle>
NewsService-->>NewsApi: NewsPageResponse
NewsApi-->>Client: BaseResponse<NewsPageResponse>
sequenceDiagram
participant Scheduler
participant DaumNewsCrawler
participant NewsRepository
Scheduler->>DaumNewsCrawler: fetchNewsFromDaum(keyword)
DaumNewsCrawler->>NewsRepository: existsByUrl(url)
NewsRepository-->>DaumNewsCrawler: boolean
DaumNewsCrawler->>NewsRepository: save(newsArticle)
NewsRepository-->>DaumNewsCrawler: NewsArticle
sequenceDiagram
participant AdminClient
participant AdminApi
participant NewsSaveScheduler
participant DaumNewsCrawler
participant NewsRepository
AdminClient->>AdminApi: POST /api/admin/crawl
AdminApi->>NewsSaveScheduler: crawlingForAdmin()
NewsSaveScheduler->>DaumNewsCrawler: fetchNewsFromDaum(keyword) x4
DaumNewsCrawler->>NewsRepository: existsByUrl(url)
NewsRepository-->>DaumNewsCrawler: boolean
DaumNewsCrawler->>NewsRepository: save(newsArticle)
NewsRepository-->>DaumNewsCrawler: NewsArticle
AdminApi-->>AdminClient: BaseResponse<Void>
Estimated code review effort🎯 4 (Complex) | ⏱️ ~40 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Possibly related PRs
Suggested reviewers
Poem
Note ⚡️ Unit Test Generation is now available in beta!Learn more here, or try it out under "Finishing Touches" below. 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (2)
🚧 Files skipped from review as they are similar to previous changes (2)
✨ Finishing Touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 12
🧹 Nitpick comments (11)
src/main/java/com/blockguard/server/infra/naver/ocr/NaverOcrClient.java (1)
1-4: 같은 패키지 내 클래스는 import 불필요현재 파일과 동일한
com.blockguard.server.infra.naver.ocr패키지의ByteArrayResourceWithFilename를 import 하고 있습니다. 같은 패키지의 클래스는 import 문이 필요 없으므로 제거하여 불필요한 라인을 줄일 수 있습니다.-import com.blockguard.server.infra.naver.ocr.ByteArrayResourceWithFilename;build.gradle (1)
49-51: Jsoup 버전을 중앙 관리 변수 또는 BOM으로 추출하는 방안을 고려하세요
라이브러리 버전을 직접 하드코딩하면 추후 여러 모듈에서 동일 의존성을 사용할 때 일관성 유지가 어렵습니다.ext(gradle.properties) 혹은 Spring Dependency Management의importBom을 사용해 버전 통제 지점을 하나로 두면 유지보수가 수월합니다.src/main/java/com/blockguard/server/domain/news/dto/response/PageableInfo.java (1)
7-15: Java 21에서는 레코드(record)로 간결하게 표현할 수 있습니다
불변 DTO이며 모든 필드가private final성격이므로 Lombok + 필드 선언 대신 레코드를 사용하면 보일러플레이트를 제거할 수 있습니다.-@Getter -@AllArgsConstructor -@Builder -public class PageableInfo { - private int page; - private int size; - private long totalElements; - private int totalPages; -} +public record PageableInfo( + int page, + int size, + long totalElements, + int totalPages +) {}src/main/java/com/blockguard/server/global/common/codes/ErrorCode.java (1)
38-38: 상수명에 어색한 전치사 ‘TO’ 사용 – 가독성 향상을 위해 수정 권장
FAIL_TO_CRAWLING_NEWS→ 일반적으로 “~에 실패” 패턴은FAIL_NEWS_CRAWLING또는FAIL_TO_CRAWL_NEWS형태가 자연스럽습니다.src/main/java/com/blockguard/server/global/config/swagger/SwaggerResponseDescription.java (1)
99-102: 성공 응답 코드도 Swagger 문서에 병행 추가 필요
실패 코드만 추가되어 있어 성공 시 무엇이 반환되는지 Swagger 스펙이 불완전합니다.GET_NEWS_ARTICLES_SUCCESS를SuccessCode와 매핑하여 문서화하는 것을 권장합니다.src/main/java/com/blockguard/server/domain/news/scheduler/NewsSaveScheduler.java (1)
19-22: 에러 처리 및 성능 개선을 고려해보세요.현재 순차적으로 크롤링을 실행하고 있어 다음과 같은 개선점이 있습니다:
- 하나의 크롤링이 실패하면 나머지도 실행되지 않을 수 있습니다
- 순차 실행으로 인한 성능 이슈가 있을 수 있습니다
다음과 같이 개선할 수 있습니다:
@Scheduled(cron = "0 0 4 * * *") public void saveNewsArticles(){ log.info("뉴스 크롤링 스케줄링 시작"); - daumNewsCrawler.fetchNewsFromDaum("보이스피싱"); - daumNewsCrawler.fetchNewsFromDaum("스미싱"); - daumNewsCrawler.fetchNewsFromDaum("메신저 피싱"); - daumNewsCrawler.fetchNewsFromDaum("몸캠"); + String[] keywords = {"보이스피싱", "스미싱", "메신저 피싱", "몸캠"}; + for (String keyword : keywords) { + try { + daumNewsCrawler.fetchNewsFromDaum(keyword); + } catch (Exception e) { + log.error("키워드 '{}' 크롤링 중 오류 발생: {}", keyword, e.getMessage(), e); + } + } log.info("뉴스 크롤링 스케줄링 완료"); }src/main/java/com/blockguard/server/domain/news/dto/response/NewsPageResponse.java (1)
13-16: 필드 명명 및 문서화 개선 필요필드명과 클래스에 대한 문서화를 추가하는 것이 좋겠습니다. 특히
sort필드의 경우 어떤 형식의 값이 들어가는지 명확하지 않습니다.+/** + * 페이지네이션된 뉴스 기사 목록 응답 DTO + */ @Getter @Builder @AllArgsConstructor public class NewsPageResponse { + /** 뉴스 기사 목록 */ private List<NewsArticleResponse> news; + /** 페이지네이션 정보 */ private PageableInfo pageableInfo; + /** 정렬 기준 (예: "published_at_desc", "published_at_asc") */ private String sort;src/main/java/com/blockguard/server/infra/crawler/DaumNewsCrawler.java (1)
87-88: Thread.sleep 대신 스케줄링 메커니즘 사용 권장
Thread.sleep()은 스레드를 블로킹하여 리소스를 비효율적으로 사용합니다. 대량의 크롤링 작업 시 성능 문제가 발생할 수 있습니다.Spring의
@Async와CompletableFuture를 사용하거나, 전용 스케줄러를 사용하여 비동기적으로 처리하는 것을 고려해보세요. 또한 rate limiting을 위해RateLimiter(예: Guava) 사용을 권장합니다.src/main/java/com/blockguard/server/domain/news/dto/response/NewsArticleResponse.java (1)
34-46: 시간 포맷팅 로직 분리 고려시간 포맷팅 로직이 DTO에 포함되어 있는데, 이는 단일 책임 원칙에 어긋날 수 있습니다. 별도의 유틸리티 클래스로 분리하는 것을 고려해보세요.
TimeFormatUtils같은 유틸리티 클래스를 만들어 시간 관련 포맷팅 로직을 중앙화하면 재사용성과 테스트 용이성이 향상됩니다. 또한 타임존 처리도 명시적으로 할 수 있습니다.public class TimeFormatUtils { public static String formatRelativeTime(LocalDateTime time, ZoneId zoneId) { // 타임존을 고려한 시간 계산 ZonedDateTime zonedTime = time.atZone(zoneId); ZonedDateTime now = ZonedDateTime.now(zoneId); // ... 포맷팅 로직 } }src/main/java/com/blockguard/server/domain/news/application/NewsService.java (2)
31-31: 하드코딩된 문자열 상수화를 권장합니다."전체" 문자열을 상수로 정의하여 유지보수성을 향상시키는 것이 좋겠습니다.
+ private static final String ALL_CATEGORY = "전체"; + public NewsPageResponse getNewsList(int page, int size, String sort, String category) { Pageable pageable = PageRequest.of(page -1, size, getSort(sort)); Page<NewsArticle> newsPage; - if(category.equals("전체")){ + if(ALL_CATEGORY.equals(category)){
54-60: 정렬 로직이 잘 구현되었습니다.최신 Java switch 표현식을 사용하여 깔끔하게 구현되었습니다. 필드명을 상수로 추출하면 더욱 좋겠습니다.
+ private static final String PUBLISHED_AT_FIELD = "publishedAt"; + private Sort getSort(String sort) { return switch (sort) { - case "published_at_asc" -> Sort.by("publishedAt").ascending(); - case "published_at_desc" -> Sort.by("publishedAt").descending(); - default -> Sort.by("publishedAt").descending(); + case "published_at_asc" -> Sort.by(PUBLISHED_AT_FIELD).ascending(); + case "published_at_desc" -> Sort.by(PUBLISHED_AT_FIELD).descending(); + default -> Sort.by(PUBLISHED_AT_FIELD).descending(); }; }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (19)
build.gradle(1 hunks)src/main/java/com/blockguard/server/domain/analysis/application/FraudAnalysisService.java(1 hunks)src/main/java/com/blockguard/server/domain/news/api/NewsApi.java(1 hunks)src/main/java/com/blockguard/server/domain/news/application/NewsService.java(1 hunks)src/main/java/com/blockguard/server/domain/news/dao/NewsRepository.java(1 hunks)src/main/java/com/blockguard/server/domain/news/domain/NewsArticle.java(2 hunks)src/main/java/com/blockguard/server/domain/news/domain/enums/Category.java(1 hunks)src/main/java/com/blockguard/server/domain/news/dto/response/NewsArticleResponse.java(1 hunks)src/main/java/com/blockguard/server/domain/news/dto/response/NewsPageResponse.java(1 hunks)src/main/java/com/blockguard/server/domain/news/dto/response/PageableInfo.java(1 hunks)src/main/java/com/blockguard/server/domain/news/scheduler/NewsSaveScheduler.java(1 hunks)src/main/java/com/blockguard/server/global/common/codes/ErrorCode.java(1 hunks)src/main/java/com/blockguard/server/global/common/codes/SuccessCode.java(1 hunks)src/main/java/com/blockguard/server/global/config/SecurityConfig.java(1 hunks)src/main/java/com/blockguard/server/global/config/swagger/SwaggerResponseDescription.java(1 hunks)src/main/java/com/blockguard/server/infra/crawler/DaumNewsCrawler.java(1 hunks)src/main/java/com/blockguard/server/infra/naver/ocr/ByteArrayResourceWithFilename.java(1 hunks)src/main/java/com/blockguard/server/infra/naver/ocr/NaverOcrClient.java(1 hunks)src/test/java/com/blockguard/server/infra/crawler/DaumNewsCrawlerTest.java(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
src/main/java/com/blockguard/server/domain/analysis/application/FraudAnalysisService.java (1)
src/main/java/com/blockguard/server/infra/ocr/NaverOcrClient.java (2)
NaverOcrClient(19-103)extractTextFromImage(32-90)
src/main/java/com/blockguard/server/domain/news/domain/NewsArticle.java (1)
src/main/java/com/blockguard/server/global/common/entity/BaseEntity.java (1)
BaseEntity(11-24)
src/main/java/com/blockguard/server/domain/news/dto/response/PageableInfo.java (2)
src/main/java/com/blockguard/server/domain/news/dto/response/NewsArticleResponse.java (1)
Getter(11-47)src/main/java/com/blockguard/server/domain/news/dto/response/NewsPageResponse.java (1)
Getter(9-16)
src/main/java/com/blockguard/server/domain/news/domain/enums/Category.java (1)
src/main/java/com/blockguard/server/domain/analysis/domain/enums/FraudType.java (2)
FraudType(5-38)FraudType(34-36)
src/test/java/com/blockguard/server/infra/crawler/DaumNewsCrawlerTest.java (1)
src/test/java/com/blockguard/server/ServerApplicationTests.java (1)
ServerApplicationTests(6-13)
src/main/java/com/blockguard/server/domain/news/dao/NewsRepository.java (3)
src/main/java/com/blockguard/server/domain/guardian/dao/GuardianRepository.java (1)
GuardianRepository(12-27)src/main/java/com/blockguard/server/domain/fraud/dao/FraudUrlRepository.java (1)
FraudUrlRepository(8-11)src/main/java/com/blockguard/server/domain/user/dao/UserRepository.java (1)
UserRepository(9-13)
🔇 Additional comments (9)
src/main/java/com/blockguard/server/infra/naver/ocr/ByteArrayResourceWithFilename.java (1)
1-1: 패키지 재배치 👍
infra.naver.ocr하위로 이동하면서 네임스페이스가 더 명확해졌습니다. 별다른 로직 변경이 없으므로 영향 범위는 패키지 스캔에 한정되며, 기존com.blockguard.server루트 스캔 설정이면 문제없습니다.src/main/java/com/blockguard/server/domain/analysis/application/FraudAnalysisService.java (1)
9-9: 레거시 NaverOcrClient 경로 참조 없음 확인 완료
rg검색 결과,com.blockguard.server.infra.ocr.NaverOcrClient를 참조하는 파일이 더 이상 존재하지 않으므로 추가 조치가 필요 없습니다.src/main/java/com/blockguard/server/global/common/codes/SuccessCode.java (1)
34-34: LGTM! 성공 코드 추가가 적절합니다.새로운 뉴스 조회 API를 위한 성공 코드가 기존 패턴과 일관성 있게 추가되었습니다. 코드 번호 순서도 올바르고 한국어 메시지도 적절합니다.
src/main/java/com/blockguard/server/domain/news/scheduler/NewsSaveScheduler.java (1)
15-15: 크론 표현식이 정확합니다.매일 오전 4시에 실행되는 스케줄링이 올바르게 설정되었습니다.
src/main/java/com/blockguard/server/domain/news/domain/NewsArticle.java (1)
37-42: 새로운 필드 추가가 적절합니다.카테고리와 필터링 여부를 나타내는 필드가 올바르게 추가되었습니다.
EnumType.STRING사용과nullable = false설정이 적절합니다.참고:
isFilteredOut필드명이 boolean 타입에 대해 일반적인 Java 네이밍 규칙인filteredOut로 하는 것도 고려해볼 수 있지만, 현재 명명도 의미가 명확하므로 문제없습니다.src/main/java/com/blockguard/server/domain/news/dao/NewsRepository.java (1)
11-16: Repository 메서드 구현이 적절함카테고리별 필터링과 필터링된 기사 제외 로직이 잘 구현되어 있습니다. Spring Data JPA 명명 규칙을 잘 따르고 있습니다.
src/main/java/com/blockguard/server/domain/news/api/NewsApi.java (1)
25-25: 페이지 인덱싱 변환 로직 정상 작동 확인
NewsService#getNewsList에서 다음 코드로page파라미터를 1-based → 0-based로 올바르게 변환하고 있습니다.
• src/main/java/com/blockguard/server/domain/news/application/NewsService.javaPageable pageable = PageRequest.of(page - 1, size, getSort(sort));따라서 API의
@RequestParam(defaultValue = "1") int page설정과 충돌이 없습니다.src/main/java/com/blockguard/server/domain/news/application/NewsService.java (2)
1-19: 패키지 선언과 임포트가 잘 구성되어 있습니다.필요한 의존성만 임포트하고 있으며, 패키지 구조도 도메인 주도 설계 원칙을 따르고 있습니다.
28-28: 페이지 인덱스 변환이 올바르게 구현되었습니다.1-based 페이지 번호를 0-based로 올바르게 변환하고 있습니다.
| public BaseResponse<NewsPageResponse> getNewsArticles( | ||
| @RequestParam(defaultValue = "1") int page, | ||
| @RequestParam(defaultValue = "10") int size, | ||
| @RequestParam(defaultValue = "published_at_desc") String sort, | ||
| @RequestParam(defaultValue = "전체") String category) { |
There was a problem hiding this comment.
🛠️ Refactor suggestion
입력 파라미터 검증 필요
페이지와 사이즈 파라미터에 대한 검증이 필요합니다. 특히 사이즈 파라미터의 경우 너무 큰 값이 입력되면 성능 문제가 발생할 수 있습니다.
public BaseResponse<NewsPageResponse> getNewsArticles(
- @RequestParam(defaultValue = "1") int page,
- @RequestParam(defaultValue = "10") int size,
+ @RequestParam(defaultValue = "1") @Min(1) int page,
+ @RequestParam(defaultValue = "10") @Min(1) @Max(100) int size,
@RequestParam(defaultValue = "published_at_desc") String sort,
@RequestParam(defaultValue = "전체") String category) {추가로 클래스 레벨에 @Validated 애노테이션을 추가해야 합니다.
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public BaseResponse<NewsPageResponse> getNewsArticles( | |
| @RequestParam(defaultValue = "1") int page, | |
| @RequestParam(defaultValue = "10") int size, | |
| @RequestParam(defaultValue = "published_at_desc") String sort, | |
| @RequestParam(defaultValue = "전체") String category) { | |
| public BaseResponse<NewsPageResponse> getNewsArticles( | |
| @RequestParam(defaultValue = "1") @Min(1) int page, | |
| @RequestParam(defaultValue = "10") @Min(1) @Max(100) int size, | |
| @RequestParam(defaultValue = "published_at_desc") String sort, | |
| @RequestParam(defaultValue = "전체") String category) { |
🤖 Prompt for AI Agents
In src/main/java/com/blockguard/server/domain/news/api/NewsApi.java around lines
24 to 28, input validation is missing for the page and size parameters. Add
validation annotations such as @Min(1) for page and @Min(1) and @Max(sensible
upper limit) for size to prevent invalid or excessively large values. Also, add
the @Validated annotation at the class level to enable validation processing for
the controller.
| public NewsPageResponse getNewsList(int page, int size, String sort, String category) { | ||
| Pageable pageable = PageRequest.of(page -1, size, getSort(sort)); | ||
| Page<NewsArticle> newsPage; | ||
|
|
||
| if(category.equals("전체")){ | ||
| newsPage = newsRepository.findAllByIsFilteredOutFalse(pageable); | ||
| } else{ | ||
| Category enumCategory = Category.from(category); | ||
| newsPage = newsRepository.findByCategoryAndIsFilteredOutFalse(enumCategory, pageable); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
입력 검증 및 에러 처리 추가를 고려해주세요.
메서드에 다음과 같은 개선사항을 적용하는 것을 권장합니다:
- 입력 매개변수에 대한 null 체크
Category.from()메서드의 예외 처리- 페이지 크기에 대한 범위 검증
public NewsPageResponse getNewsList(int page, int size, String sort, String category) {
+ if (category == null || sort == null) {
+ throw new IllegalArgumentException("Category and sort parameters cannot be null");
+ }
+ if (page < 1 || size < 1) {
+ throw new IllegalArgumentException("Page and size must be positive numbers");
+ }
+
Pageable pageable = PageRequest.of(page -1, size, getSort(sort));
Page<NewsArticle> newsPage;
if(category.equals("전체")){
newsPage = newsRepository.findAllByIsFilteredOutFalse(pageable);
} else{
- Category enumCategory = Category.from(category);
+ try {
+ Category enumCategory = Category.from(category);
+ newsPage = newsRepository.findByCategoryAndIsFilteredOutFalse(enumCategory, pageable);
+ } catch (IllegalArgumentException e) {
+ log.warn("Invalid category provided: {}", category);
+ throw new InvalidCategoryException("Invalid category: " + category);
+ }
- newsPage = newsRepository.findByCategoryAndIsFilteredOutFalse(enumCategory, pageable);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| public NewsPageResponse getNewsList(int page, int size, String sort, String category) { | |
| Pageable pageable = PageRequest.of(page -1, size, getSort(sort)); | |
| Page<NewsArticle> newsPage; | |
| if(category.equals("전체")){ | |
| newsPage = newsRepository.findAllByIsFilteredOutFalse(pageable); | |
| } else{ | |
| Category enumCategory = Category.from(category); | |
| newsPage = newsRepository.findByCategoryAndIsFilteredOutFalse(enumCategory, pageable); | |
| } | |
| public NewsPageResponse getNewsList(int page, int size, String sort, String category) { | |
| // 1. Null checks for required String parameters | |
| if (category == null || sort == null) { | |
| throw new IllegalArgumentException("Category and sort parameters cannot be null"); | |
| } | |
| // 2. Range checks for pagination parameters | |
| if (page < 1 || size < 1) { | |
| throw new IllegalArgumentException("Page and size must be positive numbers"); | |
| } | |
| Pageable pageable = PageRequest.of(page - 1, size, getSort(sort)); | |
| Page<NewsArticle> newsPage; | |
| if (category.equals("전체")) { | |
| newsPage = newsRepository.findAllByIsFilteredOutFalse(pageable); | |
| } else { | |
| try { | |
| // 3. Validate and convert category, wrapping any IllegalArgumentException | |
| Category enumCategory = Category.from(category); | |
| newsPage = newsRepository.findByCategoryAndIsFilteredOutFalse(enumCategory, pageable); | |
| } catch (IllegalArgumentException e) { | |
| log.warn("Invalid category provided: {}", category); | |
| throw new InvalidCategoryException("Invalid category: " + category, e); | |
| } | |
| } | |
| // ... (rest of method that builds and returns NewsPageResponse) | |
| } |
🤖 Prompt for AI Agents
In src/main/java/com/blockguard/server/domain/news/application/NewsService.java
around lines 27 to 36, add input validation and error handling to the
getNewsList method. First, check for null values in the input parameters page,
size, sort, and category, and handle them appropriately. Second, wrap the call
to Category.from(category) in a try-catch block to handle any exceptions from
invalid category strings. Third, validate that the page size is within an
acceptable range (e.g., greater than zero and below a maximum limit) and handle
invalid sizes by throwing an exception or returning a default value.
| public static Category from(String input){ | ||
| return switch (input){ | ||
| case "보이스피싱" -> VOICE_PHISHING; | ||
| case "스미싱" -> SMISHING; | ||
| case "메신저 피싱", "메신저피싱" -> MESSAGE_VOICE_PHISHING; | ||
| default -> ETC; | ||
| }; | ||
| } |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
from 메서드 구현이 적절합니다.
한국어 키워드를 enum 상수로 매핑하는 로직이 잘 구현되었습니다. 스위치 표현식 사용과 "메신저 피싱"/"메신저피싱" 둘 다 처리하는 부분이 좋습니다.
중복 코드 검토 제안: src/main/java/com/blockguard/server/domain/analysis/domain/enums/FraudType.java에 이미 유사한 카테고리들이 정의되어 있습니다 (VOICE_PHISHING("보이스피싱"), MESSENGER_PHISHING("메신저피싱"), SMISHING("스미싱")). 코드 중복을 피하기 위해 두 enum 간의 통합이나 공통 매핑 로직 사용을 고려해보세요.
🏁 Script executed:
#!/bin/bash
# 기존 FraudType enum과의 중복 확인
rg -A 10 -B 5 "VOICE_PHISHING|SMISHING|MESSENGER_PHISHING" --type javaLength of output: 3575
Category와 FraudType enum 매핑 로직 통합 제안
현재 Category enum과 FraudType enum이 동일한 한글 키워드(“보이스피싱”, “메신저피싱”, “스미싱”)를 별도 구현하고 있어 중복이 발생하고 있습니다. 아래 위치를 참조하여 공통화 방안을 검토해주세요.
-
src/main/java/com/blockguard/server/domain/news/domain/enums/Category.java
• enum 상수: VOICE_PHISHING, SMISHING, MESSAGE_VOICE_PHISHING
•public static Category from(String input)메서드에서 한글 키워드 매핑 -
src/main/java/com/blockguard/server/domain/analysis/domain/enums/FraudType.java
• enum 상수: VOICE_PHISHING("보이스피싱"), MESSENGER_PHISHING("메신저피싱"), SMISHING("스미싱")
제안 예시:
- 하나의
FraudType.from(input)공통 메서드를 통해 매핑 결과를 받아Category로 변환 - 또는 공통 유틸리티 클래스로 한글→Enum 매핑 로직을 추출
이렇게 하면 중복된 문자열 처리 로직을 제거하고 유지보수성을 높일 수 있습니다.
🤖 Prompt for AI Agents
In src/main/java/com/blockguard/server/domain/news/domain/enums/Category.java
around lines 6 to 13, the string-to-enum mapping logic duplicates the same
Korean keywords found in FraudType.java. To fix this, refactor
Category.from(String input) to delegate the string mapping to a common method,
such as FraudType.from(input), then convert the resulting FraudType enum to the
corresponding Category enum. Alternatively, extract the shared string-to-enum
mapping logic into a utility class used by both enums. This removes duplication
and centralizes the keyword mapping logic for easier maintenance.
| } catch (IOException e) { | ||
| throw new BusinessExceptionHandler(ErrorCode.FAIL_TO_CRAWLING_NEWS); | ||
| } catch (InterruptedException e) { | ||
| throw new RuntimeException(e); | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
예외 처리 개선 필요
현재 IOException을 BusinessException으로 감싸고 있는데, 크롤링 실패의 구체적인 원인을 알 수 없습니다. 또한 InterruptedException을 RuntimeException으로 다시 던지는 것은 적절하지 않습니다.
} catch (IOException e) {
+ log.error("Failed to crawl news from Daum for keyword: {}", keyword, e);
throw new BusinessExceptionHandler(ErrorCode.FAIL_TO_CRAWLING_NEWS);
} catch (InterruptedException e) {
- throw new RuntimeException(e);
+ Thread.currentThread().interrupt();
+ log.warn("News crawling interrupted for keyword: {}", keyword);
+ throw new BusinessExceptionHandler(ErrorCode.CRAWLING_INTERRUPTED);
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| } catch (IOException e) { | |
| throw new BusinessExceptionHandler(ErrorCode.FAIL_TO_CRAWLING_NEWS); | |
| } catch (InterruptedException e) { | |
| throw new RuntimeException(e); | |
| } | |
| } catch (IOException e) { | |
| log.error("Failed to crawl news from Daum for keyword: {}", keyword, e); | |
| throw new BusinessExceptionHandler(ErrorCode.FAIL_TO_CRAWLING_NEWS); | |
| } catch (InterruptedException e) { | |
| Thread.currentThread().interrupt(); | |
| log.warn("News crawling interrupted for keyword: {}", keyword); | |
| throw new BusinessExceptionHandler(ErrorCode.CRAWLING_INTERRUPTED); | |
| } |
🤖 Prompt for AI Agents
In src/main/java/com/blockguard/server/infra/crawler/DaumNewsCrawler.java around
lines 90 to 94, improve exception handling by including the original IOException
cause when throwing BusinessExceptionHandler to preserve the specific crawling
failure details. For InterruptedException, restore the thread's interrupted
status by calling Thread.currentThread().interrupt() instead of rethrowing as
RuntimeException, and handle or propagate it appropriately without losing the
interrupt signal.
yeonju73
left a comment
There was a problem hiding this comment.
수고하셨습니다!! 하나 궁금한게 response body에서 imageUrl 그대로 웹에 입력하면 그 기사 이미지가 뜨는거 맞나요!?
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
src/main/java/com/blockguard/server/domain/admin/api/AdminApi.java (1)
37-37: SuccessCode 상수명에 오타가 있습니다.
CRWAL_DAUM_NEWS_SUCCESS에서 "CRWAL"은 "CRAWL"의 오타로 보입니다.SuccessCode 클래스에서 해당 상수명을 수정하는 것이 좋겠습니다:
-CRWAL_DAUM_NEWS_SUCCESS +CRAWL_DAUM_NEWS_SUCCESS
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/main/java/com/blockguard/server/domain/admin/api/AdminApi.java(2 hunks)src/main/java/com/blockguard/server/global/common/codes/SuccessCode.java(1 hunks)src/main/java/com/blockguard/server/global/config/SecurityConfig.java(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main/java/com/blockguard/server/global/config/SecurityConfig.java
- src/main/java/com/blockguard/server/global/common/codes/SuccessCode.java
🔇 Additional comments (1)
src/main/java/com/blockguard/server/domain/admin/api/AdminApi.java (1)
5-5: 새로운 의존성 주입이 적절히 구현되었습니다.
DaumNewsCrawler의존성이 올바르게 import되고 주입되었습니다.Also applies to: 20-20
💻 Related Issue
close #33
🚀 Work Description
: 실시간으로 조회하는 방식 대신 스케줄러를 통해 일정 시간 간격마다 DB에 크롤링해서 저장하고 프론트에 반환하는 형식으로 구현하였습니다. 현재는 새벽 4시마다 스케줄러가 실행되는데 추후에 시간 간격이나 특정 시간에 조회하도록 변경하겠습니다.
🙇🏻♀️ To Reviewer
Summary by CodeRabbit
Summary by CodeRabbit
신규 기능
버그 수정
문서화
테스트
기타