diff --git a/.gitignore b/.gitignore index 78fef29..eb33a19 100644 --- a/.gitignore +++ b/.gitignore @@ -142,4 +142,7 @@ gradle-app.setting # Java heap dump *.hprof +# environment setting +src/main/resources/*.yml + # End of https://www.toptal.com/developers/gitignore/api/intellij+all,java,gradle diff --git a/src/main/java/community/whatever/onembackendjava/api/controller/CsvController.java b/src/main/java/community/whatever/onembackendjava/api/controller/CsvController.java new file mode 100644 index 0000000..146b962 --- /dev/null +++ b/src/main/java/community/whatever/onembackendjava/api/controller/CsvController.java @@ -0,0 +1,29 @@ +package community.whatever.onembackendjava.api.controller; + +import community.whatever.onembackendjava.api.service.CsvReaderService; +import community.whatever.onembackendjava.common.util.ResponseFormatter; +import community.whatever.onembackendjava.common.util.model.ResultJson; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/csv") +public class CsvController { + + private final CsvReaderService csvReaderService; + + + // CSV 파일을 업로드하여 차단 도메인 목록 갱신 + @PostMapping("/upload") + public ResponseEntity uploadCsv(@RequestPart(name = "blockDomains") MultipartFile file) { + log.info("file >> {}", file); + csvReaderService.loadBlockedDomains(file); + return ResponseFormatter.ConvertResponse(); + } + +} diff --git a/src/main/java/community/whatever/onembackendjava/api/domain/BlockedDomainInterface.java b/src/main/java/community/whatever/onembackendjava/api/domain/BlockedDomainInterface.java new file mode 100644 index 0000000..2f732ae --- /dev/null +++ b/src/main/java/community/whatever/onembackendjava/api/domain/BlockedDomainInterface.java @@ -0,0 +1,11 @@ +package community.whatever.onembackendjava.api.domain; + +import org.springframework.web.multipart.MultipartFile; + +import java.util.Set; + +public interface BlockedDomainInterface { + void loadBlockedDomains(MultipartFile file); // CSV 파일에서 차단 목록 로드 + Set getBlockedDomains(); // 차단된 도메인 목록 반환 + boolean isBlocked(String url); // 특정 URL이 차단된 도메인인지 확인 +} diff --git a/src/main/java/community/whatever/onembackendjava/api/service/CsvReaderService.java b/src/main/java/community/whatever/onembackendjava/api/service/CsvReaderService.java new file mode 100644 index 0000000..5c80a72 --- /dev/null +++ b/src/main/java/community/whatever/onembackendjava/api/service/CsvReaderService.java @@ -0,0 +1,57 @@ +package community.whatever.onembackendjava.api.service; + +import community.whatever.onembackendjava.api.domain.BlockedDomainInterface; +import community.whatever.onembackendjava.common.error.BusinessExceptionGenerator; +import community.whatever.onembackendjava.common.error.model.ErrorCode; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; + + +@Slf4j +@Service +public class CsvReaderService implements BlockedDomainInterface { + + private Set blockedDomains = new HashSet<>(); + + // CSV 파일을 읽어 BlockedDomains 리스트에 저장 + public void loadBlockedDomains(MultipartFile file) { + if (file.isEmpty()) throw BusinessExceptionGenerator.createBusinessException(ErrorCode.DB001); + + Set domainSet = new HashSet<>(); + try (BufferedReader br = new BufferedReader( + new InputStreamReader(file.getInputStream(), StandardCharsets.UTF_8))) { + + String line; + while ((line = br.readLine()) != null) { + domainSet.add(line.trim()); + } + } catch (Exception e) { + throw BusinessExceptionGenerator.createBusinessException("RU001", e.getMessage()); + } + + this.blockedDomains = domainSet; // 새로운 데이터로 업데이트 + } + + // 차단된 도메인 목록 반환 + public Set getBlockedDomains() { + return blockedDomains; + } + + // URL이 차단된 도메인에 포함되는지 확인 + public boolean isBlocked(String url) { + for (String domain : blockedDomains) { + if (url.contains(domain)) { + return true; + } + } + return false; + } + +} diff --git a/src/main/java/community/whatever/onembackendjava/api/service/UrlShortenService.java b/src/main/java/community/whatever/onembackendjava/api/service/UrlShortenService.java index cd41a6d..0108c3d 100644 --- a/src/main/java/community/whatever/onembackendjava/api/service/UrlShortenService.java +++ b/src/main/java/community/whatever/onembackendjava/api/service/UrlShortenService.java @@ -1,21 +1,26 @@ package community.whatever.onembackendjava.api.service; +import community.whatever.onembackendjava.api.domain.BlockedDomainInterface; import community.whatever.onembackendjava.api.dto.ShortenUrlDto; -import community.whatever.onembackendjava.common.error.BusinessException; import community.whatever.onembackendjava.common.error.BusinessExceptionGenerator; +import community.whatever.onembackendjava.common.error.model.ErrorCode; +import community.whatever.onembackendjava.common.util.BlackListService; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.stream.Stream; @Slf4j @Service +@RequiredArgsConstructor public class UrlShortenService { + private final BlockedDomainInterface blockedDomainInterface; private static final String UrlRegex = "https?://(?:www\\.)?[a-zA-Z0-9./]+"; private static final Pattern URL_PATTERN = Pattern.compile(UrlRegex); @@ -23,7 +28,7 @@ public class UrlShortenService { public ShortenUrlDto.Get.Response shortenUrlSearch(ShortenUrlDto.Get.Request param){ if (!shortenUrls.containsKey(param.getKey())) { - throw BusinessExceptionGenerator.createBusinessException("DB003"); + throw BusinessExceptionGenerator.createBusinessException(ErrorCode.DB003); } return ShortenUrlDto.Get.Response.builder() @@ -33,7 +38,10 @@ public ShortenUrlDto.Get.Response shortenUrlSearch(ShortenUrlDto.Get.Request par public ShortenUrlDto.Create.Response shortenUrlCreate(ShortenUrlDto.Create.Request param){ if (validateOriginUrl(param.getOriginUrl())) { - throw BusinessExceptionGenerator.createBusinessException("DB001"); + throw BusinessExceptionGenerator.createBusinessException(ErrorCode.DB001); + } + if(blockedDomainInterface.isBlocked(param.getOriginUrl())){ + throw BusinessExceptionGenerator.createBusinessException(ErrorCode.DB004); } Random random = new Random(); diff --git a/src/main/java/community/whatever/onembackendjava/common/error/BusinessExceptionGenerator.java b/src/main/java/community/whatever/onembackendjava/common/error/BusinessExceptionGenerator.java index 1c81490..ebdcddd 100644 --- a/src/main/java/community/whatever/onembackendjava/common/error/BusinessExceptionGenerator.java +++ b/src/main/java/community/whatever/onembackendjava/common/error/BusinessExceptionGenerator.java @@ -16,6 +16,10 @@ public BusinessExceptionGenerator(RestControllerAdvice restControllerAdvice){ this.restControllerAdvice = restControllerAdvice; } + public static BusinessException createBusinessException(ErrorCode errorCode) { + return new BusinessException(errorCode.getErrorCode(), errorCode.getMessage()); + } + public static BusinessException createBusinessException(String errorCode) { ErrorCode enumError = ErrorCode.valueOf(errorCode); return new BusinessException(enumError.getErrorCode(), enumError.getMessage()); diff --git a/src/main/java/community/whatever/onembackendjava/common/error/model/ErrorCode.java b/src/main/java/community/whatever/onembackendjava/common/error/model/ErrorCode.java index 5ccb9cc..7f0e04d 100644 --- a/src/main/java/community/whatever/onembackendjava/common/error/model/ErrorCode.java +++ b/src/main/java/community/whatever/onembackendjava/common/error/model/ErrorCode.java @@ -8,7 +8,10 @@ public enum ErrorCode { DB001("DB001","잘못된 요청입니다."), DB002("DB002","데이터를 찾을 수 없습니다."), - DB003("DB003","참조키를 찾을 수 없습니다."); + DB003("DB003","참조키를 찾을 수 없습니다."), + DB004("DB004","서비스를 이용할 수 없는 도메인입니다."), + + RU001("RU001", "csv 파일 읽기에 실패했습니다."); private final String errorCode; private final String message; diff --git a/src/main/java/community/whatever/onembackendjava/common/util/BlackListService.java b/src/main/java/community/whatever/onembackendjava/common/util/BlackListService.java new file mode 100644 index 0000000..87fd309 --- /dev/null +++ b/src/main/java/community/whatever/onembackendjava/common/util/BlackListService.java @@ -0,0 +1,23 @@ +package community.whatever.onembackendjava.common.util; + +import org.springframework.core.env.Environment; +import org.springframework.stereotype.Component; + +import java.util.Arrays; +import java.util.List; + +@Component +public class BlackListService { + private final List blockedDomains; + + public BlackListService(Environment env) { + String domains = env.getProperty("spring.blacklist.domains", ""); // 기본값 빈 문자열 + this.blockedDomains = Arrays.stream(domains.split(",")) + .filter(domain -> !domain.isEmpty()) + .toList(); + } + + public List getBlockedDomains() { + return blockedDomains; + } +} diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml new file mode 100644 index 0000000..e69de29 diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml deleted file mode 100644 index ab6a3d8..0000000 --- a/src/main/resources/application.yml +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=onem-backend