feat : initial commit
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
bootJar {
|
||||
enabled = false
|
||||
}
|
||||
|
||||
jar {
|
||||
enabled = true
|
||||
archiveClassifier = ''
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
// common/src/main/java/com/healthsync/common/config/JpaAuditingConfig.java
|
||||
package com.healthsync.common.config;
|
||||
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
|
||||
|
||||
/**
|
||||
* JPA Auditing 설정을 활성화하는 클래스입니다.
|
||||
* BaseEntity의 @CreatedDate, @LastModifiedDate 어노테이션이 동작하도록 합니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
@Configuration
|
||||
@EnableJpaAuditing
|
||||
public class JpaAuditingConfig {
|
||||
// JPA Auditing 기능을 활성화합니다.
|
||||
// BaseEntity의 생성일시, 수정일시가 자동으로 설정됩니다.
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.healthsync.common.config;
|
||||
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.redis.connection.RedisConnectionFactory;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
|
||||
import org.springframework.data.redis.serializer.StringRedisSerializer;
|
||||
|
||||
/**
|
||||
* Redis 설정을 관리하는 클래스입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
@Configuration
|
||||
public class RedisConfig {
|
||||
|
||||
/**
|
||||
* RedisTemplate 빈을 생성합니다.
|
||||
* JSON 직렬화를 통해 객체 저장을 지원합니다.
|
||||
*
|
||||
* @param connectionFactory Redis 연결 팩토리
|
||||
* @return RedisTemplate<String, Object>
|
||||
*/
|
||||
@Bean
|
||||
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
|
||||
RedisTemplate<String, Object> template = new RedisTemplate<>();
|
||||
template.setConnectionFactory(connectionFactory);
|
||||
|
||||
// Key는 String 직렬화
|
||||
template.setKeySerializer(new StringRedisSerializer());
|
||||
template.setHashKeySerializer(new StringRedisSerializer());
|
||||
|
||||
// Value는 JSON 직렬화
|
||||
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
|
||||
|
||||
return template;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package com.healthsync.common.config;
|
||||
|
||||
import io.swagger.v3.oas.models.OpenAPI;
|
||||
import io.swagger.v3.oas.models.info.Info;
|
||||
import io.swagger.v3.oas.models.security.SecurityRequirement;
|
||||
import io.swagger.v3.oas.models.security.SecurityScheme;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Swagger API 문서화 설정을 관리하는 클래스입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
@Configuration
|
||||
public class SwaggerConfig {
|
||||
|
||||
/**
|
||||
* OpenAPI 설정을 생성합니다.
|
||||
* JWT 인증을 포함한 API 문서를 제공합니다.
|
||||
*
|
||||
* @return OpenAPI
|
||||
*/
|
||||
@Bean
|
||||
public OpenAPI openAPI() {
|
||||
return new OpenAPI()
|
||||
.info(new Info()
|
||||
.title("HealthSync API")
|
||||
.description("AI 기반 개인형 맞춤 건강관리 서비스 API")
|
||||
.version("1.0.0"))
|
||||
.addSecurityItem(new SecurityRequirement().addList("Bearer Authentication"))
|
||||
.components(new io.swagger.v3.oas.models.Components()
|
||||
.addSecuritySchemes("Bearer Authentication",
|
||||
new SecurityScheme()
|
||||
.type(SecurityScheme.Type.HTTP)
|
||||
.scheme("bearer")
|
||||
.bearerFormat("JWT")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
package com.healthsync.common.constants;
|
||||
|
||||
/**
|
||||
* 시스템에서 사용되는 에러 코드 상수 클래스입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
public class ErrorCode {
|
||||
|
||||
// 인증 관련
|
||||
public static final String AUTHENTICATION_FAILED = "AUTH_001";
|
||||
public static final String INVALID_TOKEN = "AUTH_002";
|
||||
public static final String TOKEN_EXPIRED = "AUTH_003";
|
||||
|
||||
// 사용자 관련
|
||||
public static final String USER_NOT_FOUND = "USER_001";
|
||||
public static final String USER_ALREADY_EXISTS = "USER_002";
|
||||
public static final String INVALID_USER_DATA = "USER_003";
|
||||
|
||||
// 건강 데이터 관련
|
||||
public static final String HEALTH_DATA_NOT_FOUND = "HEALTH_001";
|
||||
public static final String HEALTH_SYNC_FAILED = "HEALTH_002";
|
||||
public static final String FILE_UPLOAD_FAILED = "HEALTH_003";
|
||||
|
||||
// AI 관련
|
||||
public static final String AI_SERVICE_UNAVAILABLE = "AI_001";
|
||||
public static final String AI_ANALYSIS_FAILED = "AI_002";
|
||||
public static final String CLAUDE_API_ERROR = "AI_003";
|
||||
|
||||
// 목표 관련
|
||||
public static final String GOAL_NOT_FOUND = "GOAL_001";
|
||||
public static final String MISSION_NOT_FOUND = "GOAL_002";
|
||||
public static final String INVALID_MISSION_STATUS = "GOAL_003";
|
||||
|
||||
// 외부 서비스 관련
|
||||
public static final String EXTERNAL_SERVICE_ERROR = "EXT_001";
|
||||
public static final String GOOGLE_OAUTH_ERROR = "EXT_002";
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.healthsync.common.constants;
|
||||
|
||||
/**
|
||||
* 성공 응답 메시지 상수 클래스입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
public class SuccessCode {
|
||||
|
||||
// 인증 관련
|
||||
public static final String LOGIN_SUCCESS = "로그인이 완료되었습니다.";
|
||||
public static final String LOGOUT_SUCCESS = "로그아웃이 완료되었습니다.";
|
||||
|
||||
// 사용자 관련
|
||||
public static final String USER_REGISTRATION_SUCCESS = "회원가입이 완료되었습니다.";
|
||||
public static final String PROFILE_UPDATE_SUCCESS = "프로필이 업데이트되었습니다.";
|
||||
|
||||
// 건강 데이터 관련
|
||||
public static final String HEALTH_SYNC_SUCCESS = "건강검진 데이터 연동이 완료되었습니다.";
|
||||
public static final String FILE_UPLOAD_SUCCESS = "파일 업로드가 완료되었습니다.";
|
||||
|
||||
// 목표 관련
|
||||
public static final String MISSION_COMPLETE_SUCCESS = "미션이 완료되었습니다.";
|
||||
public static final String GOAL_SETUP_SUCCESS = "목표 설정이 완료되었습니다.";
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// common/src/main/java/com/healthsync/common/dto/ApiResponse.java
|
||||
package com.healthsync.common.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* 공통 API 응답 DTO 클래스입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ApiResponse<T> {
|
||||
|
||||
private int status;
|
||||
private String message;
|
||||
private T data;
|
||||
|
||||
@Builder.Default
|
||||
private String timestamp = LocalDateTime.now().toString();
|
||||
|
||||
private String traceId;
|
||||
|
||||
/**
|
||||
* 성공 응답을 생성합니다.
|
||||
*
|
||||
* @param data 응답 데이터
|
||||
* @return ApiResponse 인스턴스
|
||||
*/
|
||||
public static <T> ApiResponse<T> success(T data) {
|
||||
return ApiResponse.<T>builder()
|
||||
.status(200)
|
||||
.message("SUCCESS")
|
||||
.data(data)
|
||||
.timestamp(LocalDateTime.now().toString())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 성공 응답을 생성합니다.
|
||||
*
|
||||
* @param message 성공 메시지
|
||||
* @param data 응답 데이터
|
||||
* @return ApiResponse 인스턴스
|
||||
*/
|
||||
public static <T> ApiResponse<T> success(String message, T data) {
|
||||
return ApiResponse.<T>builder()
|
||||
.status(200)
|
||||
.message(message)
|
||||
.data(data)
|
||||
.timestamp(LocalDateTime.now().toString())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 에러 응답을 생성합니다.
|
||||
*
|
||||
* @param message 에러 메시지
|
||||
* @return ApiResponse 인스턴스
|
||||
*/
|
||||
public static <T> ApiResponse<T> error(String message) {
|
||||
return ApiResponse.<T>builder()
|
||||
.status(500)
|
||||
.message(message)
|
||||
.data(null)
|
||||
.timestamp(LocalDateTime.now().toString())
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 성공 여부를 반환합니다.
|
||||
*
|
||||
* @return 성공 여부
|
||||
*/
|
||||
public boolean isSuccess() {
|
||||
return status >= 200 && status < 300;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// common/src/main/java/com/healthsync/common/dto/ErrorResponse.java
|
||||
package com.healthsync.common.dto;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* API 에러 응답 DTO 클래스입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
@Data
|
||||
@Builder
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ErrorResponse {
|
||||
|
||||
private String code;
|
||||
private String message;
|
||||
|
||||
@Builder.Default
|
||||
private String timestamp = LocalDateTime.now().toString();
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// common/src/main/java/com/healthsync/common/entity/BaseEntity.java
|
||||
package com.healthsync.common.entity;
|
||||
|
||||
import jakarta.persistence.*;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import org.springframework.data.annotation.CreatedDate;
|
||||
import org.springframework.data.annotation.LastModifiedDate;
|
||||
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
|
||||
|
||||
import java.time.LocalDateTime;
|
||||
|
||||
/**
|
||||
* JPA 엔티티의 공통 필드를 정의하는 기본 클래스입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@MappedSuperclass
|
||||
@EntityListeners(AuditingEntityListener.class)
|
||||
public abstract class BaseEntity {
|
||||
|
||||
/**
|
||||
* 생성 일시
|
||||
*/
|
||||
@CreatedDate
|
||||
@Column(name = "created_at", nullable = false, updatable = false)
|
||||
private LocalDateTime createdAt;
|
||||
|
||||
/**
|
||||
* 수정 일시
|
||||
*/
|
||||
@LastModifiedDate
|
||||
@Column(name = "updated_at", nullable = false)
|
||||
private LocalDateTime updatedAt;
|
||||
|
||||
/**
|
||||
* 엔티티 생성 전 실행되는 메서드
|
||||
*/
|
||||
@PrePersist
|
||||
protected void onCreate() {
|
||||
LocalDateTime now = LocalDateTime.now();
|
||||
if (createdAt == null) {
|
||||
createdAt = now;
|
||||
}
|
||||
if (updatedAt == null) {
|
||||
updatedAt = now;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 엔티티 수정 전 실행되는 메서드
|
||||
*/
|
||||
@PreUpdate
|
||||
protected void onUpdate() {
|
||||
updatedAt = LocalDateTime.now();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// common/src/main/java/com/healthsync/common/exception/AuthenticationException.java
|
||||
package com.healthsync.common.exception;
|
||||
|
||||
import com.healthsync.common.constants.ErrorCode;
|
||||
|
||||
/**
|
||||
* 인증 실패 시 발생하는 예외입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
public class AuthenticationException extends BusinessException {
|
||||
|
||||
/**
|
||||
* AuthenticationException 생성자
|
||||
*
|
||||
* @param message 에러 메시지
|
||||
*/
|
||||
public AuthenticationException(String message) {
|
||||
super(ErrorCode.AUTHENTICATION_FAILED, message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// common/src/main/java/com/healthsync/common/exception/BusinessException.java
|
||||
package com.healthsync.common.exception;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 비즈니스 로직에서 발생하는 예외를 처리하는 클래스입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
@Getter
|
||||
public class BusinessException extends RuntimeException {
|
||||
|
||||
private final String errorCode;
|
||||
|
||||
public BusinessException(String errorCode, String message) {
|
||||
super(message);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
|
||||
public BusinessException(String errorCode, String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
this.errorCode = errorCode;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.healthsync.common.exception;
|
||||
|
||||
/**
|
||||
* 외부 API 호출 실패 시 발생하는 예외 클래스입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
public class ExternalApiException extends BusinessException {
|
||||
|
||||
public ExternalApiException(String message) {
|
||||
super("EXTERNAL_API_ERROR", message);
|
||||
}
|
||||
|
||||
public ExternalApiException(String message, Throwable cause) {
|
||||
super("EXTERNAL_API_ERROR", message, cause);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
package com.healthsync.common.exception;
|
||||
|
||||
import com.healthsync.common.dto.ErrorResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
|
||||
log.warn("Business exception occurred: {}", ex.getMessage());
|
||||
ErrorResponse errorResponse = ErrorResponse.builder()
|
||||
.code(ex.getErrorCode())
|
||||
.message(ex.getMessage())
|
||||
.build();
|
||||
return ResponseEntity.badRequest().body(errorResponse);
|
||||
}
|
||||
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
|
||||
log.warn("Validation exception occurred: {}", ex.getMessage());
|
||||
String message = "유효하지 않은 입력값입니다.";
|
||||
try {
|
||||
if (ex.getBindingResult().hasErrors()) {
|
||||
message = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.debug("Error getting validation message: {}", e.getMessage());
|
||||
}
|
||||
ErrorResponse errorResponse = ErrorResponse.builder()
|
||||
.code("VALIDATION_ERROR")
|
||||
.message(message)
|
||||
.build();
|
||||
return ResponseEntity.badRequest().body(errorResponse);
|
||||
}
|
||||
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ErrorResponse> handleException(Exception ex) {
|
||||
log.error("Unexpected exception occurred", ex);
|
||||
ErrorResponse errorResponse = ErrorResponse.builder()
|
||||
.code("INTERNAL_SERVER_ERROR")
|
||||
.message("서버 내부 오류가 발생했습니다.")
|
||||
.build();
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
|
||||
}
|
||||
}
|
||||
+71
@@ -0,0 +1,71 @@
|
||||
// common/src/main/java/com/healthsync/common/exception/GlobalExceptionHandler.java
|
||||
package com.healthsync.common.exception;
|
||||
|
||||
import com.healthsync.common.dto.ErrorResponse;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.validation.BindException;
|
||||
import org.springframework.web.bind.MethodArgumentNotValidException;
|
||||
import org.springframework.web.bind.annotation.ExceptionHandler;
|
||||
import org.springframework.web.bind.annotation.RestControllerAdvice;
|
||||
|
||||
/**
|
||||
* 전역 예외 처리 핸들러입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@RestControllerAdvice
|
||||
public class GlobalExceptionHandler {
|
||||
|
||||
/**
|
||||
* 비즈니스 예외 처리
|
||||
*
|
||||
* @param ex BusinessException
|
||||
* @return ErrorResponse
|
||||
*/
|
||||
@ExceptionHandler(BusinessException.class)
|
||||
public ResponseEntity<ErrorResponse> handleBusinessException(BusinessException ex) {
|
||||
log.warn("Business exception occurred: {}", ex.getMessage());
|
||||
ErrorResponse errorResponse = ErrorResponse.builder()
|
||||
.code(ex.getErrorCode())
|
||||
.message(ex.getMessage())
|
||||
.build();
|
||||
return ResponseEntity.badRequest().body(errorResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* 유효성 검증 예외 처리 (RequestBody)
|
||||
*
|
||||
* @param ex MethodArgumentNotValidException
|
||||
* @return ErrorResponse
|
||||
*/
|
||||
@ExceptionHandler(MethodArgumentNotValidException.class)
|
||||
public ResponseEntity<ErrorResponse> handleValidationException(MethodArgumentNotValidException ex) {
|
||||
log.warn("Validation exception occurred: {}", ex.getMessage());
|
||||
String message = ex.getBindingResult().getAllErrors().get(0).getDefaultMessage();
|
||||
ErrorResponse errorResponse = ErrorResponse.builder()
|
||||
.code("VALIDATION_ERROR")
|
||||
.message(message)
|
||||
.build();
|
||||
return ResponseEntity.badRequest().body(errorResponse);
|
||||
}
|
||||
|
||||
/**
|
||||
* 일반적인 예외 처리
|
||||
*
|
||||
* @param ex Exception
|
||||
* @return ErrorResponse
|
||||
*/
|
||||
@ExceptionHandler(Exception.class)
|
||||
public ResponseEntity<ErrorResponse> handleException(Exception ex) {
|
||||
log.error("Unexpected exception occurred", ex);
|
||||
ErrorResponse errorResponse = ErrorResponse.builder()
|
||||
.code("INTERNAL_SERVER_ERROR")
|
||||
.message("서버 내부 오류가 발생했습니다.")
|
||||
.build();
|
||||
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(errorResponse);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// common/src/main/java/com/healthsync/common/exception/MissionNotFoundException.java
|
||||
package com.healthsync.common.exception;
|
||||
|
||||
import com.healthsync.common.constants.ErrorCode;
|
||||
|
||||
/**
|
||||
* 미션을 찾을 수 없을 때 발생하는 예외입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
public class MissionNotFoundException extends BusinessException {
|
||||
|
||||
/**
|
||||
* MissionNotFoundException 생성자
|
||||
*
|
||||
* @param missionId 미션 ID
|
||||
*/
|
||||
public MissionNotFoundException(String missionId) {
|
||||
super(ErrorCode.MISSION_NOT_FOUND, "미션을 찾을 수 없습니다: " + missionId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// common/src/main/java/com/healthsync/common/exception/UserNotFoundException.java
|
||||
package com.healthsync.common.exception;
|
||||
|
||||
import com.healthsync.common.constants.ErrorCode;
|
||||
|
||||
/**
|
||||
* 사용자를 찾을 수 없을 때 발생하는 예외입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
public class UserNotFoundException extends BusinessException {
|
||||
|
||||
/**
|
||||
* UserNotFoundException 생성자
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
*/
|
||||
public UserNotFoundException(String userId) {
|
||||
super(ErrorCode.USER_NOT_FOUND, "사용자를 찾을 수 없습니다: " + userId);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.healthsync.common.exception;
|
||||
|
||||
/**
|
||||
* 유효성 검증 실패 시 발생하는 예외 클래스입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
public class ValidationException extends BusinessException {
|
||||
|
||||
public ValidationException(String message) {
|
||||
super("VALIDATION_ERROR", message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
// common/src/main/java/com/healthsync/common/util/DateUtil.java
|
||||
package com.healthsync.common.util;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.format.DateTimeFormatter;
|
||||
import java.time.temporal.ChronoUnit;
|
||||
|
||||
/**
|
||||
* 날짜 관련 유틸리티 클래스입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
public class DateUtil {
|
||||
|
||||
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||||
private static final DateTimeFormatter DATETIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
|
||||
|
||||
/**
|
||||
* 현재 날짜를 문자열로 반환합니다.
|
||||
*
|
||||
* @return 현재 날짜 (yyyy-MM-dd 형식)
|
||||
*/
|
||||
public static String getCurrentDate() {
|
||||
return LocalDate.now().format(DATE_FORMATTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 현재 날짜시간을 문자열로 반환합니다.
|
||||
*
|
||||
* @return 현재 날짜시간 (yyyy-MM-dd HH:mm:ss 형식)
|
||||
*/
|
||||
public static String getCurrentDateTime() {
|
||||
return LocalDateTime.now().format(DATETIME_FORMATTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* 생년월일로부터 나이를 계산합니다.
|
||||
*
|
||||
* @param birthDate 생년월일 (yyyy-MM-dd 형식)
|
||||
* @return 나이
|
||||
*/
|
||||
public static int calculateAge(String birthDate) {
|
||||
LocalDate birth = LocalDate.parse(birthDate, DATE_FORMATTER);
|
||||
return (int) ChronoUnit.YEARS.between(birth, LocalDate.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalDate 생년월일로부터 나이를 계산합니다.
|
||||
*
|
||||
* @param birthDate 생년월일
|
||||
* @return 나이
|
||||
*/
|
||||
public static int calculateAge(LocalDate birthDate) {
|
||||
return (int) ChronoUnit.YEARS.between(birthDate, LocalDate.now());
|
||||
}
|
||||
|
||||
/**
|
||||
* 두 날짜 사이의 일수를 계산합니다.
|
||||
*
|
||||
* @param startDate 시작 날짜
|
||||
* @param endDate 종료 날짜
|
||||
* @return 일수
|
||||
*/
|
||||
public static long getDaysBetween(LocalDate startDate, LocalDate endDate) {
|
||||
return ChronoUnit.DAYS.between(startDate, endDate);
|
||||
}
|
||||
|
||||
/**
|
||||
* 문자열을 LocalDate로 변환합니다.
|
||||
*
|
||||
* @param dateString 날짜 문자열 (yyyy-MM-dd 형식)
|
||||
* @return LocalDate
|
||||
*/
|
||||
public static LocalDate parseDate(String dateString) {
|
||||
return LocalDate.parse(dateString, DATE_FORMATTER);
|
||||
}
|
||||
|
||||
/**
|
||||
* LocalDate를 문자열로 변환합니다.
|
||||
*
|
||||
* @param date LocalDate
|
||||
* @return 날짜 문자열 (yyyy-MM-dd 형식)
|
||||
*/
|
||||
public static String formatDate(LocalDate date) {
|
||||
return date.format(DATE_FORMATTER);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,156 @@
|
||||
// common/src/main/java/com/healthsync/common/util/JwtUtil.java
|
||||
package com.healthsync.common.util;
|
||||
|
||||
import io.jsonwebtoken.*;
|
||||
import io.jsonwebtoken.security.Keys;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.ZoneId;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* JWT 토큰 유틸리티 클래스입니다.
|
||||
*
|
||||
* @author healthsync-team
|
||||
* @version 1.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Component
|
||||
public class JwtUtil {
|
||||
|
||||
private final SecretKey secretKey;
|
||||
private final long accessTokenValidityInMilliseconds;
|
||||
private final long refreshTokenValidityInMilliseconds;
|
||||
|
||||
/**
|
||||
* JwtUtil 생성자
|
||||
*
|
||||
* @param secret JWT 시크릿 키
|
||||
* @param accessTokenValidityInMilliseconds 액세스 토큰 유효 시간
|
||||
* @param refreshTokenValidityInMilliseconds 리프레시 토큰 유효 시간
|
||||
*/
|
||||
public JwtUtil(
|
||||
@Value("${jwt.secret:healthsync-default-secret-key-for-development-only}") String secret,
|
||||
@Value("${jwt.access-token.expire-length:3600000}") long accessTokenValidityInMilliseconds,
|
||||
@Value("${jwt.refresh-token.expire-length:604800000}") long refreshTokenValidityInMilliseconds) {
|
||||
|
||||
this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8));
|
||||
this.accessTokenValidityInMilliseconds = accessTokenValidityInMilliseconds;
|
||||
this.refreshTokenValidityInMilliseconds = refreshTokenValidityInMilliseconds;
|
||||
}
|
||||
|
||||
/**
|
||||
* 액세스 토큰을 생성합니다.
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @return 생성된 액세스 토큰
|
||||
*/
|
||||
public String generateAccessToken(String userId) {
|
||||
return createToken(userId, accessTokenValidityInMilliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 리프레시 토큰을 생성합니다.
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @return 생성된 리프레시 토큰
|
||||
*/
|
||||
public String generateRefreshToken(String userId) {
|
||||
return createToken(userId, refreshTokenValidityInMilliseconds);
|
||||
}
|
||||
|
||||
/**
|
||||
* 액세스 토큰을 생성합니다. (별칭 메서드)
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @return 생성된 액세스 토큰
|
||||
*/
|
||||
public String createAccessToken(String userId) {
|
||||
return generateAccessToken(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 리프레시 토큰을 생성합니다. (별칭 메서드)
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @return 생성된 리프레시 토큰
|
||||
*/
|
||||
public String createRefreshToken(String userId) {
|
||||
return generateRefreshToken(userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰을 생성합니다.
|
||||
*
|
||||
* @param userId 사용자 ID
|
||||
* @param validityInMilliseconds 유효 시간(밀리초)
|
||||
* @return 생성된 토큰
|
||||
*/
|
||||
private String createToken(String userId, long validityInMilliseconds) {
|
||||
Date now = new Date();
|
||||
Date validity = new Date(now.getTime() + validityInMilliseconds);
|
||||
|
||||
return Jwts.builder()
|
||||
.setSubject(userId)
|
||||
.setIssuedAt(now)
|
||||
.setExpiration(validity)
|
||||
.signWith(secretKey, SignatureAlgorithm.HS256)
|
||||
.compact();
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰에서 사용자 ID를 추출합니다.
|
||||
*
|
||||
* @param token JWT 토큰
|
||||
* @return 사용자 ID
|
||||
*/
|
||||
public String getUserId(String token) {
|
||||
return getClaims(token).getSubject();
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰의 유효성을 검증합니다.
|
||||
*
|
||||
* @param token JWT 토큰
|
||||
* @return 유효성 여부
|
||||
*/
|
||||
public boolean validateToken(String token) {
|
||||
try {
|
||||
getClaims(token);
|
||||
return true;
|
||||
} catch (JwtException | IllegalArgumentException e) {
|
||||
log.error("Invalid JWT token: {}", e.getMessage());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰에서 Claims를 추출합니다.
|
||||
*
|
||||
* @param token JWT 토큰
|
||||
* @return Claims
|
||||
*/
|
||||
private Claims getClaims(String token) {
|
||||
return Jwts.parserBuilder()
|
||||
.setSigningKey(secretKey)
|
||||
.build()
|
||||
.parseClaimsJws(token)
|
||||
.getBody();
|
||||
}
|
||||
|
||||
/**
|
||||
* 토큰의 만료 시간을 반환합니다.
|
||||
*
|
||||
* @param token JWT 토큰
|
||||
* @return 만료 시간
|
||||
*/
|
||||
public LocalDateTime getExpirationDate(String token) {
|
||||
Date expiration = getClaims(token).getExpiration();
|
||||
return expiration.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user