This commit is contained in:
leejisun9
2025-08-27 14:10:05 +09:00
36 changed files with 1291 additions and 299 deletions

View File

@@ -49,6 +49,7 @@ src/main/java/com/bio/bio_backend/
```java ```java
public class ApiResponseDto<T> { public class ApiResponseDto<T> {
private boolean success; // 성공/실패 여부 (true/false)
private int code; // HTTP 상태 코드 private int code; // HTTP 상태 코드
private String message; // 응답 메시지 (ApiResponseCode enum 값) private String message; // 응답 메시지 (ApiResponseCode enum 값)
private String description; // 응답 설명 private String description; // 응답 설명
@@ -62,6 +63,7 @@ public class ApiResponseDto<T> {
```json ```json
{ {
"success": true,
"code": 201, "code": 201,
"message": "COMMON_SUCCESS_CREATED", "message": "COMMON_SUCCESS_CREATED",
"description": "Created successfully", "description": "Created successfully",
@@ -77,6 +79,7 @@ public class ApiResponseDto<T> {
```json ```json
{ {
"success": false,
"code": 409, "code": 409,
"message": "USER_ID_DUPLICATE", "message": "USER_ID_DUPLICATE",
"description": "User ID already exists" "description": "User ID already exists"
@@ -131,7 +134,8 @@ public enum ApiResponseCode {
- **모든 API 응답**: `ApiResponseDto<T>`로 감싸서 반환 - **모든 API 응답**: `ApiResponseDto<T>`로 감싸서 반환
- **공용 응답 코드**: `COMMON_` 접두사로 시작하는 범용 코드 사용 - **공용 응답 코드**: `COMMON_` 접두사로 시작하는 범용 코드 사용
- **일관된 구조**: `code`, `message`, `description`, `data` 필드로 표준화 - **일관된 구조**: `success`, `code`, `message`, `description`, `data` 필드로 표준화
- **성공/실패 구분**: `success` 필드로 명확한 성공/실패 여부 전달
- **제네릭 활용**: `<T>`를 통해 다양한 데이터 타입 지원 - **제네릭 활용**: `<T>`를 통해 다양한 데이터 타입 지원
### 3. JWT 인증 시스템 ### 3. JWT 인증 시스템
@@ -163,6 +167,21 @@ public enum ApiResponseCode {
- **URL**: `http://localhost:8080/service/swagger-ui.html` - **URL**: `http://localhost:8080/service/swagger-ui.html`
- **API Docs**: `http://localhost:8080/service/api-docs` - **API Docs**: `http://localhost:8080/service/api-docs`
### 5. 시스템 모니터링 (Actuator)
#### Health 체크
- **기본 Health**: `http://localhost:8080/service/actuator/health`
- **Readiness Probe**: `http://localhost:8080/service/actuator/health/readiness`
- **Liveness Probe**: `http://localhost:8080/service/actuator/health/liveness`
#### 시스템 정보
- **애플리케이션 정보**: `http://localhost:8080/service/actuator/info`
- **환경 설정**: `http://localhost:8080/service/actuator/env`
- **설정 속성**: `http://localhost:8080/service/actuator/configprops`
- **메트릭**: `http://localhost:8080/service/actuator/metrics`
#### 주요 어노테이션 #### 주요 어노테이션
```java ```java
@@ -182,7 +201,7 @@ public enum ApiResponseCode {
- **SwaggerConfig.java**: OpenAPI 기본 정보 설정 - **SwaggerConfig.java**: OpenAPI 기본 정보 설정
- **application.properties**: Swagger UI 커스터마이징 - **application.properties**: Swagger UI 커스터마이징
### 5. 트랜잭션 관리 ### 6. 트랜잭션 관리
#### 기본 설정 #### 기본 설정
@@ -206,7 +225,7 @@ public class MemberServiceImpl {
- **메서드별**: 데이터 수정 시에만 `@Transactional` 개별 적용 - **메서드별**: 데이터 수정 시에만 `@Transactional` 개별 적용
- **설정**: `spring.jpa.open-in-view=false` (성능 최적화) - **설정**: `spring.jpa.open-in-view=false` (성능 최적화)
### 6. 오류 등록 및 사용 ### 7. 오류 등록 및 사용
#### 오류 코드 등록 #### 오류 코드 등록
@@ -233,7 +252,7 @@ throw new ApiException(ApiResponseCode.USER_ID_DUPLICATE);
- **예외 클래스**: `ApiException`으로 비즈니스 로직 예외 처리 - **예외 클래스**: `ApiException`으로 비즈니스 로직 예외 처리
- **자동 처리**: `GlobalExceptionHandler`가 일관된 응답 형태로 변환 - **자동 처리**: `GlobalExceptionHandler`가 일관된 응답 형태로 변환
### 7. 로깅 시스템 ### 8. 로깅 시스템
#### @LogExecution 어노테이션 사용법 #### @LogExecution 어노테이션 사용법
@@ -287,7 +306,7 @@ public OrderDto processOrder() { }
**중요**: `@LogExecution` 어노테이션이 없으면 메서드 실행 로그가 출력되지 않습니다 **중요**: `@LogExecution` 어노테이션이 없으면 메서드 실행 로그가 출력되지 않습니다
### 8. MapStruct ### 9. MapStruct
**매퍼 인터페이스** **매퍼 인터페이스**
@@ -311,7 +330,7 @@ Member entity = memberMapper.toEntity(dto);
**자동 생성**: 컴파일 시 `MemberMapperImpl` 구현체 생성 **자동 생성**: 컴파일 시 `MemberMapperImpl` 구현체 생성
### 9. BaseEntity 상속 ### 10. BaseEntity 상속
**모든 엔티티는 `BaseEntity` 상속을 원칙으로 합니다.** **모든 엔티티는 `BaseEntity` 상속을 원칙으로 합니다.**

View File

@@ -38,6 +38,9 @@ dependencies {
// Validation // Validation
implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-validation'
// Spring Boot Actuator
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// MapStruct // MapStruct
implementation 'org.mapstruct:mapstruct:1.5.5.Final' implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'

View File

@@ -1,4 +1,47 @@
create table st_common_code (
sort_order integer not null,
use_flag boolean not null,
created_at timestamp(6) not null,
created_oid bigint,
oid bigint not null,
updated_at timestamp(6) not null,
updated_oid bigint,
code varchar(50) not null unique,
group_code varchar(50) not null,
parent_code varchar(50),
character_ref1 varchar(100),
character_ref2 varchar(100),
character_ref3 varchar(100),
character_ref4 varchar(100),
character_ref5 varchar(100),
name varchar(100) not null,
description varchar(500),
created_id varchar(255),
updated_id varchar(255),
primary key (oid)
);
create table st_common_group_code (
sort_order integer not null,
use_flag boolean not null,
created_at timestamp(6) not null,
created_oid bigint,
oid bigint not null,
updated_at timestamp(6) not null,
updated_oid bigint,
code varchar(50) not null unique,
character_ref1_title varchar(100),
character_ref2_title varchar(100),
character_ref3_title varchar(100),
character_ref4_title varchar(100),
character_ref5_title varchar(100),
name varchar(100) not null,
created_id varchar(255),
updated_id varchar(255),
primary key (oid)
);
create table st_file ( create table st_file (
use_flag boolean not null, use_flag boolean not null,
created_at timestamp(6) not null, created_at timestamp(6) not null,
@@ -38,5 +81,17 @@
primary key (oid) primary key (oid)
); );
create index idx_common_code_code
on st_common_code (code);
create index idx_common_code_group_code
on st_common_code (group_code);
create index idx_common_code_parent_code
on st_common_code (parent_code);
create index idx_common_group_code_code
on st_common_group_code (code);
create index idx_member_user_id create index idx_member_user_id
on st_member (user_id); on st_member (user_id);

View File

@@ -0,0 +1,208 @@
package com.bio.bio_backend.domain.admin.common_code.controller;
import com.bio.bio_backend.domain.admin.common_code.dto.*;
import com.bio.bio_backend.domain.admin.common_code.service.CommonCodeService;
import com.bio.bio_backend.domain.admin.common_code.mapper.CommonCodeMapper;
import com.bio.bio_backend.domain.admin.common_code.mapper.CommonGroupCodeMapper;
import com.bio.bio_backend.global.dto.ApiResponseDto;
import com.bio.bio_backend.global.constants.ApiResponseCode;
import com.bio.bio_backend.global.annotation.LogExecution;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
import java.util.List;
@Slf4j
@RestController
@RequestMapping("/admin/common-code")
@RequiredArgsConstructor
@Tag(name = "공통 코드 관리", description = "공통 코드 및 그룹 코드 관리 API")
public class CommonCodeController {
private final CommonCodeService commonCodeService;
private final CommonCodeMapper commonCodeMapper;
private final CommonGroupCodeMapper commonGroupCodeMapper;
// 그룹 코드 관련 API
@LogExecution("그룹 코드 생성")
@Operation(summary = "그룹 코드 생성", description = "새로운 그룹 코드를 생성합니다.")
@ApiResponses({
@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)))
})
@PostMapping("/group")
public ResponseEntity<ApiResponseDto<CreateCommonGroupCodeResponseDto>> createGroupCode(@RequestBody @Valid CreateCommonGroupCodeRequestDto requestDto) {
CommonGroupCodeDto groupCodeDto = commonGroupCodeMapper.toCommonGroupCodeDto(requestDto);
CommonGroupCodeDto createdGroupCode = commonCodeService.createGroupCode(groupCodeDto);
CreateCommonGroupCodeResponseDto responseDto = commonGroupCodeMapper.toCreateCommonGroupCodeResponseDto(createdGroupCode);
ApiResponseDto<CreateCommonGroupCodeResponseDto> apiResponse = ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_CREATED, responseDto);
return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse);
}
@LogExecution("그룹 코드 수정")
@Operation(summary = "그룹 코드 수정", description = "기존 그룹 코드를 수정합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "그룹 코드 수정 성공"),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content(schema = @Schema(implementation = ApiResponseDto.class))),
@ApiResponse(responseCode = "404", description = "그룹 코드를 찾을 수 없음", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
})
@PutMapping("/group/{code}")
public ResponseEntity<ApiResponseDto<String>> updateGroupCode(
@PathVariable String code,
@RequestBody @Valid UpdateCommonGroupCodeRequestDto requestDto) {
CommonGroupCodeDto groupCodeDto = commonGroupCodeMapper.toCommonGroupCodeDto(requestDto);
commonCodeService.updateGroupCode(code, groupCodeDto);
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_UPDATED));
}
@LogExecution("그룹 코드 삭제")
@Operation(summary = "그룹 코드 삭제", description = "그룹 코드를 삭제합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "그룹 코드 삭제 성공"),
@ApiResponse(responseCode = "400", description = "하위 공통 코드가 존재하여 삭제할 수 없음", content = @Content(schema = @Schema(implementation = ApiResponseDto.class))),
@ApiResponse(responseCode = "404", description = "그룹 코드를 찾을 수 없음", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
})
@DeleteMapping("/group/{code}")
public ResponseEntity<ApiResponseDto<Void>> deleteGroupCode(@PathVariable String code) {
commonCodeService.deleteGroupCode(code);
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_DELETED));
}
@LogExecution("그룹 코드 조회")
@Operation(summary = "그룹 코드 조회", description = "특정 그룹 코드를 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "그룹 코드 조회 성공"),
@ApiResponse(responseCode = "404", description = "그룹 코드를 찾을 수 없음", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
})
@GetMapping("/group/{code}")
public ResponseEntity<ApiResponseDto<CommonGroupCodeDto>> getGroupCode(@PathVariable String code) {
CommonGroupCodeDto groupCode = commonCodeService.getGroupCode(code);
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_RETRIEVED, groupCode));
}
@LogExecution("전체 그룹 코드 조회")
@Operation(summary = "전체 그룹 코드 조회", description = "모든 그룹 코드를 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "전체 그룹 코드 조회 성공")
})
@GetMapping("/group")
public ResponseEntity<ApiResponseDto<List<CommonGroupCodeDto>>> getAllGroupCodes() {
List<CommonGroupCodeDto> groupCodes = commonCodeService.getAllGroupCodes();
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_RETRIEVED, groupCodes));
}
@LogExecution("활성 그룹 코드 조회")
@Operation(summary = "활성 그룹 코드 조회", description = "사용 중인 그룹 코드만 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "활성 그룹 코드 조회 성공")
})
@GetMapping("/group/active")
public ResponseEntity<ApiResponseDto<List<CommonGroupCodeDto>>> getActiveGroupCodes() {
List<CommonGroupCodeDto> groupCodes = commonCodeService.getActiveGroupCodes();
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_RETRIEVED, groupCodes));
}
// 공통 코드 관련 API
@LogExecution("공통 코드 생성")
@Operation(summary = "공통 코드 생성", description = "새로운 공통 코드를 생성합니다.")
@ApiResponses({
@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)))
})
@PostMapping
public ResponseEntity<ApiResponseDto<CreateCommonCodeResponseDto>> createCode(@RequestBody @Valid CreateCommonCodeRequestDto requestDto) {
CommonCodeDto commonCodeDto = commonCodeMapper.toCommonCodeDto(requestDto);
CommonCodeDto createdCommonCode = commonCodeService.createCode(commonCodeDto);
CreateCommonCodeResponseDto responseDto = commonCodeMapper.toCreateCommonCodeResponseDto(createdCommonCode);
ApiResponseDto<CreateCommonCodeResponseDto> apiResponse = ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_CREATED, responseDto);
return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse);
}
@LogExecution("공통 코드 수정")
@Operation(summary = "공통 코드 수정", description = "기존 공통 코드를 수정합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "공통 코드 수정 성공"),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content(schema = @Schema(implementation = ApiResponseDto.class))),
@ApiResponse(responseCode = "404", description = "공통 코드를 찾을 수 없음", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
})
@PutMapping("/{code}")
public ResponseEntity<ApiResponseDto<String>> updateCode(
@PathVariable String code,
@RequestBody @Valid UpdateCommonCodeRequestDto requestDto) {
CommonCodeDto commonCodeDto = commonCodeMapper.toCommonCodeDto(requestDto);
commonCodeService.updateCode(code, commonCodeDto);
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_UPDATED));
}
@LogExecution("공통 코드 삭제")
@Operation(summary = "공통 코드 삭제", description = "공통 코드를 삭제합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "공통 코드 삭제 성공"),
@ApiResponse(responseCode = "400", description = "하위 공통 코드가 존재하여 삭제할 수 없음", content = @Content(schema = @Schema(implementation = ApiResponseDto.class))),
@ApiResponse(responseCode = "404", description = "공통 코드를 찾을 수 없음", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
})
@DeleteMapping("/{code}")
public ResponseEntity<ApiResponseDto<Void>> deleteCode(@PathVariable String code) {
commonCodeService.deleteCode(code);
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_DELETED));
}
@LogExecution("공통 코드 조회")
@Operation(summary = "공통 코드 조회", description = "특정 공통 코드를 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "공통 코드 조회 성공"),
@ApiResponse(responseCode = "404", description = "공통 코드를 찾을 수 없음", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
})
@GetMapping("/{code}")
public ResponseEntity<ApiResponseDto<CommonCodeDto>> getCode(@PathVariable String code) {
CommonCodeDto commonCode = commonCodeService.getCode(code);
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_RETRIEVED, commonCode));
}
@LogExecution("그룹별 공통 코드 조회")
@Operation(summary = "그룹별 공통 코드 조회", description = "특정 그룹에 속한 공통 코드들을 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "그룹별 공통 코드 조회 성공")
})
@GetMapping("/group/{groupCode}/codes")
public ResponseEntity<ApiResponseDto<List<CommonCodeDto>>> getCodesByGroupCode(@PathVariable String groupCode) {
List<CommonCodeDto> commonCodes = commonCodeService.getActiveCodesByGroupCode(groupCode);
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_RETRIEVED, commonCodes));
}
@LogExecution("상위 코드별 공통 코드 조회")
@Operation(summary = "상위 코드별 공통 코드 조회", description = "특정 상위 코드에 속한 공통 코드들을 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "상위 코드별 공통 코드 조회 성공")
})
@GetMapping("/parent/{parentCode}/codes")
public ResponseEntity<ApiResponseDto<List<CommonCodeDto>>> getCodesByParentCode(@PathVariable String parentCode) {
List<CommonCodeDto> commonCodes = commonCodeService.getActiveCodesByParentCode(parentCode);
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_RETRIEVED, commonCodes));
}
@LogExecution("전체 공통 코드 조회")
@Operation(summary = "전체 공통 코드 조회", description = "모든 공통 코드를 조회합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "전체 공통 코드 조회 성공")
})
@GetMapping
public ResponseEntity<ApiResponseDto<List<CommonCodeDto>>> getAllCodes() {
List<CommonCodeDto> commonCodes = commonCodeService.getAllCodes();
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_RETRIEVED, commonCodes));
}
}

View File

@@ -0,0 +1,27 @@
package com.bio.bio_backend.domain.admin.common_code.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CommonCodeDto {
private Long oid;
private String code;
private String name;
private String description;
private String groupCode;
private String parentCode;
private String characterRef1;
private String characterRef2;
private String characterRef3;
private String characterRef4;
private String characterRef5;
private Integer sortOrder;
private Boolean useFlag;
}

View File

@@ -0,0 +1,24 @@
package com.bio.bio_backend.domain.admin.common_code.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CommonGroupCodeDto {
private Long oid;
private String code;
private String name;
private String characterRef1Title;
private String characterRef2Title;
private String characterRef3Title;
private String characterRef4Title;
private String characterRef5Title;
private Integer sortOrder;
private Boolean useFlag;
}

View File

@@ -0,0 +1,55 @@
package com.bio.bio_backend.domain.admin.common_code.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CreateCommonCodeRequestDto {
@NotBlank(message = "코드는 필수입니다")
@Size(max = 50, message = "코드는 50자를 초과할 수 없습니다")
private String code;
@NotBlank(message = "이름은 필수입니다")
@Size(max = 100, message = "이름은 100자를 초과할 수 없습니다")
private String name;
@Size(max = 500, message = "설명은 500자를 초과할 수 없습니다")
private String description;
@NotBlank(message = "그룹 코드는 필수입니다")
@Size(max = 50, message = "그룹 코드는 50자를 초과할 수 없습니다")
private String groupCode;
@Size(max = 50, message = "상위 코드는 50자를 초과할 수 없습니다")
private String parentCode;
@Size(max = 100, message = "문자 참조1은 100자를 초과할 수 없습니다")
private String characterRef1;
@Size(max = 100, message = "문자 참조2는 100자를 초과할 수 없습니다")
private String characterRef2;
@Size(max = 100, message = "문자 참조3은 100자를 초과할 수 없습니다")
private String characterRef3;
@Size(max = 100, message = "문자 참조4는 100자를 초과할 수 없습니다")
private String characterRef4;
@Size(max = 100, message = "문자 참조5는 100자를 초과할 수 없습니다")
private String characterRef5;
@Builder.Default
private Integer sortOrder = 0;
@Builder.Default
private Boolean useFlag = true;
}

View File

@@ -0,0 +1,27 @@
package com.bio.bio_backend.domain.admin.common_code.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CreateCommonCodeResponseDto {
private Long oid;
private String code;
private String name;
private String description;
private String groupCode;
private String parentCode;
private String characterRef1;
private String characterRef2;
private String characterRef3;
private String characterRef4;
private String characterRef5;
private Integer sortOrder;
private Boolean useFlag;
}

View File

@@ -0,0 +1,45 @@
package com.bio.bio_backend.domain.admin.common_code.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CreateCommonGroupCodeRequestDto {
@NotBlank(message = "코드는 필수입니다")
@Size(max = 50, message = "코드는 50자를 초과할 수 없습니다")
private String code;
@NotBlank(message = "이름은 필수입니다")
@Size(max = 100, message = "이름은 100자를 초과할 수 없습니다")
private String name;
@Size(max = 100, message = "차트 참조1 제목은 100자를 초과할 수 없습니다")
private String characterRef1Title;
@Size(max = 100, message = "차트 참조2 제목은 100자를 초과할 수 없습니다")
private String characterRef2Title;
@Size(max = 100, message = "차트 참조3 제목은 100자를 초과할 수 없습니다")
private String characterRef3Title;
@Size(max = 100, message = "차트 참조4 제목은 100자를 초과할 수 없습니다")
private String characterRef4Title;
@Size(max = 100, message = "차트 참조5 제목은 100자를 초과할 수 없습니다")
private String characterRef5Title;
@Builder.Default
private Integer sortOrder = 0;
@Builder.Default
private Boolean useFlag = true;
}

View File

@@ -0,0 +1,24 @@
package com.bio.bio_backend.domain.admin.common_code.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CreateCommonGroupCodeResponseDto {
private Long oid;
private String code;
private String name;
private String characterRef1Title;
private String characterRef2Title;
private String characterRef3Title;
private String characterRef4Title;
private String characterRef5Title;
private Integer sortOrder;
private Boolean useFlag;
}

View File

@@ -0,0 +1,49 @@
package com.bio.bio_backend.domain.admin.common_code.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UpdateCommonCodeRequestDto {
@NotBlank(message = "이름은 필수입니다")
@Size(max = 100, message = "이름은 100자를 초과할 수 없습니다")
private String name;
@Size(max = 500, message = "설명은 500자를 초과할 수 없습니다")
private String description;
@NotBlank(message = "그룹 코드는 필수입니다")
@Size(max = 50, message = "그룹 코드는 50자를 초과할 수 없습니다")
private String groupCode;
@Size(max = 50, message = "상위 코드는 50자를 초과할 수 없습니다")
private String parentCode;
@Size(max = 100, message = "문자 참조1은 100자를 초과할 수 없습니다")
private String characterRef1;
@Size(max = 100, message = "문자 참조2는 100자를 초과할 수 없습니다")
private String characterRef2;
@Size(max = 100, message = "문자 참조3은 100자를 초과할 수 없습니다")
private String characterRef3;
@Size(max = 100, message = "문자 참조4는 100자를 초과할 수 없습니다")
private String characterRef4;
@Size(max = 100, message = "문자 참조5는 100자를 초과할 수 없습니다")
private String characterRef5;
private Integer sortOrder;
private Boolean useFlag;
}

View File

@@ -0,0 +1,39 @@
package com.bio.bio_backend.domain.admin.common_code.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class UpdateCommonGroupCodeRequestDto {
@NotBlank(message = "이름은 필수입니다")
@Size(max = 100, message = "이름은 100자를 초과할 수 없습니다")
private String name;
@Size(max = 100, message = "문자 참조1 제목은 100자를 초과할 수 없습니다")
private String characterRef1Title;
@Size(max = 100, message = "문자 참조2 제목은 100자를 초과할 수 없습니다")
private String characterRef2Title;
@Size(max = 100, message = "문자 참조3 제목은 100자를 초과할 수 없습니다")
private String characterRef3Title;
@Size(max = 100, message = "문자 참조4 제목은 100자를 초과할 수 없습니다")
private String characterRef4Title;
@Size(max = 100, message = "문자 참조5 제목은 100자를 초과할 수 없습니다")
private String characterRef5Title;
private Integer sortOrder;
private Boolean useFlag;
}

View File

@@ -0,0 +1,75 @@
package com.bio.bio_backend.domain.admin.common_code.entity;
import com.bio.bio_backend.global.constants.AppConstants;
import com.bio.bio_backend.global.entity.BaseEntity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(
name = AppConstants.TABLE_PREFIX + "common_code",
indexes = {
@Index(name = "idx_common_code_code", columnList = "code"),
@Index(name = "idx_common_code_group_code", columnList = "group_code"),
@Index(name = "idx_common_code_parent_code", columnList = "parent_code")
}
)
public class CommonCode extends BaseEntity {
@Column(name = "code", nullable = false, length = 50, unique = true)
private String code;
@Column(name = "name", nullable = false, length = 100)
private String name;
@Column(name = "description", length = 500)
private String description;
@Column(name = "group_code", nullable = false, length = 50)
private String groupCode;
@Column(name = "parent_code", length = 50)
private String parentCode;
@Column(name = "character_ref1", length = 100)
private String characterRef1;
@Column(name = "character_ref2", length = 100)
private String characterRef2;
@Column(name = "character_ref3", length = 100)
private String characterRef3;
@Column(name = "character_ref4", length = 100)
private String characterRef4;
@Column(name = "character_ref5", length = 100)
private String characterRef5;
@Column(name = "sort_order", nullable = false)
@Builder.Default
private Integer sortOrder = 0;
@Column(name = "use_flag", nullable = false)
@Builder.Default
private Boolean useFlag = true;
// 관계 설정
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "group_code",
referencedColumnName = "code",
insertable = false,
updatable = false,
foreignKey = @ForeignKey(value = ConstraintMode.NO_CONSTRAINT)
)
private CommonGroupCode commonGroupCode;
}

View File

@@ -0,0 +1,53 @@
package com.bio.bio_backend.domain.admin.common_code.entity;
import com.bio.bio_backend.global.constants.AppConstants;
import com.bio.bio_backend.global.entity.BaseEntity;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(
name = AppConstants.TABLE_PREFIX + "common_group_code",
indexes = {
@Index(name = "idx_common_group_code_code", columnList = "code")
}
)
public class CommonGroupCode extends BaseEntity {
@Column(name = "code", nullable = false, length = 50, unique = true)
private String code;
@Column(name = "name", nullable = false, length = 100)
private String name;
@Column(name = "character_ref1_title", length = 100)
private String characterRef1Title;
@Column(name = "character_ref2_title", length = 100)
private String characterRef2Title;
@Column(name = "character_ref3_title", length = 100)
private String characterRef3Title;
@Column(name = "character_ref4_title", length = 100)
private String characterRef4Title;
@Column(name = "character_ref5_title", length = 100)
private String characterRef5Title;
@Column(name = "sort_order", nullable = false)
@Builder.Default
private Integer sortOrder = 0;
@Column(name = "use_flag", nullable = false)
@Builder.Default
private Boolean useFlag = true;
}

View File

@@ -0,0 +1,58 @@
package com.bio.bio_backend.domain.admin.common_code.mapper;
import com.bio.bio_backend.domain.admin.common_code.dto.CommonCodeDto;
import com.bio.bio_backend.domain.admin.common_code.dto.CreateCommonCodeRequestDto;
import com.bio.bio_backend.domain.admin.common_code.dto.CreateCommonCodeResponseDto;
import com.bio.bio_backend.domain.admin.common_code.dto.UpdateCommonCodeRequestDto;
import com.bio.bio_backend.domain.admin.common_code.entity.CommonCode;
import com.bio.bio_backend.global.annotation.IgnoreBaseEntityMapping;
import com.bio.bio_backend.global.config.GlobalMapperConfig;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import java.util.List;
@Mapper(config = GlobalMapperConfig.class)
public interface CommonCodeMapper {
/**
* CommonCode 엔티티를 CommonCodeDto로 변환
*/
CommonCodeDto toCommonCodeDto(CommonCode commonCode);
/**
* CommonCodeDto를 CommonCode 엔티티로 변환
*/
@Mapping(target = "commonGroupCode", ignore = true)
CommonCode toCommonCode(CommonCodeDto commonCodeDto);
/**
* CommonCode 엔티티 리스트를 CommonCodeDto 리스트로 변환
*/
List<CommonCodeDto> toCommonCodeDtoList(List<CommonCode> commonCodes);
/**
* CommonCodeDto를 CreateCommonCodeResponseDto로 변환
*/
CreateCommonCodeResponseDto toCreateCommonCodeResponseDto(CommonCodeDto commonCodeDto);
/**
* CreateCommonCodeRequestDto를 CommonCodeDto로 변환
*/
@Mapping(target = "oid", ignore = true)
CommonCodeDto toCommonCodeDto(CreateCommonCodeRequestDto createRequestDto);
/**
* UpdateCommonCodeRequestDto를 CommonCodeDto로 변환
*/
@Mapping(target = "oid", ignore = true)
@Mapping(target = "code", ignore = true)
CommonCodeDto toCommonCodeDto(UpdateCommonCodeRequestDto updateRequestDto);
/**
* CommonCodeDto의 값으로 CommonCode 엔티티를 업데이트
*/
@IgnoreBaseEntityMapping
@Mapping(target = "commonGroupCode", ignore = true)
void updateCommonCodeFromDto(CommonCodeDto commonCodeDto, @org.mapstruct.MappingTarget CommonCode commonCode);
}

View File

@@ -0,0 +1,56 @@
package com.bio.bio_backend.domain.admin.common_code.mapper;
import com.bio.bio_backend.domain.admin.common_code.dto.CommonGroupCodeDto;
import com.bio.bio_backend.domain.admin.common_code.dto.CreateCommonGroupCodeRequestDto;
import com.bio.bio_backend.domain.admin.common_code.dto.CreateCommonGroupCodeResponseDto;
import com.bio.bio_backend.domain.admin.common_code.dto.UpdateCommonGroupCodeRequestDto;
import com.bio.bio_backend.domain.admin.common_code.entity.CommonGroupCode;
import com.bio.bio_backend.global.annotation.IgnoreBaseEntityMapping;
import com.bio.bio_backend.global.config.GlobalMapperConfig;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import java.util.List;
@Mapper(config = GlobalMapperConfig.class)
public interface CommonGroupCodeMapper {
/**
* CommonGroupCode 엔티티를 CommonGroupCodeDto로 변환
*/
CommonGroupCodeDto toCommonGroupCodeDto(CommonGroupCode commonGroupCode);
/**
* CommonGroupCodeDto를 CommonGroupCode 엔티티로 변환
*/
CommonGroupCode toCommonGroupCode(CommonGroupCodeDto commonGroupCodeDto);
/**
* CommonGroupCode 엔티티 리스트를 CommonGroupCodeDto 리스트로 변환
*/
List<CommonGroupCodeDto> toCommonGroupCodeDtoList(List<CommonGroupCode> commonGroupCodes);
/**
* CommonGroupCodeDto를 CreateCommonGroupCodeResponseDto로 변환
*/
CreateCommonGroupCodeResponseDto toCreateCommonGroupCodeResponseDto(CommonGroupCodeDto commonGroupCodeDto);
/**
* CreateCommonGroupCodeRequestDto를 CommonGroupCodeDto로 변환
*/
@Mapping(target = "oid", ignore = true)
CommonGroupCodeDto toCommonGroupCodeDto(CreateCommonGroupCodeRequestDto createRequestDto);
/**
* UpdateCommonGroupCodeRequestDto를 CommonGroupCodeDto로 변환
*/
@Mapping(target = "oid", ignore = true)
@Mapping(target = "code", ignore = true)
CommonGroupCodeDto toCommonGroupCodeDto(UpdateCommonGroupCodeRequestDto updateRequestDto);
/**
* CommonGroupCodeDto의 값으로 CommonGroupCode 엔티티를 업데이트
*/
@IgnoreBaseEntityMapping
void updateCommonGroupCodeFromDto(CommonGroupCodeDto commonGroupCodeDto, @org.mapstruct.MappingTarget CommonGroupCode commonGroupCode);
}

View File

@@ -0,0 +1,32 @@
package com.bio.bio_backend.domain.admin.common_code.repository;
import com.bio.bio_backend.domain.admin.common_code.entity.CommonCode;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface CommonCodeRepository extends JpaRepository<CommonCode, Long> {
Optional<CommonCode> findByCode(String code);
List<CommonCode> findByGroupCodeAndUseFlagOrderBySortOrderAsc(String groupCode, Boolean useFlag);
List<CommonCode> findByParentCodeAndUseFlagOrderBySortOrderAsc(String parentCode, Boolean useFlag);
@Query("SELECT cc FROM CommonCode cc WHERE cc.groupCode = :groupCode AND cc.useFlag = :useFlag ORDER BY cc.sortOrder ASC")
List<CommonCode> findActiveCodesByGroupCodeOrderBySortOrder(@Param("groupCode") String groupCode, @Param("useFlag") Boolean useFlag);
@Query("SELECT cc FROM CommonCode cc WHERE cc.parentCode = :parentCode AND cc.useFlag = :useFlag ORDER BY cc.sortOrder ASC")
List<CommonCode> findActiveCodesByParentCodeOrderBySortOrder(@Param("parentCode") String parentCode, @Param("useFlag") Boolean useFlag);
boolean existsByCode(String code);
boolean existsByGroupCode(String groupCode);
boolean existsByParentCode(String parentCode);
}

View File

@@ -0,0 +1,23 @@
package com.bio.bio_backend.domain.admin.common_code.repository;
import com.bio.bio_backend.domain.admin.common_code.entity.CommonGroupCode;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface CommonGroupCodeRepository extends JpaRepository<CommonGroupCode, Long> {
Optional<CommonGroupCode> findByCode(String code);
List<CommonGroupCode> findByUseFlagOrderBySortOrderAsc(Boolean useFlag);
@Query("SELECT cgc FROM CommonGroupCode cgc WHERE cgc.useFlag = :useFlag ORDER BY cgc.sortOrder ASC")
List<CommonGroupCode> findActiveGroupCodesOrderBySortOrder(@Param("useFlag") Boolean useFlag);
boolean existsByCode(String code);
}

View File

@@ -0,0 +1,28 @@
package com.bio.bio_backend.domain.admin.common_code.service;
import com.bio.bio_backend.domain.admin.common_code.dto.CommonCodeDto;
import com.bio.bio_backend.domain.admin.common_code.dto.CommonGroupCodeDto;
import java.util.List;
public interface CommonCodeService {
// 그룹 코드 관련
CommonGroupCodeDto createGroupCode(CommonGroupCodeDto groupCodeDto);
void updateGroupCode(String code, CommonGroupCodeDto groupCodeDto);
void deleteGroupCode(String code);
CommonGroupCodeDto getGroupCode(String code);
List<CommonGroupCodeDto> getAllGroupCodes();
List<CommonGroupCodeDto> getActiveGroupCodes();
// 공통 코드 관련
CommonCodeDto createCode(CommonCodeDto codeDto);
void updateCode(String code, CommonCodeDto codeDto);
void deleteCode(String code);
CommonCodeDto getCode(String code);
List<CommonCodeDto> getCodesByGroupCode(String groupCode);
List<CommonCodeDto> getActiveCodesByGroupCode(String groupCode);
List<CommonCodeDto> getCodesByParentCode(String parentCode);
List<CommonCodeDto> getActiveCodesByParentCode(String parentCode);
List<CommonCodeDto> getAllCodes();
}

View File

@@ -0,0 +1,166 @@
package com.bio.bio_backend.domain.admin.common_code.service;
import com.bio.bio_backend.domain.admin.common_code.dto.CommonCodeDto;
import com.bio.bio_backend.domain.admin.common_code.dto.CommonGroupCodeDto;
import com.bio.bio_backend.domain.admin.common_code.entity.CommonCode;
import com.bio.bio_backend.domain.admin.common_code.entity.CommonGroupCode;
import com.bio.bio_backend.domain.admin.common_code.mapper.CommonCodeMapper;
import com.bio.bio_backend.domain.admin.common_code.mapper.CommonGroupCodeMapper;
import com.bio.bio_backend.domain.admin.common_code.repository.CommonCodeRepository;
import com.bio.bio_backend.domain.admin.common_code.repository.CommonGroupCodeRepository;
import com.bio.bio_backend.global.constants.AppConstants;
import com.bio.bio_backend.global.exception.ApiException;
import com.bio.bio_backend.global.constants.ApiResponseCode;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Slf4j
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class CommonCodeServiceImpl implements CommonCodeService {
private final CommonGroupCodeRepository commonGroupCodeRepository;
private final CommonCodeRepository commonCodeRepository;
private final CommonCodeMapper commonCodeMapper;
private final CommonGroupCodeMapper commonGroupCodeMapper;
// 그룹 코드 관련 메서드들
@Override
@Transactional
public CommonGroupCodeDto createGroupCode(CommonGroupCodeDto groupCodeDto) {
if (commonGroupCodeRepository.existsByCode(groupCodeDto.getCode())) {
throw new ApiException(ApiResponseCode.COMMON_CODE_DUPLICATE);
}
CommonGroupCode groupCode = commonGroupCodeMapper.toCommonGroupCode(groupCodeDto);
CommonGroupCode savedGroupCode = commonGroupCodeRepository.save(groupCode);
return commonGroupCodeMapper.toCommonGroupCodeDto(savedGroupCode);
}
@Override
@Transactional
public void updateGroupCode(String code, CommonGroupCodeDto groupCodeDto) {
CommonGroupCode existingGroupCode = commonGroupCodeRepository.findByCode(code)
.orElseThrow(() -> new ApiException(ApiResponseCode.COMMON_NOT_FOUND, "그룹 코드를 찾을 수 없습니다: " + code));
commonGroupCodeMapper.updateCommonGroupCodeFromDto(groupCodeDto, existingGroupCode);
commonGroupCodeRepository.save(existingGroupCode);
}
@Override
@Transactional
public void deleteGroupCode(String code) {
CommonGroupCode groupCode = commonGroupCodeRepository.findByCode(code)
.orElseThrow(() -> new ApiException(ApiResponseCode.COMMON_NOT_FOUND, "그룹 코드를 찾을 수 없습니다: " + code));
// 하위 공통 코드가 있는지 확인
if (commonCodeRepository.existsByGroupCode(code)) {
throw new ApiException(ApiResponseCode.COMMON_BAD_REQUEST, "하위 공통 코드가 존재하여 삭제할 수 없습니다: " + code);
}
commonGroupCodeRepository.delete(groupCode);
}
@Override
public CommonGroupCodeDto getGroupCode(String code) {
CommonGroupCode groupCode = commonGroupCodeRepository.findByCode(code)
.orElseThrow(() -> new ApiException(ApiResponseCode.COMMON_NOT_FOUND, "그룹 코드를 찾을 수 없습니다: " + code));
return commonGroupCodeMapper.toCommonGroupCodeDto(groupCode);
}
@Override
public List<CommonGroupCodeDto> getAllGroupCodes() {
List<CommonGroupCode> groupCodes = commonGroupCodeRepository.findAll();
return commonGroupCodeMapper.toCommonGroupCodeDtoList(groupCodes);
}
@Override
public List<CommonGroupCodeDto> getActiveGroupCodes() {
List<CommonGroupCode> groupCodes = commonGroupCodeRepository.findByUseFlagOrderBySortOrderAsc(true);
return commonGroupCodeMapper.toCommonGroupCodeDtoList(groupCodes);
}
// 공통 코드 관련 메서드들
@Override
@Transactional
public CommonCodeDto createCode(CommonCodeDto commonCodeDto) {
if (commonCodeRepository.existsByCode(commonCodeDto.getCode())) {
throw new ApiException(ApiResponseCode.USER_ID_DUPLICATE, "이미 존재하는 공통 코드입니다: " + commonCodeDto.getCode());
}
// 그룹 코드 존재 여부 확인
if (!commonGroupCodeRepository.existsByCode(commonCodeDto.getGroupCode())) {
throw new ApiException(ApiResponseCode.COMMON_BAD_REQUEST, "존재하지 않는 그룹 코드입니다: " + commonCodeDto.getGroupCode());
}
CommonCode commonCode = commonCodeMapper.toCommonCode(commonCodeDto);
CommonCode savedCommonCode = commonCodeRepository.save(commonCode);
return commonCodeMapper.toCommonCodeDto(savedCommonCode);
}
@Override
@Transactional
public void updateCode(String code, CommonCodeDto commonCodeDto) {
CommonCode existingCommonCode = commonCodeRepository.findByCode(code)
.orElseThrow(() -> new ApiException(ApiResponseCode.COMMON_NOT_FOUND, "공통 코드를 찾을 수 없습니다: " + code));
commonCodeMapper.updateCommonCodeFromDto(commonCodeDto, existingCommonCode);
commonCodeRepository.save(existingCommonCode);
}
@Override
@Transactional
public void deleteCode(String code) {
CommonCode commonCode = commonCodeRepository.findByCode(code)
.orElseThrow(() -> new ApiException(ApiResponseCode.COMMON_NOT_FOUND, "공통 코드를 찾을 수 없습니다: " + code));
// 하위 공통 코드가 있는지 확인
if (commonCodeRepository.existsByParentCode(code)) {
throw new ApiException(ApiResponseCode.COMMON_BAD_REQUEST, "하위 공통 코드가 존재하여 삭제할 수 없습니다: " + code);
}
commonCodeRepository.delete(commonCode);
}
@Override
public CommonCodeDto getCode(String code) {
CommonCode commonCode = commonCodeRepository.findByCode(code)
.orElseThrow(() -> new ApiException(ApiResponseCode.COMMON_NOT_FOUND, "공통 코드를 찾을 수 없습니다: " + code));
return commonCodeMapper.toCommonCodeDto(commonCode);
}
@Override
public List<CommonCodeDto> getCodesByGroupCode(String groupCode) {
List<CommonCode> commonCodes = commonCodeRepository.findByGroupCodeAndUseFlagOrderBySortOrderAsc(groupCode, true);
return commonCodeMapper.toCommonCodeDtoList(commonCodes);
}
@Override
public List<CommonCodeDto> getActiveCodesByGroupCode(String groupCode) {
List<CommonCode> commonCodes = commonCodeRepository.findActiveCodesByGroupCodeOrderBySortOrder(groupCode, true);
return commonCodeMapper.toCommonCodeDtoList(commonCodes);
}
@Override
public List<CommonCodeDto> getCodesByParentCode(String parentCode) {
List<CommonCode> commonCodes = commonCodeRepository.findByParentCodeAndUseFlagOrderBySortOrderAsc(parentCode, true);
return commonCodeMapper.toCommonCodeDtoList(commonCodes);
}
@Override
public List<CommonCodeDto> getActiveCodesByParentCode(String parentCode) {
List<CommonCode> commonCodes = commonCodeRepository.findActiveCodesByParentCodeOrderBySortOrder(parentCode, true);
return commonCodeMapper.toCommonCodeDtoList(commonCodes);
}
@Override
public List<CommonCodeDto> getAllCodes() {
List<CommonCode> commonCodes = commonCodeRepository.findAll();
return commonCodeMapper.toCommonCodeDtoList(commonCodes);
}
}

View File

@@ -0,0 +1,16 @@
package com.bio.bio_backend.domain.base.file.dto;
import lombok.Builder;
import lombok.Data;
import java.util.List;
@Data
@Builder
public class FileUploadDto {
private Long groupOid;
private List<FileUploadResponseDto> files; // 파일 정보들
private int totalCount;
private int successCount;
private int failureCount;
private List<String> errorMessages;
}

View File

@@ -7,5 +7,4 @@ import org.springframework.web.multipart.MultipartFile;
public class FileUploadRequestDto { public class FileUploadRequestDto {
private MultipartFile file; private MultipartFile file;
private String description; private String description;
private Long groupOid;
} }

View File

@@ -1,12 +1,15 @@
package com.bio.bio_backend.domain.base.file.dto; package com.bio.bio_backend.domain.base.file.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Builder; import lombok.Builder;
import lombok.Data; import lombok.Data;
@Data @Data
@Builder @Builder
@JsonInclude(JsonInclude.Include.NON_NULL)
public class FileUploadResponseDto { public class FileUploadResponseDto {
private Long oid; private Long oid;
private Long groupOid;
private String originalFileName; private String originalFileName;
private String downloadUrl; private String downloadUrl;
} }

View File

@@ -1,10 +1,12 @@
package com.bio.bio_backend.domain.base.file.dto; package com.bio.bio_backend.domain.base.file.dto;
import lombok.Builder;
import lombok.Data; import lombok.Data;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.util.List; import java.util.List;
@Data @Data
@Builder
public class MultipleFileUploadRequestDto { public class MultipleFileUploadRequestDto {
private List<MultipartFile> files; private List<MultipartFile> files;
private String description; private String description;

View File

@@ -8,6 +8,7 @@ import java.util.List;
@Builder @Builder
public class MultipleFileUploadResponseDto { public class MultipleFileUploadResponseDto {
private List<FileUploadResponseDto> files; private List<FileUploadResponseDto> files;
private Long groupOid;
private int totalCount; private int totalCount;
private int successCount; private int successCount;
private int failureCount; private int failureCount;

View File

@@ -57,7 +57,12 @@ public class FileServiceImpl implements FileService {
File savedFile = processFileUpload(multipartFile, requestDto.getDescription(), generateOid()); File savedFile = processFileUpload(multipartFile, requestDto.getDescription(), generateOid());
// 응답 DTO 생성 및 반환 // 응답 DTO 생성 및 반환
return createUploadResponse(savedFile); return FileUploadResponseDto.builder()
.oid(savedFile.getOid())
.groupOid(savedFile.getGroupOid())
.originalFileName(savedFile.getOriginalFileName())
.downloadUrl(contextPath + "/files/download/" + savedFile.getOid())
.build();
} }
@Override @Override
@@ -90,7 +95,11 @@ public class FileServiceImpl implements FileService {
// 단일 파일 업로드 처리 // 단일 파일 업로드 처리
File savedFile = processFileUpload(multipartFile, requestDto.getDescription(), groupOid); File savedFile = processFileUpload(multipartFile, requestDto.getDescription(), groupOid);
FileUploadResponseDto uploadedFile = createUploadResponse(savedFile); FileUploadResponseDto uploadedFile = FileUploadResponseDto.builder()
.oid(savedFile.getOid())
.originalFileName(savedFile.getOriginalFileName())
.downloadUrl(contextPath + "/files/download/" + savedFile.getOid())
.build();
uploadedFiles.add(uploadedFile); uploadedFiles.add(uploadedFile);
successCount++; successCount++;
@@ -105,8 +114,8 @@ public class FileServiceImpl implements FileService {
} }
} }
// 다중 파일 업로드 결과 반환
return MultipleFileUploadResponseDto.builder() return MultipleFileUploadResponseDto.builder()
.groupOid(groupOid)
.files(uploadedFiles) .files(uploadedFiles)
.totalCount(files.size()) .totalCount(files.size())
.successCount(successCount) .successCount(successCount)
@@ -135,7 +144,6 @@ public class FileServiceImpl implements FileService {
// DB에 파일 정보 저장 // DB에 파일 정보 저장
File file = createFileEntity(originalFileName, storedFileName, targetLocation, multipartFile, description, groupOid); File file = createFileEntity(originalFileName, storedFileName, targetLocation, multipartFile, description, groupOid);
file.setCreator(SecurityUtils.getCurrentUserOid(), SecurityUtils.getCurrentUserId());
return fileRepository.save(file); return fileRepository.save(file);
@@ -158,14 +166,6 @@ public class FileServiceImpl implements FileService {
.build(); .build();
} }
private FileUploadResponseDto createUploadResponse(File savedFile) {
return FileUploadResponseDto.builder()
.oid(savedFile.getOid())
.originalFileName(savedFile.getOriginalFileName())
.downloadUrl(contextPath + "/files/download/" + savedFile.getOid())
.build();
}
@Override @Override
public File getFileByOid(Long oid) { public File getFileByOid(Long oid) {
return fileRepository.findByOidAndUseFlagTrue(oid) return fileRepository.findByOidAndUseFlagTrue(oid)
@@ -192,16 +192,12 @@ public class FileServiceImpl implements FileService {
File file = fileRepository.findByOidAndUseFlagTrue(oid) File file = fileRepository.findByOidAndUseFlagTrue(oid)
.orElseThrow(() -> new ApiException(ApiResponseCode.FILE_NOT_FOUND)); .orElseThrow(() -> new ApiException(ApiResponseCode.FILE_NOT_FOUND));
Long currentUserOid = SecurityUtils.getCurrentUserOid();
String currentUserId = SecurityUtils.getCurrentUserId(); String currentUserId = SecurityUtils.getCurrentUserId();
// 현재 사용자가 파일 소유자인지 확인 // 현재 사용자가 파일 소유자인지 확인
if (currentUserId == null || !currentUserId.equals(file.getCreatedId())) { if (currentUserId == null || !currentUserId.equals(file.getCreatedId())) {
throw new ApiException(ApiResponseCode.COMMON_FORBIDDEN); throw new ApiException(ApiResponseCode.COMMON_FORBIDDEN);
} }
// 수정자 정보 업데이트
file.setUpdater(currentUserOid, currentUserId);
// 논리적 삭제: use_flag를 false로 변경 // 논리적 삭제: use_flag를 false로 변경
file.setUseFlag(false); file.setUseFlag(false);
fileRepository.save(file); fileRepository.save(file);

View File

@@ -5,16 +5,15 @@ import com.bio.bio_backend.domain.base.member.dto.CreateMemberResponseDto;
import com.bio.bio_backend.domain.base.member.dto.MemberDto; import com.bio.bio_backend.domain.base.member.dto.MemberDto;
import com.bio.bio_backend.domain.base.member.entity.Member; import com.bio.bio_backend.domain.base.member.entity.Member;
import com.bio.bio_backend.global.annotation.IgnoreBaseEntityMapping; import com.bio.bio_backend.global.annotation.IgnoreBaseEntityMapping;
import com.bio.bio_backend.global.config.GlobalMapperConfig;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
import java.util.List; import java.util.List;
@Mapper(componentModel = "spring") @Mapper(config = GlobalMapperConfig.class)
public interface MemberMapper { public interface MemberMapper {
MemberMapper INSTANCE = Mappers.getMapper(MemberMapper.class);
/** /**
* CreateMemberRequestDto를 MemberDto로 변환 * CreateMemberRequestDto를 MemberDto로 변환
* 기본값 설정: role = MemberRole.MEMBER, useFlag = true * 기본값 설정: role = MemberRole.MEMBER, useFlag = true

View File

@@ -8,6 +8,7 @@ import com.bio.bio_backend.domain.base.member.repository.MemberRepository;
import com.bio.bio_backend.global.exception.ApiException; import com.bio.bio_backend.global.exception.ApiException;
import com.bio.bio_backend.global.constants.ApiResponseCode; import com.bio.bio_backend.global.constants.ApiResponseCode;
import com.bio.bio_backend.global.constants.AppConstants; import com.bio.bio_backend.global.constants.AppConstants;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetails;
@@ -19,8 +20,6 @@ import org.springframework.transaction.annotation.Transactional;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static com.bio.bio_backend.global.utils.OidUtils.generateOid;
@Service @Service
@RequiredArgsConstructor @RequiredArgsConstructor
@Slf4j @Slf4j
@@ -54,9 +53,10 @@ public class MemberServiceImpl implements MemberService {
.role(MemberRole.getDefault()) .role(MemberRole.getDefault())
.build(); .build();
Long oid = generateOid(); member.setCreatedOid(AppConstants.ADMIN_OID);
member.setOid(oid); member.setCreatedId(AppConstants.ADMIN_USER_ID);
member.setCreator(AppConstants.ADMIN_OID, AppConstants.ADMIN_USER_ID); member.setUpdatedOid(AppConstants.ADMIN_OID);
member.setUpdatedId(AppConstants.ADMIN_USER_ID);
Member savedMember = memberRepository.save(member); Member savedMember = memberRepository.save(member);

View File

@@ -0,0 +1,21 @@
package com.bio.bio_backend.global.config;
import org.mapstruct.*;
@MapperConfig(
componentModel = "spring",
// null 값은 매핑하지 않음 (부분 업데이트)
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
// NPE 방지용 null 체크
nullValueCheckStrategy = NullValueCheckStrategy.ALWAYS,
// 매핑 누락 시 컴파일 오류
unmappedTargetPolicy = ReportingPolicy.ERROR,
// 컬렉션 매핑 전략
collectionMappingStrategy = CollectionMappingStrategy.ACCESSOR_ONLY
)
public interface GlobalMapperConfig {
}

View File

@@ -14,7 +14,7 @@ public enum ApiResponseCode {
/*공통 Code*/ /*공통 Code*/
// 200 OK // 200 OK
COMMON_SUCCESS(HttpStatus.OK.value(), "요청 성공"), COMMON_SUCCESS(HttpStatus.OK.value(), "요청 성공하였습니다"),
COMMON_SUCCESS_CREATED(HttpStatus.CREATED.value(), "성공적으로 생성되었습니다"), COMMON_SUCCESS_CREATED(HttpStatus.CREATED.value(), "성공적으로 생성되었습니다"),
COMMON_SUCCESS_UPDATED(HttpStatus.OK.value(), "성공적으로 수정되었습니다"), COMMON_SUCCESS_UPDATED(HttpStatus.OK.value(), "성공적으로 수정되었습니다"),
COMMON_SUCCESS_DELETED(HttpStatus.OK.value(), "성공적으로 삭제되었습니다"), COMMON_SUCCESS_DELETED(HttpStatus.OK.value(), "성공적으로 삭제되었습니다"),
@@ -39,6 +39,7 @@ public enum ApiResponseCode {
// 409 Conflict // 409 Conflict
COMMON_CONFLICT(HttpStatus.CONFLICT.value(), "충돌이 발생했습니다"), COMMON_CONFLICT(HttpStatus.CONFLICT.value(), "충돌이 발생했습니다"),
COMMON_CODE_DUPLICATE(HttpStatus.CONFLICT.value(), "동일한 코드가 존재합니다"),
// 500 Internal Server Error // 500 Internal Server Error
COMMON_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "서버에서 오류가 발생했습니다"), COMMON_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "서버에서 오류가 발생했습니다"),

View File

@@ -12,14 +12,17 @@ import lombok.RequiredArgsConstructor;
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
public class ApiResponseDto<T> { public class ApiResponseDto<T> {
private static final boolean SUCCESS = true;
private static final boolean FAIL = false;
private boolean success;
private int code; private int code;
private String message; private String message;
private String description; private String description;
private T data; private T data;
private static final int SUCCESS = 200; private ApiResponseDto(boolean success, int code, String message, String description, T data){
this.success = success;
private ApiResponseDto(int code, String message, String description, T data){
this.code = code; this.code = code;
this.message = message; this.message = message;
this.description = description; this.description = description;
@@ -27,19 +30,19 @@ public class ApiResponseDto<T> {
} }
public static <T> ApiResponseDto<T> success(ApiResponseCode responseCode, T data) { public static <T> ApiResponseDto<T> success(ApiResponseCode responseCode, T data) {
return new ApiResponseDto<T>(SUCCESS, responseCode.name(), responseCode.getDescription(), data); return new ApiResponseDto<T>(SUCCESS, responseCode.getStatusCode(), responseCode.name(), responseCode.getDescription(), data);
} }
public static <T> ApiResponseDto<T> success(ApiResponseCode responseCode) { public static <T> ApiResponseDto<T> success(ApiResponseCode responseCode) {
return new ApiResponseDto<T>(SUCCESS, responseCode.name(), responseCode.getDescription(), null); return new ApiResponseDto<T>(SUCCESS, responseCode.getStatusCode(), responseCode.name(), responseCode.getDescription(), null);
} }
public static <T> ApiResponseDto<T> fail(ApiResponseCode responseCode, T data) { public static <T> ApiResponseDto<T> fail(ApiResponseCode responseCode, T data) {
return new ApiResponseDto<T>(responseCode.getStatusCode(), responseCode.name(), responseCode.getDescription(), data); return new ApiResponseDto<T>(FAIL, responseCode.getStatusCode(), responseCode.name(), responseCode.getDescription(), data);
} }
public static <T> ApiResponseDto<T> fail(ApiResponseCode responseCode) { public static <T> ApiResponseDto<T> fail(ApiResponseCode responseCode) {
return new ApiResponseDto<T>(responseCode.getStatusCode(), responseCode.name(), responseCode.getDescription(), null); return new ApiResponseDto<T>(FAIL, responseCode.getStatusCode(), responseCode.name(), responseCode.getDescription(), null);
} }
} }

View File

@@ -9,7 +9,7 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import static com.bio.bio_backend.global.utils.OidUtils.generateOid;
/** /**
* 모든 엔티티가 상속받는 기본 엔티티 클래스 * 모든 엔티티가 상속받는 기본 엔티티 클래스
@@ -18,7 +18,7 @@ import static com.bio.bio_backend.global.utils.OidUtils.generateOid;
@Getter @Getter
@Setter @Setter
@MappedSuperclass @MappedSuperclass
@EntityListeners(AuditingEntityListener.class) @EntityListeners({AuditingEntityListener.class, BaseEntityListener.class})
public abstract class BaseEntity { public abstract class BaseEntity {
@Id @Id
@@ -45,31 +45,4 @@ public abstract class BaseEntity {
@Column(name = "updated_id") @Column(name = "updated_id")
private String updatedId; private String updatedId;
@PrePersist
protected void onCreate() {
if(this.oid == null) this.oid = generateOid();
if(this.createdOid != null && this.updatedOid == null) this.updatedOid = this.createdOid;
}
/**
* 생성자 정보를 설정합니다.
* @param createdOid 생성자 OID
* @param createdId 생성자 ID
*/
public void setCreator(Long createdOid, String createdId) {
this.createdOid = createdOid;
this.createdId = createdId;
this.updatedOid = createdOid;
this.updatedId = createdId;
}
/**
* 수정자 정보를 설정합니다.
* @param updatedOid 수정자 OID
* @param updatedId 수정자 ID
*/
public void setUpdater(Long updatedOid, String updatedId) {
this.updatedOid = updatedOid;
this.updatedId = updatedId;
}
} }

View File

@@ -0,0 +1,61 @@
package com.bio.bio_backend.global.entity;
import com.bio.bio_backend.global.utils.SecurityUtils;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
import lombok.extern.slf4j.Slf4j;
import static com.bio.bio_backend.global.utils.OidUtils.generateOid;
/**
* BaseEntity의 createdOid와 updatedOid 필드를 자동으로 설정하는 엔티티 리스너
*/
@Slf4j
public class BaseEntityListener {
@PrePersist
public void prePersist(BaseEntity entity) {
if (entity.getOid() == null) {
entity.setOid(generateOid());
}
try {
String currentUserId = SecurityUtils.getCurrentUserId();
Long currentUserOid = SecurityUtils.getCurrentUserOid();
if (currentUserOid != null) {
entity.setCreatedOid(currentUserOid);
entity.setUpdatedOid(currentUserOid);
}
if (currentUserId != null) {
entity.setCreatedId(currentUserId);
entity.setUpdatedId(currentUserId);
}
} catch (SecurityException | IllegalStateException e) {
log.warn("등록자 정보 설정 실패: {}", e.getMessage());
} catch (Exception e) {
log.error("등록자 정보 설정 오류: {}", e.getMessage(), e);
}
}
@PreUpdate
public void preUpdate(BaseEntity entity) {
try {
String currentUserId = SecurityUtils.getCurrentUserId();
Long currentUserOid = SecurityUtils.getCurrentUserOid();
if (currentUserOid != null) {
entity.setUpdatedOid(currentUserOid);
}
if (currentUserId != null) {
entity.setUpdatedId(currentUserId);
}
} catch (SecurityException | IllegalStateException e) {
log.warn("수정자 정보 설정 실패: {}", e.getMessage());
} catch (Exception e) {
log.error("수정자 정보 설정 오류: {}", e.getMessage(), e);
}
}
}

View File

@@ -11,6 +11,6 @@ public class CustomIdGenerator implements IdentifierGenerator {
@Override @Override
public Serializable generate(SharedSessionContractImplementor session, Object object) { public Serializable generate(SharedSessionContractImplementor session, Object object) {
return OidUtils.generateOid(); // 재사용 return OidUtils.generateOid();
} }
} }

View File

@@ -2,21 +2,30 @@ package com.bio.bio_backend.global.utils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Path; import java.nio.file.Path;
import java.nio.file.Paths; import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.time.LocalDate; import java.time.LocalDate;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.util.UUID; import java.util.UUID;
import org.springframework.stereotype.Component;
import lombok.RequiredArgsConstructor;
import com.bio.bio_backend.domain.base.file.dto.FileUploadDto;
import com.bio.bio_backend.domain.base.file.dto.MultipleFileUploadRequestDto;
import com.bio.bio_backend.domain.base.file.dto.MultipleFileUploadResponseDto;
import com.bio.bio_backend.domain.base.file.service.FileService;
import java.util.List;
/** /**
* 파일 관련 유틸리티 클래스 * 파일 관련 유틸리티 클래스
*/ */
@Component
@RequiredArgsConstructor
public class FileUtils { public class FileUtils {
private final FileService fileService;
/** /**
* 파일 유효성 검사 * 파일 유효성 검사
*/ */
@@ -35,32 +44,6 @@ public class FileUtils {
} }
} }
/**
* 업로드 디렉토리 생성
*/
public static Path createUploadDirectory(String uploadPath) throws IOException {
Path uploadDir = Paths.get(uploadPath);
if (!Files.exists(uploadDir)) {
Files.createDirectories(uploadDir);
}
return uploadDir;
}
/**
* 년월일 기반 업로드 디렉토리 생성
* 예: uploads/2024/01/15/
*/
public static Path createDateBasedUploadDirectory(String baseUploadPath) throws IOException {
LocalDate today = LocalDate.now();
String yearMonthDay = today.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
Path dateBasedPath = Paths.get(baseUploadPath, yearMonthDay);
if (!Files.exists(dateBasedPath)) {
Files.createDirectories(dateBasedPath);
}
return dateBasedPath;
}
/** /**
* 년월 기반 업로드 디렉토리 생성 * 년월 기반 업로드 디렉토리 생성
* 예: uploads/2024/01/ * 예: uploads/2024/01/
@@ -76,35 +59,6 @@ public class FileUtils {
return yearMonthPath; return yearMonthPath;
} }
/**
* 년 기반 업로드 디렉토리 생성
* 예: uploads/2024/
*/
public static Path createYearUploadDirectory(String baseUploadPath) throws IOException {
LocalDate today = LocalDate.now();
String year = today.format(DateTimeFormatter.ofPattern("yyyy"));
Path yearPath = Paths.get(baseUploadPath, year);
if (!Files.exists(yearPath)) {
Files.createDirectories(yearPath);
}
return yearPath;
}
/**
* 지정된 날짜로 업로드 디렉토리 생성
* 예: uploads/2024/01/15/
*/
public static Path createDateBasedUploadDirectory(String baseUploadPath, LocalDate date) throws IOException {
String yearMonthDay = date.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
Path dateBasedPath = Paths.get(baseUploadPath, yearMonthDay);
if (!Files.exists(dateBasedPath)) {
Files.createDirectories(dateBasedPath);
}
return dateBasedPath;
}
/** /**
* 파일 확장자 추출 * 파일 확장자 추출
*/ */
@@ -127,7 +81,7 @@ public class FileUtils {
*/ */
public static Path saveFileToDisk(MultipartFile multipartFile, Path uploadDir, String storedFileName) throws IOException { public static Path saveFileToDisk(MultipartFile multipartFile, Path uploadDir, String storedFileName) throws IOException {
Path targetLocation = uploadDir.resolve(storedFileName); Path targetLocation = uploadDir.resolve(storedFileName);
Files.copy(multipartFile.getInputStream(), targetLocation, StandardCopyOption.REPLACE_EXISTING); Files.copy(multipartFile.getInputStream(), targetLocation, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
return targetLocation; return targetLocation;
} }
@@ -138,173 +92,44 @@ public class FileUtils {
return StringUtils.cleanPath(originalFileName); return StringUtils.cleanPath(originalFileName);
} }
/**
* 파일 크기를 사람이 읽기 쉬운 형태로 변환
*/
public static String formatFileSize(long bytes) {
if (bytes < 1024) return bytes + " B";
if (bytes < 1024 * 1024) return String.format("%.1f KB", bytes / 1024.0);
if (bytes < 1024 * 1024 * 1024) return String.format("%.1f MB", bytes / (1024.0 * 1024.0));
return String.format("%.1f GB", bytes / (1024.0 * 1024.0 * 1024.0));
}
/** /**
* 파일 확장자로부터 MIME 타입 추정 * 파일 업로드 (단일/다중 파일 모두 지원)
* 사용 예시:
* // 단일 파일 업로드
* FileUploadDto fileResult = fileUtils.uploadFile(
* requestDto.getFile(),
* "프로필 이미지"
* );
* member.setFileGroupId(fileResult.getGroupOid());
* // 다중 파일 업로드
* FileUploadDto filesResult = fileUtils.uploadFiles(
* requestDto.getFiles(),
* "게시판 첨부파일: " + board.getTitle()
* );
* board.setFileGroupId(filesResult.getGroupOid());
*/ */
public static String getMimeTypeFromExtension(String fileName) { public FileUploadDto uploadFile(MultipartFile file, String description) {
if (fileName == null) return "application/octet-stream"; // 단일 파일도 List로 감싸서 다중 파일 업로드 방식 사용
List<MultipartFile> files = List.of(file);
String extension = extractFileExtension(fileName).toLowerCase(); return uploadFiles(files, description);
switch (extension) {
case ".txt": return "text/plain";
case ".html": case ".htm": return "text/html";
case ".css": return "text/css";
case ".js": return "application/javascript";
case ".json": return "application/json";
case ".xml": return "application/xml";
case ".pdf": return "application/pdf";
case ".zip": return "application/zip";
case ".jpg": case ".jpeg": return "image/jpeg";
case ".png": return "image/png";
case ".gif": return "image/gif";
case ".bmp": return "image/bmp";
case ".svg": return "image/svg+xml";
case ".mp4": return "video/mp4";
case ".avi": return "video/x-msvideo";
case ".mp3": return "audio/mpeg";
case ".wav": return "audio/wav";
default: return "application/octet-stream";
}
} }
/** public FileUploadDto uploadFiles(List<MultipartFile> files, String description) {
* 안전한 파일명 생성 (특수문자 제거) MultipleFileUploadRequestDto requestDto = MultipleFileUploadRequestDto.builder()
*/ .files(files)
public static String createSafeFileName(String originalFileName) { .description(description)
if (originalFileName == null) return ""; .build();
// 특수문자 제거 및 공백을 언더스코어로 변경 MultipleFileUploadResponseDto response = fileService.uploadMultipleFiles(requestDto);
String safeName = originalFileName
.replaceAll("[^a-zA-Z0-9가-힣._-]", "_")
.replaceAll("_+", "_")
.trim();
// 파일명이 너무 길면 자르기 return FileUploadDto.builder()
if (safeName.length() > 100) { .groupOid(response.getGroupOid())
String extension = extractFileExtension(safeName); .files(response.getFiles())
safeName = safeName.substring(0, 100 - extension.length()) + extension; .totalCount(response.getTotalCount())
} .successCount(response.getSuccessCount())
.failureCount(response.getFailureCount())
return safeName; .errorMessages(response.getErrorMessages())
} .build();
/**
* 파일이 이미지인지 확인
*/
public static boolean isImageFile(String fileName) {
if (fileName == null) return false;
String extension = extractFileExtension(fileName).toLowerCase();
return extension.matches("\\.(jpg|jpeg|png|gif|bmp|svg|webp)$");
}
/**
* 파일이 문서인지 확인
*/
public static boolean isDocumentFile(String fileName) {
if (fileName == null) return false;
String extension = extractFileExtension(fileName).toLowerCase();
return extension.matches("\\.(pdf|doc|docx|xls|xlsx|ppt|pptx|txt|rtf)$");
}
/**
* 파일이 압축파일인지 확인
*/
public static boolean isArchiveFile(String fileName) {
if (fileName == null) return false;
String extension = extractFileExtension(fileName).toLowerCase();
return extension.matches("\\.(zip|rar|7z|tar|gz|bz2)$");
}
/**
* 현재 날짜의 년월일 문자열 반환
* 예: "2024/01/15"
*/
public static String getCurrentDatePath() {
LocalDate today = LocalDate.now();
return today.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
}
/**
* 지정된 날짜의 년월일 문자열 반환
* 예: "2024/01/15"
*/
public static String getDatePath(LocalDate date) {
return date.format(DateTimeFormatter.ofPattern("yyyy/MM/dd"));
}
/**
* 파일 경로에서 년월일 정보 추출
* 예: "uploads/2024/01/15/file.txt" -> "2024/01/15"
*/
public static String extractDateFromPath(String filePath) {
if (filePath == null || filePath.isEmpty()) {
return "";
}
// 정규식으로 년/월/일 패턴 찾기
java.util.regex.Pattern pattern = java.util.regex.Pattern.compile("(\\d{4}/\\d{2}/\\d{2})");
java.util.regex.Matcher matcher = pattern.matcher(filePath);
if (matcher.find()) {
return matcher.group(1);
}
return "";
}
/**
* 년월일 폴더 구조가 유효한지 확인
* 예: "2024/01/15" -> true, "2024/13/45" -> false
*/
public static boolean isValidDatePath(String datePath) {
if (datePath == null || datePath.isEmpty()) {
return false;
}
try {
String[] parts = datePath.split("/");
if (parts.length != 3) {
return false;
}
int year = Integer.parseInt(parts[0]);
int month = Integer.parseInt(parts[1]);
int day = Integer.parseInt(parts[2]);
// 년도 범위 체크 (1900 ~ 2100)
if (year < 1900 || year > 2100) {
return false;
}
// 월 범위 체크 (1 ~ 12)
if (month < 1 || month > 12) {
return false;
}
// 일 범위 체크 (1 ~ 31)
if (day < 1 || day > 31) {
return false;
}
// 실제 존재하는 날짜인지 확인
LocalDate.of(year, month, day);
return true;
} catch (Exception e) {
return false;
}
} }
} }

View File

@@ -3,11 +3,6 @@
# ======================================== # ========================================
server.port=8080 server.port=8080
server.servlet.context-path=/service server.servlet.context-path=/service
management.endpoint.health.probes.enabled=true
management.health.livenessstate.enabled=true
management.health.readinessstate.enabled=true
spring.application.name=bio_backend spring.application.name=bio_backend
spring.output.ansi.enabled=always spring.output.ansi.enabled=always
@@ -118,8 +113,7 @@ springdoc.default-consumes-media-type=application/json
# ======================================== # ========================================
# 보안 설정 - 허용할 경로 # 보안 설정 - 허용할 경로
# ======================================== security.permit-all-paths=/login,/members/register,/swagger-ui/**,/swagger-ui.html,/swagger-ui/index.html,/api-docs,/api-docs/**,/v3/api-docs,/v3/api-docs/**,/ws/**,/actuator/**,/actuator/health/**,/actuator/info
security.permit-all-paths=/login,/members/register,/swagger-ui/**,/swagger-ui.html,/swagger-ui/index.html,/api-docs,/api-docs/**,/v3/api-docs,/v3/api-docs/**,/ws/**,/actuator/health/**
# 파일 업로드 설정 # 파일 업로드 설정
# ======================================== # ========================================
@@ -130,3 +124,35 @@ spring.servlet.multipart.file-size-threshold=2KB
# 파일 저장 경로 설정 # 파일 저장 경로 설정
app.file.upload.path=./uploads/ app.file.upload.path=./uploads/
# ========================================
# Spring Boot Actuator 설정
# ========================================
# Actuator 엔드포인트 활성화
management.endpoints.web.exposure.include=health,info,metrics,env,configprops
management.endpoint.health.show-details=always
management.endpoint.health.show-components=always
# Health 체크 상세 정보 표시
management.health.db.enabled=true
management.health.diskspace.enabled=true
management.health.defaults.enabled=true
# Actuator 기본 경로 설정
management.endpoints.web.base-path=/actuator
# Health 체크 타임아웃 설정 (밀리초)
management.health.defaults.timeout=10s
# 커스텀 Health 체크 그룹 설정
management.health.groups.readiness.include=db,diskSpace
management.health.groups.liveness.include=ping
# ========================================
# 애플리케이션 정보 설정 (Actuator info 엔드포인트용)
# ========================================
info.app.name=Bio Backend Service
info.app.description=생물학 연구를 위한 백엔드 서비스
info.app.version=@project.version@
info.app.java.version=@java.version@
info.app.spring-boot.version=@spring-boot.version@