Skip to content

[Feat] URL 사기분석 API#32

Merged
chaeyuuu merged 25 commits intomainfrom
feat/#29/url-fraud-api
Jul 29, 2025
Merged

[Feat] URL 사기분석 API#32
chaeyuuu merged 25 commits intomainfrom
feat/#29/url-fraud-api

Conversation

@chaeyuuu
Copy link
Copy Markdown
Contributor

@chaeyuuu chaeyuuu commented Jul 29, 2025

💻 Related Issue

close #29


🚀 Work Description

  • 공공데이터 포탈 API로 DB에 저장
  • Google Safe Browsing 적용
image : DB에 저장되어있는 url이라면 위험한 url로 간주하여 riskLevel 을 '위험'으로 반환합니다. image : 1차적으로 DB에 저장되어있지 않은 url은 안전한 url로 간주합니다. 2차로 google safe browsing api를 호출하여 한 번 더 검증을 합니다. image +) Google Safe Browsing API는 만 건까지 호출 무료인 것 같습니다.

🙇🏻‍♀️ To Reviewer

  • 공공데이터 포탈에 있는 2023년도 버전, 2024년도 버전 둘 다 db에 반영하도록 하였습니다. 2023년도 버전은 더이상 업데이트 되지 않는 로직이라 한 번 호출하고 그 뒤로는 호출하지 않아도 될 것 같습니다. 2024년도부터 업데이트 되고 있는 버전은 1년에 1번 업데이트 되는 것으로 확인해서 별도의 스케줄링 로직 말고 api로 호출하도록 하였습니다. 현재 관리자 계정이 없긴하나 임시로 만들어놨습니다. 추후에 리팩토링하면서 수정해야할 것 같습니다.
  • 외부 호출 api나 설정이 많아지면서 패키지의 구분이 조금 모호해지고 있는 것 같은데 1차적으로 개발 마무리 된 후 구조 정리 및 전반적으로 리팩토링하면 좋을 것 같습니다.!!!! 사실 제가 하면서 어디에 둬야할지 조금 헷갈리더라구욤..,. 우선 google api와 공공데이터 포털 관련 api는 infra 패키지로 구성하였습니다.

➕ Next

  • 전화번호 사기 url api
  • 뉴스 api

Summary by CodeRabbit

  • 신규 기능

    • URL 사기 여부를 분석하는 신규 API가 추가되었습니다.
    • 관리자용 사기 URL 동기화 API가 도입되었습니다.
    • Google Safe Browsing을 활용한 URL 안전성 검사 기능이 제공됩니다.
  • 버그 수정

    • 사기 분석 API의 응답 형식이 일관성 있게 개선되었습니다.
  • 보안 및 접근성

    • 사기 URL 분석 및 동기화 관련 일부 엔드포인트가 인증 없이 접근 가능하도록 변경되었습니다.
  • 기타

    • 사기 URL 엔터티에 탐지일자 필드가 추가되었습니다.
    • 성공 및 오류 코드가 일부 추가 및 수정되었습니다.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jul 29, 2025

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 @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

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.

📥 Commits

Reviewing files that changed from the base of the PR and between 183eae6 and 7b0c1b5.

📒 Files selected for processing (7)
  • src/main/java/com/blockguard/server/domain/analysis/domain/enums/RiskLevel.java (2 hunks)
  • src/main/java/com/blockguard/server/domain/fraud/application/FraudService.java (1 hunks)
  • src/main/java/com/blockguard/server/domain/fraud/domain/FraudUrl.java (2 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/infra/google/GoogleSafeBrowsingClient.java (1 hunks)

"""

Walkthrough

이번 변경에서는 URL 사기분석 API를 위한 신규 엔드포인트, 서비스, DTO, 리포지토리, 인프라 서비스 등이 추가되었습니다. 사기 URL 동기화 및 조회, Google Safe Browsing 연동, 엔티티 및 성공/실패 코드 확장, 보안 설정 허용 경로 추가 등이 포함됩니다.

Changes

Cohort / File(s) Change Summary
Fraud URL 분석 API 신규 도입
src/main/java/com/blockguard/server/domain/fraud/api/FraudApi.java, src/main/java/com/blockguard/server/domain/fraud/application/FraudService.java, src/main/java/com/blockguard/server/domain/fraud/dao/FraudUrlRepository.java, src/main/java/com/blockguard/server/domain/fraud/domain/FraudUrl.java, src/main/java/com/blockguard/server/domain/fraud/domain/enums/RiskLevel.java, src/main/java/com/blockguard/server/domain/fraud/dto/request/FraudUrlRequest.java, src/main/java/com/blockguard/server/domain/fraud/dto/response/FraudUrlResponse.java
URL 사기분석을 위한 API, 서비스, 리포지토리, 엔티티, DTO, 위험도 enum 등 신규 파일 및 기능 추가
사기 URL 동기화 및 관리자 API
src/main/java/com/blockguard/server/infra/importer/FraudUrlImporter.java, src/main/java/com/blockguard/server/domain/admin/api/AdminApi.java
외부 Open API로부터 사기 URL을 동기화하는 서비스 및 이를 트리거하는 관리자용 엔드포인트 추가
Google Safe Browsing 연동
src/main/java/com/blockguard/server/infra/google/GoogleSafeBrowsingService.java
Google Safe Browsing API를 활용한 URL 안전성 검사 서비스 신규 도입
공통 코드 확장
src/main/java/com/blockguard/server/global/common/codes/ErrorCode.java, src/main/java/com/blockguard/server/global/common/codes/SuccessCode.java
사기 URL 관련 성공/실패 코드 신규 추가
보안 설정 확장
src/main/java/com/blockguard/server/global/config/SecurityConfig.java
신규 엔드포인트(/api/fraud/url, /api/admin/update/fraud-url)에 대한 인증 예외 허용 추가
Fraud Analysis API 반환값 수정
src/main/java/com/blockguard/server/domain/analysis/api/FraudAnalysisApi.java
기존 ResponseEntity<BaseResponse<...>> 반환에서 BaseResponse<...>로 단순화 및 시그니처 변경

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>
Loading
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>
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Assessment against linked issues

Objective Addressed Explanation
URL 사기분석 API 구현 (#29)

Assessment against linked issues: Out-of-scope changes

(해당 변경사항에서 요구사항과 무관한 기능적 코드 변경은 발견되지 않았습니다.)

Suggested reviewers

  • yeonju73

Poem

🐇
새 URL을 살피는 토끼의 눈,
위험과 안전을 나누는 분주한 분,
외부 API도 부지런히 불러와
사기 URL 차단, 오늘도 성공이야!
토끼는 깡총, 코드는 번쩍—
안전한 세상 한 걸음 더!
🦺✨
"""

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#29/url-fraud-api

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.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need 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)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_SOFTWARE
src/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

📥 Commits

Reviewing files that changed from the base of the PR and between cba8f33 and 339eaa9.

📒 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 구현을 유지해도 무방합니다.

Comment thread src/main/java/com/blockguard/server/domain/admin/api/AdminApi.java Outdated
Comment on lines +23 to +24
public BaseResponse<FraudUrlResponse> fraudUrl(@RequestBody FraudUrlRequest fraudUrlRequest){
FraudUrlResponse fraudUrlResponse = fraudService.checkFraudUrl(fraudUrlRequest);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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.

Comment on lines +26 to +27
@Column(nullable = false)
private LocalDate detectedDate;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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 디렉토리가 존재하지 않습니다."
fi

Length 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 체인지로그 작성
  • JPA 자동 DDL 사용 시
    • application.properties/application.ymlspring.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.

Comment thread src/main/java/com/blockguard/server/domain/fraud/domain/FraudUrl.java Outdated
Comment on lines +44 to +49
ResponseEntity<Map> response = restTemplate.exchange(
googleUrl,
HttpMethod.POST,
entity,
Map.class
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Comment on lines +57 to +60
} catch (Exception e) {
log.error("Google Safe Browsing API 요청 실패: {}", e.getMessage());
return false;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
} 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.

Comment on lines +44 to +84
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);
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

무한 루프 및 성능 문제가 있을 수 있습니다.

페이지네이션 처리에서 몇 가지 문제점이 있습니다:

  1. API가 항상 빈 데이터를 반환하지 않으면 무한 루프가 발생할 수 있습니다
  2. 대용량 데이터 처리 시 메모리 및 성능 문제가 발생할 수 있습니다
  3. 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.

Suggested change
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.

Comment on lines +61 to +77
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++;
}
Copy link
Copy Markdown

@coderabbitai coderabbitai bot Jul 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분도 리펙토링때 고려하면 좋을것 같습니다!!

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@yeonju73 네, 맞습니다! 트랜잭션 관리와 배치 처리 최적화는 대량 데이터를 다룰 때 매우 중요한 부분이죠. 리팩토링 시점에 함께 고려하시면 성능과 데이터 일관성 측면에서 큰 개선이 있을 것 같습니다. 👍

현재는 기능 구현에 집중하고, 추후 리팩토링에서 이런 최적화 작업들을 체계적으로 진행하시는 것이 좋은 접근 방식이라고 생각합니다!

if (urlStr == null || detectedDateStr == null) continue;

if (!fraudUrlRepository.existsByUrl(urlStr)) {
LocalDate detectedDate = LocalDate.parse(detectedDateStr);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ 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.

Suggested change
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.

Copy link
Copy Markdown
Contributor

@yeonju73 yeonju73 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다! 개발 마무리하고 패키지 리팩토링 드가봅시닷 헤헤

Comment thread src/main/java/com/blockguard/server/domain/fraud/domain/FraudUrl.java Outdated
Comment thread src/main/java/com/blockguard/server/domain/fraud/domain/enums/RiskLevel.java Outdated
registry
.requestMatchers("/api/auth/**").permitAll() // 로그인/회원가입만 허용
.requestMatchers("/api/fraud-analysis").permitAll()
.requestMatchers("/api/admin/update/fraud-url", "/api/fraud/url", "api/fraud-analysis").permitAll()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

헉 슬래시는 제가 추가하겠습니닷

Comment on lines +61 to +77
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++;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 부분도 리펙토링때 고려하면 좋을것 같습니다!!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] URL 사기분석 API

2 participants