# Bio Backend ## 기술 스택 - **Framework**: Spring Boot - **Database**: PostgreSQL - **ORM**: Spring Data JPA + QueryDSL - **Security**: Spring Security + JWT - **Build Tool**: Gradle - **Container**: Docker + Kubernetes - **API Documentation**: Swagger (SpringDoc OpenAPI) ## 개발 가이드 ### 1. 프로젝트 구조 ``` src/main/java/com/bio/bio_backend/ ├── domain/ # 도메인별 패키지 │ └── base/ # 기본 도메인 │ └── member/ # 회원 도메인 │ ├── controller/ # API 엔드포인트 │ ├── service/ # 비즈니스 로직 │ ├── repository/ # 데이터 접근 │ ├── entity/ # JPA 엔티티 │ ├── dto/ # 데이터 전송 객체 │ ├── mapper/ # MapStruct 매퍼 │ └── enums/ # 도메인 열거형 ├── global/ # 공통 설정 │ ├── config/ # 설정 클래스 │ ├── security/ # 보안 설정 │ ├── exception/ # 예외 처리 │ ├── aop/ # AOP 로깅 │ ├── filter/ # HTTP 로깅 필터 │ ├── utils/ # 유틸리티 │ ├── constants/ # 상수 정의 │ ├── dto/ # 공통 DTO │ ├── entity/ # 공통 엔티티 │ └── annotation/ # 커스텀 어노테이션 ├── BioBackendApplication.java └── ServletInitializer.java ``` ### 2. API 응답 표준화 (ApiResponseDto) #### 응답 구조 모든 API 응답은 `ApiResponseDto` 형태로 표준화되어 있습니다. ```java public class ApiResponseDto { private int code; // HTTP 상태 코드 private String message; // 응답 메시지 (ApiResponseCode enum 값) private String description; // 응답 설명 private T data; // 실제 데이터 (제네릭 타입) } ``` #### 응답 예시 **성공 응답 (201 Created)** ```json { "code": 201, "message": "COMMON_SUCCESS_CREATED", "description": "Created successfully", "data": { "seq": 1, "userId": "user123", "name": "홍길동" } } ``` **실패 응답 (409 Conflict)** ```json { "code": 409, "message": "USER_ID_DUPLICATE", "description": "User ID already exists", "data": null } ``` #### 사용 방법 **Controller에서 응답 생성** ```java @PostMapping("/members") public ResponseEntity> createMember(@RequestBody CreateMemberRequestDto requestDto) { // ... 비즈니스 로직 ... // 성공 응답 ApiResponseDto apiResponse = ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_CREATED, responseDto); return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse); } ``` **공용 응답 코드 사용** ```java // ApiResponseCode.java public enum ApiResponseCode { // 공용 성공 코드 COMMON_SUCCESS_CREATED(HttpStatus.CREATED.value(), "Created successfully"), COMMON_SUCCESS_UPDATED(HttpStatus.OK.value(), "Updated successfully"), COMMON_SUCCESS_DELETED(HttpStatus.OK.value(), "Deleted successfully"), COMMON_SUCCESS_RETRIEVED(HttpStatus.OK.value(), "Retrieved successfully"), // 공용 오류 코드 COMMON_BAD_REQUEST(HttpStatus.BAD_REQUEST.value(), "Required request body is missing or Error"), COMMON_UNAUTHORIZED(HttpStatus.UNAUTHORIZED.value(), "Unauthorized"), COMMON_FORBIDDEN(HttpStatus.FORBIDDEN.value(), "Access is denied"), COMMON_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "Resource is not found"), COMMON_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "An error occurred on the server") } ``` #### 핵심 규칙 - **모든 API 응답**: `ApiResponseDto`로 감싸서 반환 - **공용 응답 코드**: `COMMON_` 접두사로 시작하는 범용 코드 사용 - **일관된 구조**: `code`, `message`, `description`, `data` 필드로 표준화 - **제네릭 활용**: ``를 통해 다양한 데이터 타입 지원 ### 3. JWT 인증 시스템 #### 토큰 구성 - **Access Token**: 15분 (900,000ms) - API 요청 시 사용 - **Refresh Token**: 7일 (604,800,000ms) - 쿠키에 저장, 자동 갱신 #### 자동 토큰 갱신 **모든 API 요청마다 자동으로 처리됩니다:** 1. **Access Token 유효**: 정상 요청 처리 2. **Access Token 만료**: Refresh Token으로 자동 갱신 3. **새 Access Token 발급**: 응답 헤더에 자동 설정 4. **Refresh Token 갱신**: 보안을 위해 매번 새로운 토큰 발급 #### 주요 컴포넌트 - **`JwtTokenIssuanceFilter`**: 로그인 시 토큰 발급 - **`JwtTokenValidationFilter`**: 요청마다 토큰 검증 및 갱신 - **`JwtUtils`**: 토큰 생성, 검증, 갱신 유틸리티 ### 4. API 문서화 (Swagger) #### Swagger UI 접속 - **URL**: `http://localhost:8080/service/swagger-ui.html` - **API Docs**: `http://localhost:8080/service/api-docs` #### 주요 어노테이션 ```java @Tag(name = "Member", description = "회원 관리 API") @Operation(summary = "회원 가입", description = "새로운 회원을 등록합니다.") @ApiResponses(value = { @ApiResponse(responseCode = "201", description = "회원 가입 성공"), @ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content(schema = @Schema(implementation = ApiResponseDto.class))), @ApiResponse(responseCode = "409", description = "중복된 사용자 정보", content = @Content(schema = @Schema(implementation = ApiResponseDto.class))) }) ``` #### 설정 파일 - **SwaggerConfig.java**: OpenAPI 기본 정보 설정 - **application.properties**: Swagger UI 커스터마이징 ### 5. 트랜잭션 관리 #### 기본 설정 ```java @Service @Transactional(readOnly = true) // 클래스 레벨: 읽기 전용 기본값 public class MemberServiceImpl { // 읽기 전용 메서드 (별도 어노테이션 불필요) public MemberDto selectMember(long seq) { ... } // 쓰기 작업 메서드 (개별 @Transactional 적용) @Transactional public MemberDto createMember(MemberDto dto) { ... } } ``` #### 핵심 규칙 - **클래스 레벨**: `@Transactional(readOnly = true)` 기본 설정 - **메서드별**: 데이터 수정 시에만 `@Transactional` 개별 적용 - **설정**: `spring.jpa.open-in-view=false` (성능 최적화) ### 6. 오류 등록 및 사용 #### 오류 코드 등록 ```java // ApiResponseCode.java public enum ApiResponseCode { USER_ID_DUPLICATE(HttpStatus.CONFLICT.value(), "User ID already exists"), } ``` #### 오류 사용 방법 ```java // Service에서 예외 발생 throw new ApiException(ApiResponseCode.USER_ID_DUPLICATE); // Controller에서 예외 처리 (자동) // GlobalExceptionHandler가 ApiException을 잡아서 응답 변환 ``` #### 핵심 규칙 - **오류 코드**: `ApiResponseCode` enum에 모든 오류 정의 - **예외 클래스**: `ApiException`으로 비즈니스 로직 예외 처리 - **자동 처리**: `GlobalExceptionHandler`가 일관된 응답 형태로 변환 ### 7. 로깅 시스템 #### @LogExecution 어노테이션 사용법 **메서드 실행 로깅을 위한 필수 어노테이션입니다.** ##### 기본 사용법 ```java @LogExecution("회원 등록") @PostMapping("/register") public ResponseEntity> createMember(@RequestBody CreateMemberRequestDto requestDto) { // 메서드 실행 시 자동으로 로그 출력 } ``` ##### 로그 출력 예시 **메서드 시작 시:** ``` [START] 회원 등록 | 호출경로: MemberController.createMember | 사용자: 고명빈(kmbin92) ``` **메서드 성공 시:** ``` [SUCCESS] 회원 등록 | 호출경로: MemberController.createMember | 사용자: 고명빈(kmbin92) | 시간: 200ms ``` **메서드 실패 시:** ``` [FAILED] 회원 등록 | 호출경로: MemberController.createMember | 사용자: 고명빈(kmbin92) | 시간: 23ms | 오류: 이미 존재하는 사용자 ID입니다 ``` ##### 어노테이션 설정 ```java @LogExecution("사용자 정보 조회") public UserDto getUserInfo() { } @LogExecution("주문 처리") public OrderDto processOrder() { } ``` ##### 로깅 시스템 구성 1. **MethodExecutionLoggingAspect**: 메서드 실행 로깅 (AOP) 2. **HttpLoggingFilter**: HTTP 요청/응답 로깅 (필터) 3. **logback-spring.xml**: 로그 파일 관리 및 설정 **중요**: `@LogExecution` 어노테이션이 없으면 메서드 실행 로그가 출력되지 않습니다 ### 8. MapStruct **매퍼 인터페이스** ```java @Mapper(componentModel = "spring") public interface MemberMapper { MemberDto toDto(Member member); Member toEntity(MemberDto dto); } ``` **사용 예시** ```java // Entity → DTO MemberDto dto = memberMapper.toDto(member); // DTO → Entity Member entity = memberMapper.toEntity(dto); ``` **자동 생성**: 컴파일 시 `MemberMapperImpl` 구현체 생성