add: common/exception 로직 추가

This commit is contained in:
UNGGU0704 2025-06-12 13:44:22 +09:00
parent 72a9758866
commit 0de0eca32e
19 changed files with 778 additions and 0 deletions

View File

@ -0,0 +1,82 @@
package com.ktds.hi.common.exception;
import org.springframework.http.HttpStatus;
/**
* API 관련 예외
* HTTP 상태 코드와 함께 사용되는 예외
*/
public class ApiException extends BusinessException {
private final HttpStatus httpStatus;
public ApiException(HttpStatus httpStatus, String message) {
super(httpStatus.name(), message);
this.httpStatus = httpStatus;
}
public ApiException(HttpStatus httpStatus, String message, Throwable cause) {
super(httpStatus.name(), message, cause);
this.httpStatus = httpStatus;
}
public HttpStatus getHttpStatus() {
return httpStatus;
}
/**
* Bad Request (400)
*/
public static ApiException badRequest(String message) {
return new ApiException(HttpStatus.BAD_REQUEST, message);
}
/**
* Unauthorized (401)
*/
public static ApiException unauthorized(String message) {
return new ApiException(HttpStatus.UNAUTHORIZED, message);
}
/**
* Forbidden (403)
*/
public static ApiException forbidden(String message) {
return new ApiException(HttpStatus.FORBIDDEN, message);
}
/**
* Not Found (404)
*/
public static ApiException notFound(String message) {
return new ApiException(HttpStatus.NOT_FOUND, message);
}
/**
* Method Not Allowed (405)
*/
public static ApiException methodNotAllowed(String message) {
return new ApiException(HttpStatus.METHOD_NOT_ALLOWED, message);
}
/**
* Conflict (409)
*/
public static ApiException conflict(String message) {
return new ApiException(HttpStatus.CONFLICT, message);
}
/**
* Internal Server Error (500)
*/
public static ApiException internalServerError(String message) {
return new ApiException(HttpStatus.INTERNAL_SERVER_ERROR, message);
}
/**
* Service Unavailable (503)
*/
public static ApiException serviceUnavailable(String message) {
return new ApiException(HttpStatus.SERVICE_UNAVAILABLE, message);
}
}

View File

@ -0,0 +1,28 @@
package com.ktds.hi.common.exception;
public class AuthenticationException extends BusinessException {
public AuthenticationException(String message) {
super("AUTHENTICATION_ERROR", message);
}
public AuthenticationException(String message, Throwable cause) {
super("AUTHENTICATION_ERROR", message, cause);
}
public static AuthenticationException loginFailed() {
return new AuthenticationException("아이디 또는 비밀번호가 일치하지 않습니다");
}
public static AuthenticationException tokenExpired() {
return new AuthenticationException("토큰이 만료되었습니다");
}
public static AuthenticationException invalidToken() {
return new AuthenticationException("유효하지 않은 토큰입니다");
}
public static AuthenticationException authenticationRequired() {
return new AuthenticationException("로그인이 필요합니다");
}
}

View File

@ -0,0 +1,32 @@
package com.ktds.hi.common.exception;
public class AuthorizationException extends BusinessException {
public AuthorizationException(String message) {
super("AUTHORIZATION_ERROR", message);
}
public AuthorizationException(String message, Object... args) {
super("AUTHORIZATION_ERROR", message, args);
}
public AuthorizationException(String message, Throwable cause) {
super("AUTHORIZATION_ERROR", message, cause);
}
public static AuthorizationException accessDenied() {
return new AuthorizationException("접근 권한이 없습니다");
}
public static AuthorizationException accessDenied(String resource) {
return new AuthorizationException(resource + "에 대한 접근 권한이 없습니다");
}
public static AuthorizationException insufficientRole(String requiredRole) {
return new AuthorizationException(requiredRole + " 권한이 필요합니다");
}
public static AuthorizationException ownershipRequired() {
return new AuthorizationException("본인만 접근할 수 있습니다");
}
}

View File

@ -0,0 +1,88 @@
// common/src/main/java/com/ktds/hi/common/exception/BusinessException.java
package com.ktds.hi.common.exception;
/**
* 비즈니스 로직 예외의 기본 클래스
* 모든 커스텀 예외의 부모 클래스
*/
public class BusinessException extends RuntimeException {
private String errorCode;
private Object[] args;
/**
* 메시지만으로 예외 생성
*/
public BusinessException(String message) {
super(message);
this.errorCode = "BUSINESS_ERROR";
}
/**
* 메시지와 원인으로 예외 생성
*/
public BusinessException(String message, Throwable cause) {
super(message, cause);
this.errorCode = "BUSINESS_ERROR";
}
/**
* 에러 코드와 메시지로 예외 생성
*/
public BusinessException(String errorCode, String message) {
super(message);
this.errorCode = errorCode;
}
/**
* 에러 코드, 메시지, 파라미터로 예외 생성
*/
public BusinessException(String errorCode, String message, Object... args) {
super(message);
this.errorCode = errorCode;
this.args = args;
}
/**
* 에러 코드, 메시지, 원인으로 예외 생성
*/
public BusinessException(String errorCode, String message, Throwable cause) {
super(message, cause);
this.errorCode = errorCode;
}
/**
* 에러 코드 반환
*/
public String getErrorCode() {
return errorCode;
}
/**
* 메시지 파라미터 반환
*/
public Object[] getArgs() {
return args;
}
/**
* 정적 팩토리 메서드 - 일반적인 비즈니스 예외
*/
public static BusinessException of(String message) {
return new BusinessException(message);
}
/**
* 정적 팩토리 메서드 - 에러 코드와 메시지
*/
public static BusinessException of(String errorCode, String message) {
return new BusinessException(errorCode, message);
}
/**
* 정적 팩토리 메서드 - 원인 포함
*/
public static BusinessException of(String message, Throwable cause) {
return new BusinessException(message, cause);
}
}

View File

@ -0,0 +1,32 @@
package com.ktds.hi.common.exception;
public class BusinessRuleException extends BusinessException {
public BusinessRuleException(String message) {
super("BUSINESS_RULE_VIOLATION", message);
}
public BusinessRuleException(String message, Object... args) {
super("BUSINESS_RULE_VIOLATION", message, args);
}
public BusinessRuleException(String message, Throwable cause) {
super("BUSINESS_RULE_VIOLATION", message, cause);
}
public static BusinessRuleException invalidStateTransition(String from, String to) {
return new BusinessRuleException(String.format("%s 상태에서 %s 상태로 변경할 수 없습니다", from, to));
}
public static BusinessRuleException unauthorizedOperation(String operation) {
return new BusinessRuleException(operation + " 작업을 수행할 권한이 없습니다");
}
public static BusinessRuleException timeConstraintViolation(String operation) {
return new BusinessRuleException(operation + "의 시간 제한을 위반했습니다");
}
public static BusinessRuleException duplicateOperation(String operation) {
return new BusinessRuleException("이미 " + operation + "을(를) 수행했습니다");
}
}

View File

@ -0,0 +1,37 @@
package com.ktds.hi.common.exception;
/**
* 동시성 관련 예외
* 동시 접근, 충돌 등에서 발생하는 예외
*/
public class ConcurrencyException extends BusinessException {
public ConcurrencyException(String message) {
super("CONCURRENCY_ERROR", message);
}
public ConcurrencyException(String message, Throwable cause) {
super("CONCURRENCY_ERROR", message, cause);
}
/**
* 낙관적 충돌
*/
public static ConcurrencyException optimisticLockConflict() {
return new ConcurrencyException("동시 수정으로 인한 충돌이 발생했습니다. 다시 시도해주세요");
}
/**
* 비관적 타임아웃
*/
public static ConcurrencyException lockTimeout() {
return new ConcurrencyException("리소스 잠금 시간이 초과되었습니다");
}
/**
* 데드락 발생
*/
public static ConcurrencyException deadlockDetected() {
return new ConcurrencyException("데드락이 감지되었습니다");
}
}

View File

@ -0,0 +1,37 @@
package com.ktds.hi.common.exception;
/**
* 설정 관련 예외
* 애플리케이션 설정 오류, 환경 변수 누락 등에서 발생하는 예외
*/
public class ConfigurationException extends BusinessException {
public ConfigurationException(String message) {
super("CONFIGURATION_ERROR", message);
}
public ConfigurationException(String message, Throwable cause) {
super("CONFIGURATION_ERROR", message, cause);
}
/**
* 필수 설정 누락
*/
public static ConfigurationException missingProperty(String propertyName) {
return new ConfigurationException("필수 설정이 누락되었습니다: " + propertyName);
}
/**
* 잘못된 설정
*/
public static ConfigurationException invalidValue(String propertyName, String value) {
return new ConfigurationException(String.format("잘못된 설정 값입니다. %s: %s", propertyName, value));
}
/**
* 설정 파일 로드 실패
*/
public static ConfigurationException loadFailed(String configFile) {
return new ConfigurationException("설정 파일 로드에 실패했습니다: " + configFile);
}
}

View File

@ -0,0 +1,37 @@
package com.ktds.hi.common.exception;
/**
* 데이터 접근 관련 예외
* 데이터베이스, 캐시 데이터 저장소 접근 발생하는 예외
*/
public class DataAccessException extends BusinessException {
public DataAccessException(String message) {
super("DATA_ACCESS_ERROR", message);
}
public DataAccessException(String message, Throwable cause) {
super("DATA_ACCESS_ERROR", message, cause);
}
/**
* 데이터베이스 연결 실패
*/
public static DataAccessException connectionFailed() {
return new DataAccessException("데이터베이스 연결에 실패했습니다");
}
/**
* 데이터 정합성 오류
*/
public static DataAccessException integrityViolation(String detail) {
return new DataAccessException("데이터 정합성 오류: " + detail);
}
/**
* 캐시 접근 실패
*/
public static DataAccessException cacheAccessFailed() {
return new DataAccessException("캐시 접근에 실패했습니다");
}
}

View File

@ -0,0 +1,24 @@
package com.ktds.hi.common.exception;
public class DataIntegrityException extends BusinessException {
public DataIntegrityException(String message) {
super("DATA_INTEGRITY_ERROR", message);
}
public DataIntegrityException(String message, Throwable cause) {
super("DATA_INTEGRITY_ERROR", message, cause);
}
public static DataIntegrityException foreignKeyViolation(String entity, Object id) {
return new DataIntegrityException(String.format("%s(ID: %s)에 참조된 데이터가 있어 삭제할 수 없습니다", entity, id));
}
public static DataIntegrityException uniqueConstraintViolation(String field, Object value) {
return new DataIntegrityException(String.format("중복된 값입니다. %s: %s", field, value));
}
public static DataIntegrityException notNullViolation(String field) {
return new DataIntegrityException(field + "은(는) 필수 값입니다");
}
}

View File

@ -0,0 +1,36 @@
package com.ktds.hi.common.exception;
public class DuplicateResourceException extends BusinessException {
public DuplicateResourceException(String resourceType, String field, Object value) {
super("DUPLICATE_RESOURCE", String.format("이미 존재하는 %s입니다. %s: %s", resourceType, field, value));
}
public DuplicateResourceException(String message) {
super("DUPLICATE_RESOURCE", message);
}
public DuplicateResourceException(String message, Throwable cause) {
super("DUPLICATE_RESOURCE", message, cause);
}
public static DuplicateResourceException username(String username) {
return new DuplicateResourceException("사용자", "사용자명", username);
}
public static DuplicateResourceException email(String email) {
return new DuplicateResourceException("사용자", "이메일", email);
}
public static DuplicateResourceException nickname(String nickname) {
return new DuplicateResourceException("사용자", "닉네임", nickname);
}
public static DuplicateResourceException phone(String phone) {
return new DuplicateResourceException("사용자", "전화번호", phone);
}
public static DuplicateResourceException storeName(String storeName) {
return new DuplicateResourceException("매장", "매장명", storeName);
}
}

View File

@ -0,0 +1,36 @@
package com.ktds.hi.common.exception;
public class ExternalServiceException extends BusinessException {
private final String serviceName;
public ExternalServiceException(String serviceName, String message) {
super("EXTERNAL_SERVICE_ERROR", String.format("%s 서비스 오류: %s", serviceName, message));
this.serviceName = serviceName;
}
public ExternalServiceException(String serviceName, String message, Throwable cause) {
super("EXTERNAL_SERVICE_ERROR", String.format("%s 서비스 오류: %s", serviceName, message), cause);
this.serviceName = serviceName;
}
public String getServiceName() {
return serviceName;
}
public static ExternalServiceException apiCallFailed(String serviceName, String endpoint) {
return new ExternalServiceException(serviceName, endpoint + " API 호출에 실패했습니다");
}
public static ExternalServiceException serviceUnavailable(String serviceName) {
return new ExternalServiceException(serviceName, "서비스를 사용할 수 없습니다");
}
public static ExternalServiceException timeout(String serviceName) {
return new ExternalServiceException(serviceName, "응답 시간이 초과되었습니다");
}
public static ExternalServiceException authenticationFailed(String serviceName) {
return new ExternalServiceException(serviceName, "인증에 실패했습니다");
}
}

View File

@ -0,0 +1,44 @@
package com.ktds.hi.common.exception;
/**
* 파일 처리 관련 예외
* 파일 업로드, 다운로드, 변환 등에서 발생하는 예외
*/
public class FileProcessingException extends BusinessException {
public FileProcessingException(String message) {
super("FILE_PROCESSING_ERROR", message);
}
public FileProcessingException(String message, Throwable cause) {
super("FILE_PROCESSING_ERROR", message, cause);
}
/**
* 지원하지 않는 파일 형식
*/
public static FileProcessingException unsupportedFileType(String fileType) {
return new FileProcessingException("지원하지 않는 파일 형식입니다: " + fileType);
}
/**
* 파일 크기 초과
*/
public static FileProcessingException fileSizeExceeded(long maxSize) {
return new FileProcessingException("파일 크기가 제한을 초과했습니다. 최대 크기: " + maxSize + "bytes");
}
/**
* 파일 업로드 실패
*/
public static FileProcessingException uploadFailed() {
return new FileProcessingException("파일 업로드에 실패했습니다");
}
/**
* 파일 변환 실패
*/
public static FileProcessingException conversionFailed(String fromType, String toType) {
return new FileProcessingException(String.format("파일 변환에 실패했습니다: %s -> %s", fromType, toType));
}
}

View File

@ -0,0 +1,37 @@
package com.ktds.hi.common.exception;
/**
* JWT 처리 관련 예외
* JWT 파싱, 서명 검증 등에서 발생하는 예외
*/
public class JwtException extends TokenException {
public JwtException(String message) {
super(message);
}
public JwtException(String message, Throwable cause) {
super(message, cause);
}
/**
* JWT 서명 검증 실패
*/
public static JwtException signatureVerificationFailed() {
return new JwtException("JWT 서명 검증에 실패했습니다");
}
/**
* JWT 파싱 실패
*/
public static JwtException parsingFailed() {
return new JwtException("JWT 파싱에 실패했습니다");
}
/**
* JWT 클레임 누락
*/
public static JwtException missingClaim(String claimName) {
return new JwtException("필수 클레임이 누락되었습니다: " + claimName);
}
}

View File

@ -0,0 +1,37 @@
package com.ktds.hi.common.exception;
/**
* 요청 제한 관련 예외
* API 호출 제한, 처리량 제한 등에서 발생하는 예외
*/
public class RateLimitException extends BusinessException {
private final long retryAfter; // 재시도 가능 시간 ()
public RateLimitException(String message, long retryAfter) {
super("RATE_LIMIT_EXCEEDED", message);
this.retryAfter = retryAfter;
}
public RateLimitException(String message) {
this(message, 60); // 기본값: 60초 재시도
}
public long getRetryAfter() {
return retryAfter;
}
/**
* API 호출 제한 초과
*/
public static RateLimitException apiCallLimitExceeded(long retryAfter) {
return new RateLimitException("API 호출 제한을 초과했습니다. 잠시 후 다시 시도해주세요", retryAfter);
}
/**
* 동시 요청 제한 초과
*/
public static RateLimitException concurrentRequestLimitExceeded() {
return new RateLimitException("동시 요청 제한을 초과했습니다", 5);
}
}

View File

@ -0,0 +1,40 @@
package com.ktds.hi.common.exception;
public class ResourceNotFoundException extends BusinessException {
public ResourceNotFoundException(String resourceType, Object id) {
super("RESOURCE_NOT_FOUND", String.format("%s를 찾을 수 없습니다. ID: %s", resourceType, id));
}
public ResourceNotFoundException(String message) {
super("RESOURCE_NOT_FOUND", message);
}
public ResourceNotFoundException(String resourceType, String field, Object value) {
super("RESOURCE_NOT_FOUND", String.format("%s를 찾을 수 없습니다. %s: %s", resourceType, field, value));
}
public ResourceNotFoundException(String message, Throwable cause) {
super("RESOURCE_NOT_FOUND", message, cause);
}
public static ResourceNotFoundException byId(String resourceType, Object id) {
return new ResourceNotFoundException(resourceType, id);
}
public static ResourceNotFoundException user(Long userId) {
return new ResourceNotFoundException("사용자", userId);
}
public static ResourceNotFoundException store(Long storeId) {
return new ResourceNotFoundException("매장", storeId);
}
public static ResourceNotFoundException review(Long reviewId) {
return new ResourceNotFoundException("리뷰", reviewId);
}
public static ResourceNotFoundException userByUsername(String username) {
return new ResourceNotFoundException("사용자", "사용자명", username);
}
}

View File

@ -0,0 +1,37 @@
package com.ktds.hi.common.exception;
/**
* 보안 관련 예외
* 인증, 인가 외의 보안 관련 예외
*/
public class SecurityException extends BusinessException {
public SecurityException(String message) {
super("SECURITY_ERROR", message);
}
public SecurityException(String message, Throwable cause) {
super("SECURITY_ERROR", message, cause);
}
/**
* 보안 정책 위반
*/
public static SecurityException policyViolation(String policy) {
return new SecurityException("보안 정책 위반: " + policy);
}
/**
* 의심스러운 활동 감지
*/
public static SecurityException suspiciousActivity(String activity) {
return new SecurityException("의심스러운 활동이 감지되었습니다: " + activity);
}
/**
* IP 차단
*/
public static SecurityException ipBlocked(String ip) {
return new SecurityException("차단된 IP 주소입니다: " + ip);
}
}

View File

@ -0,0 +1,34 @@
package com.ktds.hi.common.exception;
/**
* 서비스 레이어 예외
* 비즈니스 로직 실행 발생하는 일반적인 예외
*/
public class ServiceException extends BusinessException {
public ServiceException(String message) {
super("SERVICE_ERROR", message);
}
public ServiceException(String message, Throwable cause) {
super("SERVICE_ERROR", message, cause);
}
public ServiceException(String errorCode, String message) {
super(errorCode, message);
}
/**
* 서비스 일시 중단
*/
public static ServiceException temporarilyUnavailable(String serviceName) {
return new ServiceException("서비스가 일시적으로 이용할 수 없습니다: " + serviceName);
}
/**
* 처리 한도 초과
*/
public static ServiceException limitExceeded(String limitType) {
return new ServiceException("처리 한도를 초과했습니다: " + limitType);
}
}

View File

@ -0,0 +1,44 @@
package com.ktds.hi.common.exception;
/**
* JWT 토큰 관련 예외
* 토큰 만료, 유효하지 않은 토큰 등에 사용
*/
public class TokenException extends BusinessException {
public TokenException(String message) {
super("TOKEN_ERROR", message);
}
public TokenException(String message, Throwable cause) {
super("TOKEN_ERROR", message, cause);
}
/**
* 토큰 만료 예외
*/
public static TokenException expired() {
return new TokenException("토큰이 만료되었습니다");
}
/**
* 토큰 형식 오류 예외
*/
public static TokenException invalid() {
return new TokenException("유효하지 않은 토큰입니다");
}
/**
* 토큰 누락 예외
*/
public static TokenException missing() {
return new TokenException("토큰이 필요합니다");
}
/**
* 잘못된 토큰 타입 예외
*/
public static TokenException wrongType(String expectedType) {
return new TokenException(expectedType + " 토큰이 필요합니다");
}
}

View File

@ -0,0 +1,36 @@
package com.ktds.hi.common.exception;
public class ValidationException extends BusinessException {
public ValidationException(String message) {
super("VALIDATION_ERROR", message);
}
public ValidationException(String message, Object... args) {
super("VALIDATION_ERROR", message, args);
}
public ValidationException(String field, String message) {
super("VALIDATION_ERROR", String.format("%s: %s", field, message));
}
public ValidationException(String message, Throwable cause) {
super("VALIDATION_ERROR", message, cause);
}
public static ValidationException requiredField(String fieldName) {
return new ValidationException(fieldName + "은(는) 필수 입력 항목입니다");
}
public static ValidationException invalidFormat(String fieldName) {
return new ValidationException(fieldName + "의 형식이 올바르지 않습니다");
}
public static ValidationException outOfRange(String fieldName, Object min, Object max) {
return new ValidationException(String.format("%s는 %s와 %s 사이의 값이어야 합니다", fieldName, min, max));
}
public static ValidationException lengthExceeded(String fieldName, int maxLength) {
return new ValidationException(String.format("%s는 %d자 이하로 입력해주세요", fieldName, maxLength));
}
}