Skip to content

Commit a6c0aee

Browse files
authored
Merge pull request #32 from Block-Guard/feat/#29/url-fraud-api
[Feat] URL 사기분석 API
2 parents cba8f33 + 7b0c1b5 commit a6c0aee

File tree

14 files changed

+312
-10
lines changed

14 files changed

+312
-10
lines changed
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.blockguard.server.domain.admin.api;
2+
3+
import com.blockguard.server.global.common.codes.SuccessCode;
4+
import com.blockguard.server.global.common.response.BaseResponse;
5+
import com.blockguard.server.infra.importer.FraudUrlImporter;
6+
import io.swagger.v3.oas.annotations.Operation;
7+
import lombok.AllArgsConstructor;
8+
import org.springframework.web.bind.annotation.PostMapping;
9+
import org.springframework.web.bind.annotation.RequestMapping;
10+
import org.springframework.web.bind.annotation.RestController;
11+
12+
@RestController
13+
@AllArgsConstructor
14+
@RequestMapping("/api/admin")
15+
public class AdminApi {
16+
17+
private final FraudUrlImporter fraudUrlImporter;
18+
19+
@PostMapping("/update/fraud-url")
20+
@Operation(summary = "공공 api 데이터 호출 - 관리자용")
21+
public BaseResponse<Void> syncFraudUrls(){
22+
fraudUrlImporter.syncFraudUrlsFromOpenApi();
23+
return BaseResponse.of(SuccessCode.IMPORT_OPEN_API_SUCCESS);
24+
}
25+
}

src/main/java/com/blockguard/server/domain/analysis/api/FraudAnalysisApi.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,18 @@ public class FraudAnalysisApi {
2828

2929
@PostMapping(value = "/fraud-analysis", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
3030
@Operation(summary = "사기 분석")
31-
public ResponseEntity<BaseResponse<FraudAnalysisResponse>> analyzeFraud
31+
public BaseResponse<FraudAnalysisResponse> analyzeFraud
3232
(@RequestParam("fraudAnalysisRequest") String jsonStr,
3333
@RequestParam(value = "imageFiles", required = false) List<MultipartFile> imageFiles
3434
) throws JsonProcessingException {
3535

36+
// TODO: 이미지 파일 개수 픽스 필요
3637
if (imageFiles != null && imageFiles.size() > 2) {
3738
throw new BusinessExceptionHandler(ErrorCode.IMAGE_UPLOAD_LIMIT_EXCEEDED);
3839
}
3940

4041
FraudAnalysisRequest request = objectMapper.readValue(jsonStr, FraudAnalysisRequest.class);
4142
FraudAnalysisResponse fraudAnalysisResponse = fraudAnalysisService.fraudAnalysis(request, imageFiles);
42-
return ResponseEntity.ok(BaseResponse.of(SuccessCode.ANALYZE_FRAUD_SUCCESS, fraudAnalysisResponse));
43+
return BaseResponse.of(SuccessCode.ANALYZE_FRAUD_SUCCESS, fraudAnalysisResponse);
4344
}
4445
}

src/main/java/com/blockguard/server/domain/analysis/domain/enums/RiskLevel.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
11
package com.blockguard.server.domain.analysis.domain.enums;
22

33
import lombok.Getter;
4-
4+
import com.fasterxml.jackson.annotation.JsonValue;
5+
6+
import lombok.AllArgsConstructor;
7+
8+
@AllArgsConstructor
59
@Getter
610
public enum RiskLevel {
711
Dangers("위험"),
812
Caution("주의"),
913
Safety("안전");
1014

1115
private final String name;
16+
private final String value;
17+
18+
@JsonValue
19+
public String getValue(){
20+
return value;
21+
}
1222

1323
RiskLevel(String name) {
1424
this.name = name;
@@ -23,5 +33,4 @@ public static RiskLevel fromScore(double score) {
2333
return Dangers;
2434
}
2535
}
26-
27-
}
36+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.blockguard.server.domain.fraud.api;
2+
3+
import com.blockguard.server.domain.fraud.application.FraudService;
4+
import com.blockguard.server.domain.fraud.dto.request.FraudUrlRequest;
5+
import com.blockguard.server.domain.fraud.dto.response.FraudUrlResponse;
6+
import com.blockguard.server.global.common.codes.SuccessCode;
7+
import com.blockguard.server.global.common.response.BaseResponse;
8+
import io.swagger.v3.oas.annotations.Operation;
9+
import jakarta.validation.Valid;
10+
import lombok.AllArgsConstructor;
11+
import org.springframework.web.bind.annotation.PostMapping;
12+
import org.springframework.web.bind.annotation.RequestBody;
13+
import org.springframework.web.bind.annotation.RequestMapping;
14+
import org.springframework.web.bind.annotation.RestController;
15+
16+
@RestController
17+
@AllArgsConstructor
18+
@RequestMapping("/api/fraud")
19+
public class FraudApi {
20+
private final FraudService fraudService;
21+
22+
@PostMapping("/url")
23+
@Operation(summary = "입력된 url 사기 분석")
24+
public BaseResponse<FraudUrlResponse> fraudUrl(@Valid @RequestBody FraudUrlRequest fraudUrlRequest){
25+
FraudUrlResponse fraudUrlResponse = fraudService.checkFraudUrl(fraudUrlRequest);
26+
return BaseResponse.of(SuccessCode.CHECK_URL_FRAUD_SUCCESS, fraudUrlResponse);
27+
}
28+
29+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
package com.blockguard.server.domain.fraud.application;
2+
3+
import com.blockguard.server.domain.analysis.domain.enums.RiskLevel;
4+
import com.blockguard.server.domain.fraud.dao.FraudUrlRepository;
5+
import com.blockguard.server.domain.fraud.dto.request.FraudUrlRequest;
6+
import com.blockguard.server.domain.fraud.dto.response.FraudUrlResponse;
7+
import com.blockguard.server.global.common.codes.ErrorCode;
8+
import com.blockguard.server.global.exception.BusinessExceptionHandler;
9+
import com.blockguard.server.infra.google.GoogleSafeBrowsingClient;
10+
import lombok.AllArgsConstructor;
11+
import org.springframework.stereotype.Service;
12+
13+
@Service
14+
@AllArgsConstructor
15+
public class FraudService {
16+
private final FraudUrlRepository fraudUrlRepository;
17+
private final GoogleSafeBrowsingClient googleSafeBrowsingService;
18+
19+
public FraudUrlResponse checkFraudUrl(FraudUrlRequest fraudUrlRequest) {
20+
String url = fraudUrlRequest.getUrl();
21+
22+
if (url == null || url.trim().isEmpty()){
23+
throw new BusinessExceptionHandler(ErrorCode.URL_REQUIRED);
24+
}
25+
26+
// 1차: DB 검사
27+
if(fraudUrlRepository.existsByUrl(url)){
28+
return FraudUrlResponse.builder()
29+
.riskLevel(RiskLevel.Dangers)
30+
.build();
31+
}
32+
33+
// 2차: Google Safe Browsing API 호출
34+
boolean isSafe = googleSafeBrowsingService.isUrlSafe(url);
35+
if (!isSafe){
36+
return FraudUrlResponse.builder()
37+
.riskLevel(RiskLevel.Dangers)
38+
.build();
39+
}
40+
41+
return FraudUrlResponse.builder()
42+
.riskLevel(RiskLevel.Safety)
43+
.build();
44+
}
45+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.blockguard.server.domain.fraud.dao;
2+
3+
import com.blockguard.server.domain.fraud.domain.FraudUrl;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
import java.util.Optional;
7+
8+
public interface FraudUrlRepository extends JpaRepository<FraudUrl, Long> {
9+
boolean existsByUrl(String url);
10+
Optional<FraudUrl> findByUrl(String url);
11+
}

src/main/java/com/blockguard/server/domain/fraud/domain/FraudUrl.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import jakarta.persistence.*;
55
import lombok.*;
66

7+
import java.time.LocalDate;
78
import java.time.LocalDateTime;
89

910
@Entity
@@ -22,9 +23,8 @@ public class FraudUrl extends BaseEntity {
2223
@Column(nullable = false, columnDefinition = "TEXT", unique = true)
2324
private String url;
2425

25-
// Todo: Open API 제공자 추후 enum type 변경
26-
@Column(nullable = false, length = 100)
27-
private String provider;
26+
@Column(nullable = false)
27+
private LocalDate detectedDate;
2828

2929
@Column(name = "last_checked_at", nullable = false)
3030
private LocalDateTime lastCheckedAt;
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.blockguard.server.domain.fraud.dto.request;
2+
3+
import jakarta.validation.constraints.NotBlank;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Getter;
6+
7+
@Getter
8+
@AllArgsConstructor
9+
public class FraudUrlRequest {
10+
@NotBlank
11+
private String url;
12+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.blockguard.server.domain.fraud.dto.response;
2+
3+
import com.blockguard.server.domain.analysis.domain.enums.RiskLevel;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
@Getter
8+
@Builder
9+
public class FraudUrlResponse {
10+
private RiskLevel riskLevel;
11+
}

src/main/java/com/blockguard/server/global/common/codes/ErrorCode.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ public enum ErrorCode {
2727
DUPLICATE_GUARDIAN_NAME(HttpStatus.BAD_REQUEST, 4024, "이미 등록된 보호자 이름입니다."),
2828
INVALID_EMAIL_TYPE(HttpStatus.BAD_REQUEST, 4025, "이메일 형식이 올바르지 않습니다."),
2929
IMAGE_UPLOAD_LIMIT_EXCEEDED(HttpStatus.BAD_REQUEST, 4026, "이미지는 최대 2장까지만 업로드 가능합니다."),
30+
FAIL_IMPORT_OPEN_API(HttpStatus.BAD_REQUEST, 4027, "OPEN API 호출에 실패하였습니다."),
31+
URL_REQUIRED(HttpStatus.BAD_REQUEST, 4028, "URL은 필수 입력 값입니다."),
3032

3133
// 5000~ : server error
3234
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 5000, "서버 오류가 발생했습니다."),

0 commit comments

Comments
 (0)