Conversation
|
Warning Rate limit exceeded@chaeyuuu has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 8 minutes and 45 seconds before requesting another review. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the We recommend that you space out your commits to avoid hitting the rate limit. 🚦 How do rate limits work?CodeRabbit enforces hourly rate limits for each developer per organization. Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout. Please see our FAQ for further information. 📒 Files selected for processing (7)
""" Walkthrough이번 변경에서는 URL 사기분석 API를 위한 신규 엔드포인트, 서비스, DTO, 리포지토리, 인프라 서비스 등이 추가되었습니다. 사기 URL 동기화 및 조회, Google Safe Browsing 연동, 엔티티 및 성공/실패 코드 확장, 보안 설정 허용 경로 추가 등이 포함됩니다. Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant FraudApi
participant FraudService
participant FraudUrlRepository
participant GoogleSafeBrowsingService
Client->>+FraudApi: POST /api/fraud/url (FraudUrlRequest)
FraudApi->>+FraudService: checkFraudUrl(FraudUrlRequest)
FraudService->>FraudUrlRepository: existsByUrl(url)
alt URL exists
FraudService-->>FraudApi: FraudUrlResponse(Danger)
else URL not exists
FraudService->>GoogleSafeBrowsingService: isUrlSafe(url)
alt Unsafe
FraudService-->>FraudApi: FraudUrlResponse(Danger)
else Safe
FraudService-->>FraudApi: FraudUrlResponse(Safety)
end
end
FraudApi-->>Client: BaseResponse<FraudUrlResponse>
sequenceDiagram
participant Admin
participant AdminApi
participant FraudUrlImporter
participant FraudUrlRepository
participant OpenAPI
Admin->>+AdminApi: GET /api/admin/update/fraud-url
AdminApi->>+FraudUrlImporter: syncFraudUrlsFromOpenApi()
loop for each page
FraudUrlImporter->>OpenAPI: GET fraud URLs
OpenAPI-->>FraudUrlImporter: fraud URL data
loop for each URL
FraudUrlImporter->>FraudUrlRepository: existsByUrl(url)
alt Not exists
FraudUrlImporter->>FraudUrlRepository: save(FraudUrl)
end
end
end
FraudUrlImporter-->>AdminApi: void
AdminApi-->>Admin: BaseResponse<Void>
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes(해당 변경사항에서 요구사항과 무관한 기능적 코드 변경은 발견되지 않았습니다.) Suggested reviewers
Poem
✨ 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: 14
🧹 Nitpick comments (5)
src/main/java/com/blockguard/server/domain/fraud/domain/enums/RiskLevel.java (1)
7-9: 열거형 상수 명명 규칙 개선 권장Java 열거형 상수는 일반적으로 대문자와 언더스코어를 사용하는 것이 관례입니다.
- Danger("위험"), - Safety("안전"); + DANGER("위험"), + SAFETY("안전");src/main/java/com/blockguard/server/domain/fraud/dao/FraudUrlRepository.java (1)
8-11: 리포지토리 인터페이스 구현이 적절함Spring Data JPA 명명 규칙을 잘 따르고 있으며,
Optional<FraudUrl>반환으로 null-safe한 구현입니다.existsByUrl메서드는 존재 여부만 확인하므로 성능상 효율적입니다.URL 조회 성능 향상을 위해 데이터베이스 레벨에서
url컬럼에 인덱스 추가를 고려해보세요.src/main/java/com/blockguard/server/domain/analysis/api/FraudAnalysisApi.java (1)
36-36: TODO 주석을 해결해야 합니다.이미지 파일 개수 제한에 대한 TODO 주석이 있습니다. 현재는 하드코딩된 2개 제한이 있는데, 이를 설정 파일로 외부화하거나 비즈니스 요구사항에 따라 적절히 조정하는 것을 고려해보세요.
이미지 파일 개수 제한을 설정 가능하게 만드는 구현을 도와드릴까요?
src/main/java/com/blockguard/server/infra/google/GoogleSafeBrowsingService.java (1)
25-36: 하드코딩된 값들을 설정으로 외부화해야 합니다.클라이언트 정보(
clientId,clientVersion)와 위협 유형(MALWARE,SOCIAL_ENGINEERING등)이 하드코딩되어 있습니다. 이를 설정 파일로 외부화하면 유지보수성이 향상됩니다.설정 파일에 다음과 같이 추가할 수 있습니다:
google: safe: client-id: blockguard-app client-version: 1.0 threat-types: - MALWARE - SOCIAL_ENGINEERING - UNWANTED_SOFTWAREsrc/main/java/com/blockguard/server/infra/importer/FraudUrlImporter.java (1)
29-31: 사용되지 않는 레거시 API URL을 정리해야 합니다.
apiUrlOld변수가 선언되었지만 현재 구현에서 사용되지 않고 있습니다. 향후 사용 계획이 있다면 주석으로 명시하거나, 사용하지 않는다면 제거하는 것이 좋습니다.레거시 API URL 사용 계획을 명확히 하거나 제거를 고려해보세요.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (14)
src/main/java/com/blockguard/server/domain/admin/api/AdminApi.java(1 hunks)src/main/java/com/blockguard/server/domain/analysis/api/FraudAnalysisApi.java(1 hunks)src/main/java/com/blockguard/server/domain/fraud/api/FraudApi.java(1 hunks)src/main/java/com/blockguard/server/domain/fraud/application/FraudService.java(1 hunks)src/main/java/com/blockguard/server/domain/fraud/dao/FraudUrlRepository.java(1 hunks)src/main/java/com/blockguard/server/domain/fraud/domain/FraudUrl.java(2 hunks)src/main/java/com/blockguard/server/domain/fraud/domain/enums/RiskLevel.java(1 hunks)src/main/java/com/blockguard/server/domain/fraud/dto/request/FraudUrlRequest.java(1 hunks)src/main/java/com/blockguard/server/domain/fraud/dto/response/FraudUrlResponse.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/infra/google/GoogleSafeBrowsingService.java(1 hunks)src/main/java/com/blockguard/server/infra/importer/FraudUrlImporter.java(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (7)
src/main/java/com/blockguard/server/domain/fraud/domain/FraudUrl.java (2)
src/main/java/com/blockguard/server/domain/fraud/domain/FraudPhoneNumber.java (1)
FraudPhoneNumber(9-32)src/main/java/com/blockguard/server/global/common/entity/BaseEntity.java (1)
BaseEntity(11-24)
src/main/java/com/blockguard/server/domain/fraud/dto/request/FraudUrlRequest.java (1)
src/main/java/com/blockguard/server/domain/fraud/dto/response/FraudUrlResponse.java (1)
Getter(7-11)
src/main/java/com/blockguard/server/domain/fraud/dao/FraudUrlRepository.java (2)
src/main/java/com/blockguard/server/domain/analysis/domain/FraudAnalysisRecord.java (1)
FraudAnalysisRecord(12-42)src/main/java/com/blockguard/server/domain/fraud/domain/FraudPhoneNumber.java (1)
FraudPhoneNumber(9-32)
src/main/java/com/blockguard/server/domain/fraud/dto/response/FraudUrlResponse.java (2)
src/main/java/com/blockguard/server/domain/fraud/dto/request/FraudUrlRequest.java (1)
Getter(6-10)src/main/java/com/blockguard/server/domain/analysis/domain/enums/RiskLevel.java (1)
RiskLevel(3-5)
src/main/java/com/blockguard/server/domain/fraud/domain/enums/RiskLevel.java (1)
src/main/java/com/blockguard/server/domain/analysis/domain/FraudAnalysisRecord.java (1)
FraudAnalysisRecord(12-42)
src/main/java/com/blockguard/server/domain/fraud/application/FraudService.java (1)
src/main/java/com/blockguard/server/infra/google/GoogleSafeBrowsingService.java (1)
Service(13-63)
src/main/java/com/blockguard/server/global/common/codes/SuccessCode.java (2)
src/main/java/com/blockguard/server/domain/analysis/domain/enums/FraudType.java (1)
FraudType(4-9)src/main/java/com/blockguard/server/domain/fraud/domain/FraudPhoneNumber.java (1)
FraudPhoneNumber(9-32)
🔇 Additional comments (6)
src/main/java/com/blockguard/server/global/common/codes/ErrorCode.java (1)
30-30: 새로운 에러 코드 추가가 적절합니다.OPEN API 호출 실패에 대한 에러 코드가 올바르게 추가되었습니다. HTTP 상태 코드, 순차적 번호 체계, 그리고 명확한 한국어 메시지가 기존 패턴과 일관성 있게 구현되었습니다.
src/main/java/com/blockguard/server/domain/fraud/domain/FraudUrl.java (1)
7-7: LocalDate import 추가가 적절합니다.새로운 detectedDate 필드를 위한 LocalDate import가 올바르게 추가되었습니다.
src/main/java/com/blockguard/server/domain/fraud/domain/enums/RiskLevel.java (1)
13-16: JSON 직렬화 구현이 적절함
@JsonValue어노테이션을 사용하여 한국어 값이 JSON 응답에 포함되도록 구현한 것이 좋습니다. API 응답의 가독성을 높입니다.src/main/java/com/blockguard/server/global/common/codes/SuccessCode.java (1)
27-29: 성공 코드 추가 승인 및 전화번호 사기 기능 구현 확인
CHECK_PHONE_NUMBER_FRAUD_SUCCESS를 위한FraudPhoneNumber엔티티가 이미src/main/java/com/blockguard/server/domain/fraud/domain/FraudPhoneNumber.java에 구현되어 있어, 해당 성공 코드 추가는 적절합니다. 나머지 코드도 기존 패턴에 맞게 잘 작성되었습니다.src/main/java/com/blockguard/server/domain/analysis/api/FraudAnalysisApi.java (1)
31-31: 리팩토링이 올바르게 적용되었습니다.
ResponseEntity래퍼를 제거하고BaseResponse를 직접 반환하도록 변경한 것이 일관성 있고 좋습니다. 새로 추가된 다른 API들과 동일한 응답 패턴을 따르고 있습니다.src/main/java/com/blockguard/server/domain/fraud/api/FraudApi.java (1)
21-21: HTTP 메서드 선택 검토 결과: POST 사용이 적절현재
src/main/java/com/blockguard/server/domain/fraud/api/FraudApi.java(21행)에서@PostMapping("/url") public BaseResponse<FraudUrlResponse> fraudUrl(@RequestBody FraudUrlRequest fraudUrlRequest) { … }와 같이 요청 본문에 URL을 담아 처리하고 있어, URL 길이 및 특수문자 처리 측면에서 POST 방식이 적합합니다.
- URL이 짧고 특수문자가 적은 단순 조회라면
@GetMapping+@RequestParam전환을 고려해보세요.- 복잡한 URL 또는 길이 제한이 우려된다면 현행 POST 구현을 유지해도 무방합니다.
| public BaseResponse<FraudUrlResponse> fraudUrl(@RequestBody FraudUrlRequest fraudUrlRequest){ | ||
| FraudUrlResponse fraudUrlResponse = fraudService.checkFraudUrl(fraudUrlRequest); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
입력 검증이 누락되었습니다.
@RequestBody FraudUrlRequest fraudUrlRequest 파라미터에 대한 검증 어노테이션이 없습니다. URL 형식 검증이나 필수 값 검증을 추가하는 것을 고려해보세요.
다음과 같이 검증을 추가할 수 있습니다:
- public BaseResponse<FraudUrlResponse> fraudUrl(@RequestBody FraudUrlRequest fraudUrlRequest){
+ public BaseResponse<FraudUrlResponse> fraudUrl(@Valid @RequestBody FraudUrlRequest fraudUrlRequest){그리고 FraudUrlRequest DTO에 적절한 검증 어노테이션을 추가하세요.
📝 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<FraudUrlResponse> fraudUrl(@RequestBody FraudUrlRequest fraudUrlRequest){ | |
| FraudUrlResponse fraudUrlResponse = fraudService.checkFraudUrl(fraudUrlRequest); | |
| public BaseResponse<FraudUrlResponse> fraudUrl(@Valid @RequestBody FraudUrlRequest fraudUrlRequest){ | |
| FraudUrlResponse fraudUrlResponse = fraudService.checkFraudUrl(fraudUrlRequest); |
🤖 Prompt for AI Agents
In src/main/java/com/blockguard/server/domain/fraud/api/FraudApi.java around
lines 23 to 24, the method parameter fraudUrlRequest lacks validation
annotations. Add @Valid annotation before @RequestBody in the method signature
to enable validation. Also, update the FraudUrlRequest DTO by adding appropriate
validation annotations such as @NotNull, @NotEmpty, or @Pattern to enforce
required fields and URL format checks.
| @Column(nullable = false) | ||
| private LocalDate detectedDate; |
There was a problem hiding this comment.
💡 Verification agent
🧩 Analysis chain
detectedDate 필드 추가 시 데이터베이스 마이그레이션을 고려해주세요.
새로운 필수 필드(non-nullable) detectedDate가 추가되었습니다. 기존 데이터가 있는 경우를 위해 데이터베이스 마이그레이션 스크립트에서 기본값 설정을 확인해주세요.
데이터베이스 마이그레이션 스크립트에 detectedDate (또는 snake_case detected_date) 컬럼 추가가 반영되어 있는지 확인하기 위해 다음 스크립트를 실행해주세요.
🏁 Script executed:
#!/bin/bash
set -e
echo "=== Flyway 마이그레이션 스크립트 확인 (src/main/resources/db/migration) ==="
if [ -d src/main/resources/db/migration ]; then
ls -1 src/main/resources/db/migration
echo "--- 'detected_date' 컬럼 검색 결과 ---"
grep -R "detected_date" -n src/main/resources/db/migration || echo ">> 검색 결과 없음"
else
echo ">> src/main/resources/db/migration 디렉토리가 존재하지 않습니다."
fi
echo
echo "=== Liquibase changelog 확인 (src/main/resources/db/changelog) ==="
if [ -d src/main/resources/db/changelog ]; then
ls -1 src/main/resources/db/changelog
echo "--- 'detectedDate' 또는 'detected_date' 검색 결과 ---"
grep -RE "detectedDate|detected_date" -n src/main/resources/db/changelog || echo ">> 검색 결과 없음"
else
echo ">> src/main/resources/db/changelog 디렉토리가 존재하지 않습니다."
fiLength of output: 608
아래 스크립트를 실행해 프로젝트에 마이그레이션 프레임워크(Flyway/Liquibase) 사용 여부 및 관련 설정이 있는지 확인해주세요.
🏁 Script executed:
#!/bin/bash
set -e
echo "=== Flyway 의존성 및 설정 검색 ==="
grep -R "flyway" -n --include="pom.xml" --include="*.gradle" || echo ">> Flyway 관련 설정/의존성 없음"
grep -R "spring.flyway" -n src/main/resources || echo ">> application.properties/yml에 Flyway 설정 없음"
echo
echo "=== Liquibase 의존성 및 설정 검색 ==="
grep -R "liquibase" -n --include="pom.xml" --include="*.gradle" || echo ">> Liquibase 관련 설정/의존성 없음"
grep -R "spring.liquibase" -n src/main/resources || echo ">> application.properties/yml에 Liquibase 설정 없음"
echo
echo "=== 프로젝트 내 마이그레이션 스크립트 디렉토리 검색 ==="
find . -type d -iname "*migrat*" || echo ">> migration 디렉토리 없음"Length of output: 802
데이터베이스 마이그레이션 검토 및 대응 방안 필요
프로젝트 내에 Flyway/Liquibase 등 마이그레이션 툴 설정이나 스크립트 디렉토리가 없습니다. detectedDate(non-nullable) 컬럼 추가로 인해 운영 DB 스키마가 깨질 수 있으므로, 다음 중 하나를 선택해 반영해주세요.
- 마이그레이션 툴 사용 시
- Flyway:
src/main/resources/db/migration디렉토리 생성 후 신규 스크립트(v1__add_detected_date.sql 등) 추가 - Liquibase:
src/main/resources/db/changelog디렉토리 생성 후 XML/YAML/JSON 체인지로그 작성
- Flyway:
- JPA 자동 DDL 사용 시
application.properties/application.yml에spring.jpa.hibernate.ddl-auto=update(또는 적절한 값) 설정 확인- 운영환경에서는 자동 DDL보다 마이그레이션 도구 사용을 권장
위 항목 중 프로젝트 정책에 맞는 방법으로 스키마 변경 절차를 마련하고, CI/CD 파이프라인에 반영해 주세요.
🤖 Prompt for AI Agents
In src/main/java/com/blockguard/server/domain/fraud/domain/FraudUrl.java around
lines 26 to 27, a new non-nullable column detectedDate is added without any
database migration scripts or tools configured. To fix this, choose and
implement a database schema migration strategy: either add a Flyway migration
script under src/main/resources/db/migration or a Liquibase changelog under
src/main/resources/db/changelog reflecting this schema change, or if using JPA
automatic DDL, ensure spring.jpa.hibernate.ddl-auto=update is set in application
properties. Also, integrate the chosen migration process into the CI/CD pipeline
to prevent production DB schema issues.
| ResponseEntity<Map> response = restTemplate.exchange( | ||
| googleUrl, | ||
| HttpMethod.POST, | ||
| entity, | ||
| Map.class | ||
| ); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
RestTemplate 타임아웃 설정이 필요합니다.
외부 API 호출에 타임아웃이 설정되어 있지 않아 응답이 지연될 경우 무한 대기할 수 있습니다.
RestTemplate에 타임아웃을 설정하거나, 메서드 레벨에서 타임아웃을 적용하는 것을 고려해보세요. 또는 WebClient 사용을 고려해보세요.
🤖 Prompt for AI Agents
In
src/main/java/com/blockguard/server/infra/google/GoogleSafeBrowsingService.java
around lines 44 to 49, the RestTemplate call to the external API lacks a timeout
setting, which can cause indefinite waiting if the API is slow or unresponsive.
To fix this, configure the RestTemplate instance with appropriate connection and
read timeouts before making the call, or alternatively apply a timeout at the
method level. You may also consider replacing RestTemplate with WebClient, which
supports reactive timeouts more naturally.
| } catch (Exception e) { | ||
| log.error("Google Safe Browsing API 요청 실패: {}", e.getMessage()); | ||
| return false; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
예외 처리를 개선해야 합니다.
모든 예외를 catch (Exception e)로 처리하면서 false를 반환하는 것은 너무 광범위합니다. 네트워크 오류, API 키 오류, 응답 파싱 오류 등을 구분하여 처리하는 것이 좋습니다.
예외 유형별로 다르게 처리하는 것을 고려해보세요:
- } catch (Exception e) {
- log.error("Google Safe Browsing API 요청 실패: {}", e.getMessage());
- return false;
- }
+ } catch (HttpClientErrorException e) {
+ log.error("Google Safe Browsing API 클라이언트 오류: {}", e.getMessage());
+ return false;
+ } catch (HttpServerErrorException e) {
+ log.error("Google Safe Browsing API 서버 오류: {}", e.getMessage());
+ return false;
+ } catch (ResourceAccessException e) {
+ log.error("Google Safe Browsing API 네트워크 오류: {}", e.getMessage());
+ return false;
+ } catch (Exception e) {
+ log.error("예상치 못한 오류: {}", e.getMessage());
+ return false;
+ }📝 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 (Exception e) { | |
| log.error("Google Safe Browsing API 요청 실패: {}", e.getMessage()); | |
| return false; | |
| } | |
| } catch (HttpClientErrorException e) { | |
| log.error("Google Safe Browsing API 클라이언트 오류: {}", e.getMessage()); | |
| return false; | |
| } catch (HttpServerErrorException e) { | |
| log.error("Google Safe Browsing API 서버 오류: {}", e.getMessage()); | |
| return false; | |
| } catch (ResourceAccessException e) { | |
| log.error("Google Safe Browsing API 네트워크 오류: {}", e.getMessage()); | |
| return false; | |
| } catch (Exception e) { | |
| log.error("예상치 못한 오류: {}", e.getMessage()); | |
| return false; | |
| } |
🤖 Prompt for AI Agents
In
src/main/java/com/blockguard/server/infra/google/GoogleSafeBrowsingService.java
around lines 57 to 60, the current catch block catches all exceptions broadly
and returns false, which is too generic. Refactor the exception handling to
catch specific exceptions such as network errors, API key errors, and response
parsing errors separately. For each exception type, log an appropriate error
message and handle the failure accordingly to provide clearer diagnostics and
more precise error management.
| while (hasNext) { | ||
|
|
||
| String encodedKey = URLEncoder.encode(serviceKey, StandardCharsets.UTF_8); | ||
| String fullUrl = String.format("%s?page=%d&perPage=%d&serviceKey=%s", | ||
| apiUrl, page, perPage, encodedKey); | ||
|
|
||
| URI requestUrl = URI.create(fullUrl); | ||
|
|
||
| ResponseEntity<Map> response = restTemplate.getForEntity(requestUrl, Map.class); | ||
|
|
||
| if (response.getStatusCode().is2xxSuccessful()) { | ||
| Map<String, Object> responseBody = response.getBody(); | ||
| List<Map<String, Object>> data = (List<Map<String, Object>>) responseBody.get("data"); | ||
|
|
||
| if (data == null || data.isEmpty()) { | ||
| hasNext = false; | ||
| } else { | ||
| for (Map<String, Object> item : data) { | ||
| String detectedDateStr = (String) item.get("날짜"); | ||
| String urlStr = (String) item.get("홈페이지주소"); | ||
|
|
||
| if (urlStr == null || detectedDateStr == null) continue; | ||
|
|
||
| if (!fraudUrlRepository.existsByUrl(urlStr)) { | ||
| LocalDate detectedDate = LocalDate.parse(detectedDateStr); | ||
| fraudUrlRepository.save(FraudUrl.builder() | ||
| .url(urlStr) | ||
| .detectedDate(detectedDate) | ||
| .lastCheckedAt(LocalDateTime.now()) | ||
| .build()); | ||
| } | ||
| } | ||
| page++; | ||
| } | ||
|
|
||
| log.info("DB 업데이트 완료"); | ||
|
|
||
| } else { | ||
| throw new BusinessExceptionHandler(ErrorCode.FAIL_IMPORT_OPEN_API); | ||
| } | ||
| } |
There was a problem hiding this comment.
무한 루프 및 성능 문제가 있을 수 있습니다.
페이지네이션 처리에서 몇 가지 문제점이 있습니다:
- API가 항상 빈 데이터를 반환하지 않으면 무한 루프가 발생할 수 있습니다
- 대용량 데이터 처리 시 메모리 및 성능 문제가 발생할 수 있습니다
- API 호출 간 지연이 없어 외부 API에 부하를 줄 수 있습니다
다음과 같은 개선을 제안합니다:
+ private static final int MAX_PAGES = 1000; // 최대 페이지 제한
+ private static final int DELAY_BETWEEN_REQUESTS = 100; // ms 단위 지연
+
public void syncFraudUrlsFromOpenApi() {
int page = 1;
int perPage = 1000;
boolean hasNext = true;
- while (hasNext) {
+ while (hasNext && page <= MAX_PAGES) {
// 기존 코드...
+ // API 호출 간 지연 추가
+ try {
+ Thread.sleep(DELAY_BETWEEN_REQUESTS);
+ } catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ break;
+ }
}
}📝 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.
| while (hasNext) { | |
| String encodedKey = URLEncoder.encode(serviceKey, StandardCharsets.UTF_8); | |
| String fullUrl = String.format("%s?page=%d&perPage=%d&serviceKey=%s", | |
| apiUrl, page, perPage, encodedKey); | |
| URI requestUrl = URI.create(fullUrl); | |
| ResponseEntity<Map> response = restTemplate.getForEntity(requestUrl, Map.class); | |
| if (response.getStatusCode().is2xxSuccessful()) { | |
| Map<String, Object> responseBody = response.getBody(); | |
| List<Map<String, Object>> data = (List<Map<String, Object>>) responseBody.get("data"); | |
| if (data == null || data.isEmpty()) { | |
| hasNext = false; | |
| } else { | |
| for (Map<String, Object> item : data) { | |
| String detectedDateStr = (String) item.get("날짜"); | |
| String urlStr = (String) item.get("홈페이지주소"); | |
| if (urlStr == null || detectedDateStr == null) continue; | |
| if (!fraudUrlRepository.existsByUrl(urlStr)) { | |
| LocalDate detectedDate = LocalDate.parse(detectedDateStr); | |
| fraudUrlRepository.save(FraudUrl.builder() | |
| .url(urlStr) | |
| .detectedDate(detectedDate) | |
| .lastCheckedAt(LocalDateTime.now()) | |
| .build()); | |
| } | |
| } | |
| page++; | |
| } | |
| log.info("DB 업데이트 완료"); | |
| } else { | |
| throw new BusinessExceptionHandler(ErrorCode.FAIL_IMPORT_OPEN_API); | |
| } | |
| } | |
| // … other imports and class declaration … | |
| private static final int MAX_PAGES = 1000; // 최대 페이지 제한 | |
| private static final int DELAY_BETWEEN_REQUESTS = 100; // ms 단위 지연 | |
| public void syncFraudUrlsFromOpenApi() { | |
| int page = 1; | |
| int perPage = 1000; | |
| boolean hasNext = true; | |
| while (hasNext && page <= MAX_PAGES) { | |
| // API 호출 간 지연 추가 | |
| try { | |
| Thread.sleep(DELAY_BETWEEN_REQUESTS); | |
| } catch (InterruptedException e) { | |
| Thread.currentThread().interrupt(); | |
| break; | |
| } | |
| String encodedKey = URLEncoder.encode(serviceKey, StandardCharsets.UTF_8); | |
| String fullUrl = String.format("%s?page=%d&perPage=%d&serviceKey=%s", | |
| apiUrl, page, perPage, encodedKey); | |
| URI requestUrl = URI.create(fullUrl); | |
| ResponseEntity<Map> response = restTemplate.getForEntity(requestUrl, Map.class); | |
| if (response.getStatusCode().is2xxSuccessful()) { | |
| Map<String, Object> responseBody = response.getBody(); | |
| @SuppressWarnings("unchecked") | |
| List<Map<String, Object>> data = | |
| (List<Map<String, Object>>) responseBody.get("data"); | |
| if (data == null || data.isEmpty()) { | |
| hasNext = false; | |
| } else { | |
| for (Map<String, Object> item : data) { | |
| String detectedDateStr = (String) item.get("날짜"); | |
| String urlStr = (String) item.get("홈페이지주소"); | |
| if (urlStr == null || detectedDateStr == null) continue; | |
| if (!fraudUrlRepository.existsByUrl(urlStr)) { | |
| LocalDate detectedDate = LocalDate.parse(detectedDateStr); | |
| fraudUrlRepository.save(FraudUrl.builder() | |
| .url(urlStr) | |
| .detectedDate(detectedDate) | |
| .lastCheckedAt(LocalDateTime.now()) | |
| .build()); | |
| } | |
| } | |
| page++; | |
| } | |
| log.info("DB 업데이트 완료"); | |
| } else { | |
| throw new BusinessExceptionHandler(ErrorCode.FAIL_IMPORT_OPEN_API); | |
| } | |
| } | |
| } |
🤖 Prompt for AI Agents
In src/main/java/com/blockguard/server/infra/importer/FraudUrlImporter.java
between lines 44 and 84, the pagination loop risks infinite looping if the API
never returns empty data, and lacks delay between API calls which can overload
the external service. To fix this, add a maximum page limit or a termination
condition based on total pages or response metadata to prevent infinite loops.
Also, introduce a short delay (e.g., Thread.sleep) between API requests to
reduce load on the external API. Additionally, consider processing data in
smaller batches or streaming to improve memory usage for large datasets.
| for (Map<String, Object> item : data) { | ||
| String detectedDateStr = (String) item.get("날짜"); | ||
| String urlStr = (String) item.get("홈페이지주소"); | ||
|
|
||
| if (urlStr == null || detectedDateStr == null) continue; | ||
|
|
||
| if (!fraudUrlRepository.existsByUrl(urlStr)) { | ||
| LocalDate detectedDate = LocalDate.parse(detectedDateStr); | ||
| fraudUrlRepository.save(FraudUrl.builder() | ||
| .url(urlStr) | ||
| .detectedDate(detectedDate) | ||
| .lastCheckedAt(LocalDateTime.now()) | ||
| .build()); | ||
| } | ||
| } | ||
| page++; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion
트랜잭션 관리가 필요합니다.
대량의 데이터를 개별적으로 저장하면 성능이 저하되고, 부분 실패 시 데이터 일관성 문제가 발생할 수 있습니다.
배치 처리와 트랜잭션 관리를 고려해보세요:
+ @Transactional
public void syncFraudUrlsFromOpenApi() {
+ List<FraudUrl> batchList = new ArrayList<>();
+ int batchSize = 100;
// 페이지 처리 로직...
- if (!fraudUrlRepository.existsByUrl(urlStr)) {
- LocalDate detectedDate = LocalDate.parse(detectedDateStr);
- fraudUrlRepository.save(FraudUrl.builder()
+ if (!fraudUrlRepository.existsByUrl(urlStr)) {
+ // batchList에 추가
+ batchList.add(FraudUrl.builder()
.url(urlStr)
.detectedDate(detectedDate)
.lastCheckedAt(LocalDateTime.now())
- .build());
- }
+ .build());
+
+ if (batchList.size() >= batchSize) {
+ fraudUrlRepository.saveAll(batchList);
+ batchList.clear();
+ }
+ }Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In src/main/java/com/blockguard/server/infra/importer/FraudUrlImporter.java
around lines 61 to 77, the current code saves each FraudUrl entity individually
without transaction management, which can cause performance issues and data
inconsistency on partial failures. Refactor the code to batch process the data
and wrap the save operations within a single transaction. Use a transactional
annotation or programmatic transaction management to ensure atomicity, and
consider saving entities in bulk if supported by the repository to improve
performance.
There was a problem hiding this comment.
이 부분도 리펙토링때 고려하면 좋을것 같습니다!!
There was a problem hiding this comment.
@yeonju73 네, 맞습니다! 트랜잭션 관리와 배치 처리 최적화는 대량 데이터를 다룰 때 매우 중요한 부분이죠. 리팩토링 시점에 함께 고려하시면 성능과 데이터 일관성 측면에서 큰 개선이 있을 것 같습니다. 👍
현재는 기능 구현에 집중하고, 추후 리팩토링에서 이런 최적화 작업들을 체계적으로 진행하시는 것이 좋은 접근 방식이라고 생각합니다!
| if (urlStr == null || detectedDateStr == null) continue; | ||
|
|
||
| if (!fraudUrlRepository.existsByUrl(urlStr)) { | ||
| LocalDate detectedDate = LocalDate.parse(detectedDateStr); |
There was a problem hiding this comment.
🛠️ Refactor suggestion
날짜 파싱 오류 처리가 필요합니다.
LocalDate.parse(detectedDateStr)에서 날짜 형식이 올바르지 않으면 예외가 발생할 수 있습니다. 파싱 실패에 대한 처리가 필요합니다.
- LocalDate detectedDate = LocalDate.parse(detectedDateStr);
+ try {
+ LocalDate detectedDate = LocalDate.parse(detectedDateStr);
fraudUrlRepository.save(FraudUrl.builder()
.url(urlStr)
.detectedDate(detectedDate)
.lastCheckedAt(LocalDateTime.now())
.build());
+ } catch (DateTimeParseException e) {
+ log.warn("날짜 파싱 실패 - URL: {}, 날짜: {}", urlStr, detectedDateStr);
+ continue;
+ }📝 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.
| LocalDate detectedDate = LocalDate.parse(detectedDateStr); | |
| try { | |
| LocalDate detectedDate = LocalDate.parse(detectedDateStr); | |
| fraudUrlRepository.save(FraudUrl.builder() | |
| .url(urlStr) | |
| .detectedDate(detectedDate) | |
| .lastCheckedAt(LocalDateTime.now()) | |
| .build()); | |
| } catch (DateTimeParseException e) { | |
| log.warn("날짜 파싱 실패 - URL: {}, 날짜: {}", urlStr, detectedDateStr); | |
| continue; | |
| } |
🤖 Prompt for AI Agents
In src/main/java/com/blockguard/server/infra/importer/FraudUrlImporter.java at
line 68, the code uses LocalDate.parse(detectedDateStr) without handling
potential parsing exceptions. To fix this, wrap the parsing statement in a
try-catch block to catch DateTimeParseException and handle the error
appropriately, such as logging the issue or skipping the invalid entry, to
prevent the application from crashing on invalid date formats.
yeonju73
left a comment
There was a problem hiding this comment.
수고하셨습니다! 개발 마무리하고 패키지 리팩토링 드가봅시닷 헤헤
| registry | ||
| .requestMatchers("/api/auth/**").permitAll() // 로그인/회원가입만 허용 | ||
| .requestMatchers("/api/fraud-analysis").permitAll() | ||
| .requestMatchers("/api/admin/update/fraud-url", "/api/fraud/url", "api/fraud-analysis").permitAll() |
| for (Map<String, Object> item : data) { | ||
| String detectedDateStr = (String) item.get("날짜"); | ||
| String urlStr = (String) item.get("홈페이지주소"); | ||
|
|
||
| if (urlStr == null || detectedDateStr == null) continue; | ||
|
|
||
| if (!fraudUrlRepository.existsByUrl(urlStr)) { | ||
| LocalDate detectedDate = LocalDate.parse(detectedDateStr); | ||
| fraudUrlRepository.save(FraudUrl.builder() | ||
| .url(urlStr) | ||
| .detectedDate(detectedDate) | ||
| .lastCheckedAt(LocalDateTime.now()) | ||
| .build()); | ||
| } | ||
| } | ||
| page++; | ||
| } |
There was a problem hiding this comment.
이 부분도 리펙토링때 고려하면 좋을것 같습니다!!
💻 Related Issue
close #29
🚀 Work Description
🙇🏻♀️ To Reviewer
➕ Next
Summary by CodeRabbit
신규 기능
버그 수정
보안 및 접근성
기타