77 Commits

Author SHA1 Message Date
499fbc6afb Update jpa-curd-0.0.1.vsix 2025-08-29 16:29:27 +09:00
e78e98ad37 Merge branch 'main' of https://demo.stam.kr/leejisun9/bio_backend 2025-08-29 16:29:16 +09:00
f10b028e04 [JWT 개선] JwtTokenValidationFilter에서 클라이언트 IP 검증 로직을 HttpUtils를 사용하도록 변경하고, Access/Refresh Token 생성 메서드를 개선하여 코드 가독성을 향상시킴. MemberDto에 Refresh Token 및 로그인 IP 설정 추가. 2025-08-29 16:07:30 +09:00
cc2a34403d jpa create 2025-08-29 14:51:05 +09:00
e8a785a20d [JWT 개선] MemberController에서 로그아웃 시 모든 토큰 쿠키 삭제 기능 추가 및 JwtUtils에 Access/Refresh Token 쿠키 설정 및 삭제 메서드 추가. JwtTokenIssuanceFilter와 JwtTokenValidationFilter에서 Access Token을 헤더 대신 쿠키에 저장하도록 변경. 2025-08-28 16:46:53 +09:00
afef6dfa80 [회원 서비스 개선] MemberService와 MemberServiceImpl에서 updateMember 및 deleteRefreshToken 메서드의 반환 타입을 void로 변경하고, 메서드 순서를 정리하여 코드 가독성을 향상시킴. 2025-08-28 16:21:48 +09:00
a0ffeb236e [회원 로그인 응답 개선] LoginResponseDto에 name 필드 추가 및 MemberMapper에 toLoginResponseDto 메서드 추가. JwtTokenIssuanceFilter에서 로그인 성공 시 LoginResponseDto 변환 로직 수정. 2025-08-28 16:00:54 +09:00
fa1df19f64 [코드 정리] CreateMemberResponseDto에서 MemberRole 필드 제거 및 JwtTokenValidationFilter에서 역할 관련 코드 삭제 2025-08-27 16:33:18 +09:00
b0398fccee [코드 정리] 2025-08-27 16:30:30 +09:00
92be6caf80 [회원 역할 제거] MemberRole 관련 코드 삭제제 2025-08-27 16:29:08 +09:00
31aed4bda0 Merge branch 'main' of https://demo.stam.kr/leejisun9/bio_backend 2025-08-27 15:15:59 +09:00
438bfc3bc5 [API 응답 개선] ApiResponseCode에 COMMON_TARGET_NOT_FOUND 추가 및 CommonCodeServiceImpl에서 예외 처리 메시지 수정. JwtTokenIssuanceFilter와 JwtTokenValidationFilter에서 Refresh Token 생성 시 클라이언트 IP 정보 포함. JwtUtils에서 토큰 생성 메서드 개선 및 불필요한 코드 제거. 2025-08-27 15:11:29 +09:00
leejisun9
c07e7511d3 Merge branch 'main' of https://demo.stam.kr/leejisun9/bio_backend 2025-08-27 14:10:05 +09:00
leejisun9
0617347395 배포 추가 세팅 2025-08-27 14:07:49 +09:00
8c5d7c6c3f [매퍼 설정 개선] CommonCodeMapper, CommonGroupCodeMapper, MemberMapper에서 componentModel 설정을 GlobalMapperConfig로 변경하여 매퍼 설정을 통일하고, GlobalMapperConfig 클래스를 새로 추가하여 매핑 전략을 정의. 2025-08-27 09:42:10 +09:00
75d827bf04 README.md에서 ApiResponseDto의 상수 정의 섹션 제거 및 응답 예시 섹션 정리. 2025-08-27 08:40:00 +09:00
5abe2932bc [API 응답 개선] ApiResponseDto에 success 필드 추가로 성공/실패 여부 명확화. ApiResponseCode에 COMMON_CODE_DUPLICATE 추가 및 관련 메시지 수정. CommonCodeServiceImpl에서 중복 코드 예외 처리 개선. 2025-08-27 08:39:23 +09:00
sohot8653
9467a1edd0 [엔티티 리스너 추가] BaseEntityListener 클래스를 추가하여 BaseEntity의 생성자 및 수정자 정보를 자동으로 설정하도록 개선. FileServiceImpl 및 MemberServiceImpl에서 수동으로 설정하던 부분 제거. 2025-08-26 19:28:26 +09:00
sohot8653
3fc7debff4 [ID 생성기 수정] CustomIdGenerator 클래스의 generate 메서드에서 불필요한 주석 제거. 2025-08-26 18:51:11 +09:00
sohot8653
d29bc2dedd [파일 업로드 개선] FileUtils 클래스에서 불필요한 import 문 제거 및 코드 정리. 2025-08-26 18:45:01 +09:00
0c141eb397 [공통 코드 관리] 공통 코드 및 그룹 코드 관련 엔티티, DTO, 매퍼, 서비스 및 컨트롤러 추가. 기존 매퍼 삭제 및 외래 키 제약 조건 제거. 데이터베이스 스키마 수정. 2025-08-26 16:45:54 +09:00
6df8409e96 [Spring Boot Actuator 추가] build.gradle에 Spring Boot Actuator 의존성 추가 및 README.md에 시스템 모니터링 관련 문서화. application.properties에 Actuator 설정 추가. 2025-08-26 15:57:09 +09:00
88a508bd54 [공통 코드] 중간 커밋밋 2025-08-26 15:50:43 +09:00
d37986558e [파일 업로드 개선] FileUploadRequestDto에서 groupOid 필드 제거 2025-08-26 13:59:51 +09:00
eb2efbb0ea Merge branch 'main' of https://demo.stam.kr/leejisun9/bio_backend 2025-08-26 13:31:25 +09:00
12aa3ae5a3 [파일 업로드 개선] FileUploadResponseDto 및 MultipleFileUploadResponseDto에 groupOid 필드 추가, FileServiceImpl에서 응답 DTO 생성 로직 수정 및 createUploadResponse 메서드 제거. FileUtils 클래스에 파일 업로드 메서드 추가 및 불필요한 메서드 제거로 코드 정리. 2025-08-26 13:31:03 +09:00
leejisun9
1bd499fe10 Merge branch 'main' of https://demo.stam.kr/leejisun9/bio_backend 2025-08-26 13:21:23 +09:00
leejisun9
71bcbbb396 JIB 설정 추가 , skaffold,k8s 배포 설정 삭제 2025-08-26 13:21:15 +09:00
f9472d1ccd [회원 정보 개선] MemberRepository에서 불필요한 import 문 제거하여 코드 정리. 2025-08-26 10:40:56 +09:00
56c8893f9e [회원 정보 개선] MemberRepositoryCustom에서 불필요한 import 문 제거 및 코드 정리. 2025-08-26 10:40:35 +09:00
690724c77c [파일 업로드 개선] 파일 업로드 처리 메서드에 groupOid 매개변수 추가 및 관련 로직 수정. MemberRepository와 MemberService에서 불필요한 메서드 제거 및 활성화된 회원 조회 로직 개선. 2025-08-26 10:39:46 +09:00
b5d6d213f3 [로그아웃 기능 구현현] 2025-08-26 10:17:07 +09:00
e7105215b8 [파일 업로드 기능 추가] 파일 업로드 및 다운로드 API 구현 2025-08-26 09:39:09 +09:00
d8fe8399f7 [회원 정보 개선] Member 엔티티 및 DTO에 login_ip 필드 추가, JWT 관련 필터에서 클라이언트 IP 처리 로직 개선. Refresh Token 생성 및 검증 시 IP 정보 포함하여 보안 강화. 2025-08-25 11:25:45 +09:00
8cee267f36 [회원 정보 개선] Member 엔티티의 테이블 이름을 AppConstants.TABLE_PREFIX를 사용하여 동적으로 설정하도록 수정하고, AppConstants 클래스를 추가하여 테이블 접두사를 정의 2025-08-25 09:45:49 +09:00
b1218ab9bb [보안 설정 업데이트] application.properties에서 허용된 경로에 Swagger UI 관련 경로 추가. 보안 설정을 개선하여 API 문서 접근성을 향상. 2025-08-22 16:56:52 +09:00
7ae5871ae0 [회원 조회 개선] MemberRepository와 MemberService에서 사용자 ID로 활성화된 회원을 조회하는 메서드 이름 변경 및 쿼리 조건 수정. 사용자 조회 로직을 개선하여 더 명확한 기능 제공. 2025-08-22 13:52:09 +09:00
1ce08cb981 [회원 정보 개선] refresh_token 필드 길이 증가 및 MemberMapper에 DTO 업데이트 메서드 추가. MemberService에서 사용자 조회 및 업데이트 로직 개선, JWT 필터에서 refresh_token 저장 로직 제거. 2025-08-22 12:56:25 +09:00
a4c14c69f0 [회원 정보 업데이트] Member 엔티티에 name 및 email 필드 추가, 관련 DTO 및 서비스 로직 수정. 사용자 역할에서 USER 제거 및 검증 어노테이션 추가. 2025-08-22 10:48:14 +09:00
9e7929da6b [회원 조회 개선] loadUserByUsername 메서드에서 사용자 조회 실패 시 발생하는 예외 메시지를 한국어로 변경 2025-08-22 10:17:12 +09:00
f9cd84f740 Add .gitkeep file to logs directory to ensure it is tracked in version control. 2025-08-22 09:57:23 +09:00
224006b6cb Initialize log and temp directories with .gitkeep files to ensure they are tracked in version control. 2025-08-22 09:51:46 +09:00
dbac9f4702 [예외 처리 개선] GlobalExceptionHandler에서 검증 오류 상세 정보를 위한 내부 클래스 추가 및 불필요한 주석 제거. application.properties에 운영 환경 변수 설정 및 배치 처리 설정 추가. 2025-08-22 09:09:21 +09:00
2a1f211159 [README.md 업데이트] 모든 엔티티가 BaseEntity를 상속하도록 문구 수정 2025-08-22 08:41:34 +09:00
sohot8653
7430f1606f [README.md 업데이트] 모든 엔티티가 BaseEntity를 상속하도록 규정하고, 자동 생성 필드 및 사용 예시를 추가하여 문서화 개선 2025-08-22 01:10:25 +09:00
sohot8653
1153f164fc [코드 개선] Lombok의 @Getter 및 @Setter 어노테이션을 추가 2025-08-22 01:05:51 +09:00
sohot8653
1850227d24 [보안 설정 개선] JwtTokenIssuanceFilter 및 JwtTokenValidationFilter 추가로 JWT 인증 로직을 강화하고, SecurityPathConfig를 통해 허용 경로 설정을 외부화하여 보안 유연성 향상. JwtAccessDeniedHandler 및 JwtAuthenticationEntryPoint 삭제로 코드 정리. 2025-08-22 00:58:15 +09:00
09e06cd10b [JWT 필터 개선] JwtTokenValidationFilter에서 JWT 검증 로직을 개선하여 Access Token 유효성 검사 후 자동으로 Refresh Token을 갱신하는 기능 추가. WebSecurity에서 필터 설명을 업데이트하고, 허용 경로 설정을 정리하여 보안 설정을 강화. 2025-08-21 17:06:14 +09:00
6d1b89609b [예외 처리 개선] GlobalExceptionHandler에 일반 예외 처리 메서드 추가 및 로그 기능 통합. CustomAuthenticationFailureHandler에서 불필요한 줄 제거. JwtTokenValidationFilter에서 JWT 검증 오류 시 로그 기록 추가. 2025-08-21 16:29:27 +09:00
baaa003d9a [README.md 업데이트] ApiResponseDto 및 ApiResponseCode의 메시지를 한국어로 번역하고, 중복 사용자 ID 오류 코드 추가로 응답 메시지 개선 2025-08-21 15:45:00 +09:00
d9d3be86a9 [README.md 업데이트] 기본 도메인 구조 및 공통 설정 항목 추가, MapStruct 매퍼 사용 예시 포함으로 문서화 개선 2025-08-21 15:40:33 +09:00
1b46b95ad5 [JWT 인증 시스템 추가] README.md에 JWT 인증 시스템에 대한 설명 추가 및 Access Token과 Refresh Token의 구성 및 자동 갱신 로직 설명. application.properties에서 Access Token의 만료 시간을 수정하여 15분으로 설정. 2025-08-20 16:35:35 +09:00
8aa96d5fbd [JWT 기능 개선] Refresh Token 생성 및 갱신 로직을 모듈화하여 JwtUtils 클래스에 메서드 추가. JwtTokenIssuanceFilter와 JwtTokenValidationFilter에서 새로운 메서드 사용으로 코드 간결화 및 유지보수성 향상. 2025-08-20 16:30:46 +09:00
0dc32f2b39 [회원 관리 기능 추가] 회원 등록 및 로그인 API 구현, JWT 토큰 발급 및 검증 필터 추가. Member 관련 DTO, Entity, Mapper, Repository, Service 구현으로 회원 관리 기능을 강화하고, Swagger 설정을 통해 API 문서화 개선. 2025-08-20 16:22:11 +09:00
bfb87b8e33 [회원 관리 기능 추가] MemberController에 로그아웃 API 추가 및 ApiResponseDto의 fail 메서드 오버로딩으로 응답 처리 개선. CustomAuthenticationFailureHandler, GlobalExceptionHandler, JwtAccessDeniedHandler에서 null 데이터 제거로 응답 일관성 향상. 2025-08-20 10:18:54 +09:00
leejisun9
33b33ef7a3 Merge branch 'main' of https://demo.stam.kr/leejisun9/bio_backend 2025-08-19 08:56:06 +09:00
leejisun9
331d242ac3 erd 문가 추가, nginx 에러 로그 추가 2025-08-19 08:54:31 +09:00
eff011f9fd Merge pull request 'kmbin92_2025081302' (#3) from kmbin92_2025081302 into main
Reviewed-on: https://demo.stam.kr/leejisun9/bio_backend/pulls/3
2025-08-15 00:54:14 +09:00
sohot8653
0f1c5443ea [로그 기능 추가] HttpLoggingFilter를 구현하여 HTTP 요청 및 응답 로깅 기능을 추가하고, MemberController에서 회원 등록 API에 LogExecution 어노테이션을 적용하여 실행 로그를 기록하도록 수정 2025-08-15 00:53:17 +09:00
baab0352d5 [상수 리팩토링] ApiResponseCode를 global.constants 패키지로 이동하고, 관련 코드에서 ApiResponseCode 참조를 업데이트하여 코드 일관성 및 가독성 향상 2025-08-14 17:19:52 +09:00
21f121c490 [Swagger 설정 업데이트] 기본 미디어 타입을 application/json으로 설정하여 Swagger API 문서의 응답 및 요청 형식을 표준화 2025-08-14 10:50:38 +09:00
77bc400888 [API 응답 표준화] CustomApiResponse를 ApiResponseDto로 변경하여 API 응답 구조를 통일하고, 관련 코드 및 문서 업데이트 2025-08-14 09:47:24 +09:00
d0612b61e9 [API 응답 표준화] 2025-08-13 17:02:40 +09:00
d1c82848a2 Merge pull request '2025081301' (#2) from 2025081301 into main
Reviewed-on: https://demo.stam.kr/leejisun9/bio_backend/pulls/2
2025-08-13 15:41:08 +09:00
b34636baae [nginx gitignore 추가] 2025-08-13 15:38:54 +09:00
a2c6c83ed7 [Window 버전으로 변경] 2025-08-13 15:38:09 +09:00
6a4388f513 [Swagger 적용] 2025-08-13 15:23:59 +09:00
2ead0f0f12 [오류 처리 개선] - ApiException 및 ApiResponseCode 추가, GlobalExceptionHandler에서 ApiException 처리 로직 구현, README에 오류 등록 및 사용 방법 문서화 2025-08-13 14:39:36 +09:00
e0e868a282 [Swagger 초기 적용] 2025-08-13 13:53:15 +09:00
leejisun9
45f3aa0f2c PLM , 종균 의뢰 추가 2025-08-13 11:22:55 +09:00
74f8b65327 Merge pull request 'kmbin92_2025081101' (#1) from kmbin92_2025081101 into main
Reviewed-on: https://demo.stam.kr/leejisun9/bio_backend/pulls/1
2025-08-13 10:42:56 +09:00
3033582f8e [annotation-processor 경로 변경] 2025-08-13 10:42:08 +09:00
51a29996f5 [QueryDSL 의존성 변경] 2025-08-13 10:11:54 +09:00
a8e1a15b1d [회원 엔티티 및 데이터베이스 스키마 수정] - st_member 테이블에 user_id 인덱스 추가 및 Member 엔티티에서 고유 제약 조건을 인덱스로 변경 2025-08-12 16:09:51 +09:00
d1bf1dd477 [회원 엔티티 및 서비스 개선] - Member 엔티티에서 onPostPersist 메서드 제거, MemberServiceImpl에서 OID 생성 및 설정 로직 추가, BaseEntity에서 OID 초기화 로직 수정 2025-08-12 15:39:35 +09:00
c50e8d835b [회원 관리 기능 개선 및 MapStruct 도입] - 회원 엔티티 및 DTO 구조 변경, MapStruct를 통한 변환 로직 추가, 사용자 ID 중복 체크 기능 구현, README 업데이트, Gradle 의존성 수정 2025-08-12 15:00:12 +09:00
2ff5a02906 [프로젝트 구조 세팅] 2025-08-12 08:46:55 +09:00
124 changed files with 26978 additions and 1705 deletions

927
.erd

File diff suppressed because it is too large Load Diff

6
.gitignore vendored
View File

@@ -42,4 +42,8 @@ bin/
# Temporary files created by the OS # Temporary files created by the OS
.DS_Store .DS_Store
Thumbs.db Thumbs.db
/nginx-1.28.0/logs/nginx.pid
# Upload files
uploads/

View File

@@ -1,25 +0,0 @@
# ./my-spring-app/Dockerfile
# 공식 OpenJDK 이미지를 기반으로 사용
FROM openjdk:17-jdk-slim
# 컨테이너의 작업 디렉터리를 설정
WORKDIR /app
# Gradle Wrapper와 빌드 파일을 컨테이너에 복사
COPY gradlew .
COPY gradle ./gradle
# 프로젝트 설정 파일들을 복사
COPY build.gradle .
COPY settings.gradle .
# 의존성을 미리 다운로드
RUN ./gradlew dependencies
# 소스 파일들을 복사
COPY src ./src
# Gradle Wrapper를 사용하여 애플리케이션을 실행
# 이 명령어는 docker-compose.yml에서 개발용 명령으로 덮어쓸 것입니다.
CMD ["./gradlew", "bootRun"]

354
README.md
View File

@@ -1,2 +1,354 @@
# bio_backend # 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<T>` 형태로 표준화되어 있습니다.
```java
public class ApiResponseDto<T> {
private boolean success; // 성공/실패 여부 (true/false)
private int code; // HTTP 상태 코드
private String message; // 응답 메시지 (ApiResponseCode enum 값)
private String description; // 응답 설명
private T data; // 실제 데이터 (제네릭 타입)
}
```
#### 응답 예시
**성공 응답 (201 Created)**
```json
{
"success": true,
"code": 201,
"message": "COMMON_SUCCESS_CREATED",
"description": "Created successfully",
"data": {
"seq": 1,
"userId": "user123",
"name": "홍길동"
}
}
```
**실패 응답 (409 Conflict)**
```json
{
"success": false,
"code": 409,
"message": "USER_ID_DUPLICATE",
"description": "User ID already exists"
}
```
#### 사용 방법
**Controller에서 응답 생성**
```java
@PostMapping("/members")
public ResponseEntity<ApiResponseDto<CreateMemberResponseDto>> createMember(@RequestBody CreateMemberRequestDto requestDto) {
// ... 비즈니스 로직 ...
// 성공 응답
ApiResponseDto<CreateMemberResponseDto> apiResponse =
ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS_CREATED, responseDto);
return ResponseEntity.status(HttpStatus.CREATED).body(apiResponse);
}
```
**공용 응답 코드 사용**
```java
// ApiResponseCode.java
public enum ApiResponseCode {
// 공용 성공 코드
COMMON_SUCCESS(HttpStatus.OK.value(), "요청 성공"),
COMMON_SUCCESS_CREATED(HttpStatus.CREATED.value(), "성공적으로 생성되었습니다"),
COMMON_SUCCESS_UPDATED(HttpStatus.OK.value(), "성공적으로 수정되었습니다"),
COMMON_SUCCESS_DELETED(HttpStatus.OK.value(), "성공적으로 삭제되었습니다"),
COMMON_SUCCESS_RETRIEVED(HttpStatus.OK.value(), "성공적으로 조회되었습니다"),
// 공용 오류 코드
COMMON_BAD_REQUEST(HttpStatus.BAD_REQUEST.value(), "필수 요청 본문이 누락되었거나 오류가 발생했습니다"),
COMMON_UNAUTHORIZED(HttpStatus.UNAUTHORIZED.value(), "인증에 실패했습니다"),
COMMON_FORBIDDEN(HttpStatus.FORBIDDEN.value(), "접근이 거부되었습니다"),
COMMON_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "리소스를 찾을 수 없습니다"),
COMMON_CONFLICT(HttpStatus.CONFLICT.value(), "충돌이 발생했습니다"),
COMMON_INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR.value(), "서버에서 오류가 발생했습니다"),
// 사용자 관련 코드
USER_ID_DUPLICATE(HttpStatus.CONFLICT.value(), "이미 존재하는 사용자 ID입니다"),
USER_NOT_FOUND(HttpStatus.UNAUTHORIZED.value(), "사용자를 찾을 수 없습니다. 인증에 실패했습니다"),
USER_PASSWORD_INVALID(HttpStatus.UNAUTHORIZED.value(), "비밀번호가 올바르지 않습니다")
}
```
#### 핵심 규칙
- **모든 API 응답**: `ApiResponseDto<T>`로 감싸서 반환
- **공용 응답 코드**: `COMMON_` 접두사로 시작하는 범용 코드 사용
- **일관된 구조**: `success`, `code`, `message`, `description`, `data` 필드로 표준화
- **성공/실패 구분**: `success` 필드로 명확한 성공/실패 여부 전달
- **제네릭 활용**: `<T>`를 통해 다양한 데이터 타입 지원
### 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`
### 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
@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 커스터마이징
### 6. 트랜잭션 관리
#### 기본 설정
```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` (성능 최적화)
### 7. 오류 등록 및 사용
#### 오류 코드 등록
```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`가 일관된 응답 형태로 변환
### 8. 로깅 시스템
#### @LogExecution 어노테이션 사용법
**메서드 실행 로깅을 위한 필수 어노테이션입니다.**
##### 기본 사용법
```java
@LogExecution("회원 등록")
@PostMapping("/register")
public ResponseEntity<ApiResponseDto<CreateMemberResponseDto>> 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` 어노테이션이 없으면 메서드 실행 로그가 출력되지 않습니다
### 9. 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` 구현체 생성
### 10. BaseEntity 상속
**모든 엔티티는 `BaseEntity` 상속을 원칙으로 합니다.**
#### 자동 생성 필드
- **OID**: `@PrePersist`에서 자동 생성 (고유 식별자)
- **생성일시**: `@CreatedDate`로 자동 설정
- **수정일시**: `@LastModifiedDate`로 자동 갱신
- **생성자/수정자 OID**: 사용자 추적용
#### 사용 예시
```java
@Entity
@Table(name = "st_members")
public class Member extends BaseEntity {
// OID, 생성일시, 수정일시 등이 자동으로 관리됨
private String userId;
private String name;
}
```

View File

@@ -1,13 +1,8 @@
buildscript {
ext {
queryDslVersion = "5.0.0"
}
}
plugins { plugins {
id 'java' id 'java'
id 'org.springframework.boot' version '3.5.4' id 'org.springframework.boot' version '3.5.4'
id 'io.spring.dependency-management' version '1.1.7' id 'io.spring.dependency-management' version '1.1.7'
id 'com.google.cloud.tools.jib' version '3.4.0' // Jib 플러그인 추가
} }
group = 'com.bio' group = 'com.bio'
@@ -30,37 +25,54 @@ repositories {
} }
dependencies { dependencies {
// 개발용 의존성 추가
developmentOnly 'org.springframework.boot:spring-boot-devtools' developmentOnly 'org.springframework.boot:spring-boot-devtools'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
// PostgreSQL JDBC 드라이버
// PostgreSQL JDBC
runtimeOnly 'org.postgresql:postgresql' runtimeOnly 'org.postgresql:postgresql'
implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-web'
// Spring Security 추가 // Spring Securit
implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-security'
// Validation 추가 // Validation
implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-validation'
// ModelMapper 추가 // Spring Boot Actuator
implementation 'org.modelmapper:modelmapper:3.0.0' implementation 'org.springframework.boot:spring-boot-starter-actuator'
// MyBatis 추가 // MapStruct
implementation 'org.mapstruct:mapstruct:1.5.5.Final'
annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final'
// MyBatis
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3' implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.3'
implementation 'org.springframework.boot:spring-boot-starter-actuator'
// jwt
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'
// lombok
compileOnly 'org.projectlombok:lombok' compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok-mapstruct-binding:0.2.0'
testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
// querydsl // querydsl
implementation "com.querydsl:querydsl-jpa:${queryDslVersion}:jakarta" implementation 'io.github.openfeign.querydsl:querydsl-jpa:6.11'
annotationProcessor "com.querydsl:querydsl-apt:${queryDslVersion}:jakarta" annotationProcessor 'io.github.openfeign.querydsl:querydsl-apt:6.11:jpa'
annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor 'jakarta.annotation:jakarta.annotation-api'
annotationProcessor "jakarta.persistence:jakarta.persistence-api" annotationProcessor 'jakarta.persistence:jakarta.persistence-api'
// p6spy // p6spy
implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0' implementation 'com.github.gavlyukovskiy:p6spy-spring-boot-starter:1.9.0'
// SpringDoc OpenAPI (Swagger)
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.9'
} }
tasks.named('test') { tasks.named('test') {
@@ -68,16 +80,44 @@ tasks.named('test') {
} }
// querydsl // querydsl
def querydslDir = "$buildDir/generated/querydsl" def generatedSrcDir = 'build/generated/sources/annotation-processor'
clean {
sourceSets { delete file(generatedSrcDir)
main.java.srcDirs += [ querydslDir ] }
tasks.withType(JavaCompile).configureEach {
options.generatedSourceOutputDirectory = file(generatedSrcDir)
} }
tasks.withType(JavaCompile) { jib {
options.annotationProcessorGeneratedSourcesDirectory = file(querydslDir) from {
} image = 'demo.stam.kr/leejisun9/eclipse-temurin:17-jre' // 가벼운 JRE 베이스
// (선택) 인증서 추가/회사 CA 필요 시 extraDirectories 사용
clean.doLast { }
file(querydslDir).deleteDir() to {
} image = 'demo.stam.kr/leejisun9/bio-backend'; tags = ['1.0.0'] // 기본 대상 레지스트리
// tags는 skaffold가 자동 주입. 로컬 단독 빌드시 -Djib.to.tags=로 지정 가능
}
container {
// Spring Boot 컨테이너 런타임 옵션
ports = ['8080']
jvmFlags = [
'-XX:+UseContainerSupport',
'-Dserver.port=8080',
'-XX:InitialRAMPercentage=50.0',
'-XX:MaxRAMPercentage=75.0'
]
environment = [ 'SPRING_PROFILES_ACTIVE': 'dev' ]
// (선택) non-root 권장 (권한 필요한 리소스 없는 경우)
// user = '65532:65532'
// workingDirectory = '/app'
}
// (선택) 계층 최적화/추가 파일
// extraDirectories {
// paths {
// path {
// from = file('docker/extra') // 예: CA cert, 설정 템플릿
// into = '/opt/extra'
// }
// }
// }
}

View File

@@ -1,14 +1,96 @@
create table member ( create table st_common_code (
status varchar(1) not null, sort_order integer not null,
use_flag boolean not null,
created_at timestamp(6) not null, created_at timestamp(6) not null,
last_login_at timestamp(6), created_oid bigint,
oid bigint generated by default as identity, oid bigint not null,
updated_at timestamp(6) not null, updated_at timestamp(6) not null,
role varchar(40) 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 (
use_flag boolean not null,
created_at timestamp(6) not null,
created_oid bigint,
file_size bigint not null,
group_oid bigint,
oid bigint not null,
updated_at timestamp(6) not null,
updated_oid bigint,
content_type varchar(255) not null,
created_id varchar(255),
description varchar(255),
file_path varchar(255) not null,
original_file_name varchar(255) not null,
stored_file_name varchar(255) not null,
updated_id varchar(255),
primary key (oid)
);
create table st_member (
use_flag boolean not null,
created_at timestamp(6) not null,
created_oid bigint,
last_login_at timestamp(6),
oid bigint not null,
updated_at timestamp(6) not null,
updated_oid bigint,
login_ip varchar(45),
name varchar(100) not null,
password varchar(100) not null, password varchar(100) not null,
user_id varchar(100) not null, user_id varchar(100) not null,
refresh_token varchar(200), refresh_token varchar(1024),
primary key (oid), created_id varchar(255),
constraint uk_member_user_id unique (user_id) email varchar(255) not null,
updated_id varchar(255),
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
on st_member (user_id);

BIN
jpa-curd-0.0.1.vsix Normal file

Binary file not shown.

View File

@@ -1,21 +0,0 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: bio-backend-deployment
labels:
app: bio-backend
spec:
replicas: 1
selector:
matchLabels:
app: bio-backend
template:
metadata:
labels:
app: bio-backend
spec:
containers:
- name: bio-backend
image: backend-0.0.1:latest # 여기에 Skaffold가 빌드할 이미지 이름을 사용합니다.
ports:
- containerPort: 8081

View File

@@ -0,0 +1,26 @@
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;

View File

@@ -0,0 +1,25 @@
fastcgi_param QUERY_STRING $query_string;
fastcgi_param REQUEST_METHOD $request_method;
fastcgi_param CONTENT_TYPE $content_type;
fastcgi_param CONTENT_LENGTH $content_length;
fastcgi_param SCRIPT_NAME $fastcgi_script_name;
fastcgi_param REQUEST_URI $request_uri;
fastcgi_param DOCUMENT_URI $document_uri;
fastcgi_param DOCUMENT_ROOT $document_root;
fastcgi_param SERVER_PROTOCOL $server_protocol;
fastcgi_param REQUEST_SCHEME $scheme;
fastcgi_param HTTPS $https if_not_empty;
fastcgi_param GATEWAY_INTERFACE CGI/1.1;
fastcgi_param SERVER_SOFTWARE nginx/$nginx_version;
fastcgi_param REMOTE_ADDR $remote_addr;
fastcgi_param REMOTE_PORT $remote_port;
fastcgi_param SERVER_ADDR $server_addr;
fastcgi_param SERVER_PORT $server_port;
fastcgi_param SERVER_NAME $server_name;
# PHP only, required if PHP was built with --enable-force-cgi-redirect
fastcgi_param REDIRECT_STATUS 200;

109
nginx-1.28.0/conf/koi-utf Normal file
View File

@@ -0,0 +1,109 @@
# This map is not a full koi8-r <> utf8 map: it does not contain
# box-drawing and some other characters. Besides this map contains
# several koi8-u and Byelorussian letters which are not in koi8-r.
# If you need a full and standard map, use contrib/unicode2nginx/koi-utf
# map instead.
charset_map koi8-r utf-8 {
80 E282AC ; # euro
95 E280A2 ; # bullet
9A C2A0 ; # &nbsp;
9E C2B7 ; # &middot;
A3 D191 ; # small yo
A4 D194 ; # small Ukrainian ye
A6 D196 ; # small Ukrainian i
A7 D197 ; # small Ukrainian yi
AD D291 ; # small Ukrainian soft g
AE D19E ; # small Byelorussian short u
B0 C2B0 ; # &deg;
B3 D081 ; # capital YO
B4 D084 ; # capital Ukrainian YE
B6 D086 ; # capital Ukrainian I
B7 D087 ; # capital Ukrainian YI
B9 E28496 ; # numero sign
BD D290 ; # capital Ukrainian soft G
BE D18E ; # capital Byelorussian short U
BF C2A9 ; # (C)
C0 D18E ; # small yu
C1 D0B0 ; # small a
C2 D0B1 ; # small b
C3 D186 ; # small ts
C4 D0B4 ; # small d
C5 D0B5 ; # small ye
C6 D184 ; # small f
C7 D0B3 ; # small g
C8 D185 ; # small kh
C9 D0B8 ; # small i
CA D0B9 ; # small j
CB D0BA ; # small k
CC D0BB ; # small l
CD D0BC ; # small m
CE D0BD ; # small n
CF D0BE ; # small o
D0 D0BF ; # small p
D1 D18F ; # small ya
D2 D180 ; # small r
D3 D181 ; # small s
D4 D182 ; # small t
D5 D183 ; # small u
D6 D0B6 ; # small zh
D7 D0B2 ; # small v
D8 D18C ; # small soft sign
D9 D18B ; # small y
DA D0B7 ; # small z
DB D188 ; # small sh
DC D18D ; # small e
DD D189 ; # small shch
DE D187 ; # small ch
DF D18A ; # small hard sign
E0 D0AE ; # capital YU
E1 D090 ; # capital A
E2 D091 ; # capital B
E3 D0A6 ; # capital TS
E4 D094 ; # capital D
E5 D095 ; # capital YE
E6 D0A4 ; # capital F
E7 D093 ; # capital G
E8 D0A5 ; # capital KH
E9 D098 ; # capital I
EA D099 ; # capital J
EB D09A ; # capital K
EC D09B ; # capital L
ED D09C ; # capital M
EE D09D ; # capital N
EF D09E ; # capital O
F0 D09F ; # capital P
F1 D0AF ; # capital YA
F2 D0A0 ; # capital R
F3 D0A1 ; # capital S
F4 D0A2 ; # capital T
F5 D0A3 ; # capital U
F6 D096 ; # capital ZH
F7 D092 ; # capital V
F8 D0AC ; # capital soft sign
F9 D0AB ; # capital Y
FA D097 ; # capital Z
FB D0A8 ; # capital SH
FC D0AD ; # capital E
FD D0A9 ; # capital SHCH
FE D0A7 ; # capital CH
FF D0AA ; # capital hard sign
}

103
nginx-1.28.0/conf/koi-win Normal file
View File

@@ -0,0 +1,103 @@
charset_map koi8-r windows-1251 {
80 88 ; # euro
95 95 ; # bullet
9A A0 ; # &nbsp;
9E B7 ; # &middot;
A3 B8 ; # small yo
A4 BA ; # small Ukrainian ye
A6 B3 ; # small Ukrainian i
A7 BF ; # small Ukrainian yi
AD B4 ; # small Ukrainian soft g
AE A2 ; # small Byelorussian short u
B0 B0 ; # &deg;
B3 A8 ; # capital YO
B4 AA ; # capital Ukrainian YE
B6 B2 ; # capital Ukrainian I
B7 AF ; # capital Ukrainian YI
B9 B9 ; # numero sign
BD A5 ; # capital Ukrainian soft G
BE A1 ; # capital Byelorussian short U
BF A9 ; # (C)
C0 FE ; # small yu
C1 E0 ; # small a
C2 E1 ; # small b
C3 F6 ; # small ts
C4 E4 ; # small d
C5 E5 ; # small ye
C6 F4 ; # small f
C7 E3 ; # small g
C8 F5 ; # small kh
C9 E8 ; # small i
CA E9 ; # small j
CB EA ; # small k
CC EB ; # small l
CD EC ; # small m
CE ED ; # small n
CF EE ; # small o
D0 EF ; # small p
D1 FF ; # small ya
D2 F0 ; # small r
D3 F1 ; # small s
D4 F2 ; # small t
D5 F3 ; # small u
D6 E6 ; # small zh
D7 E2 ; # small v
D8 FC ; # small soft sign
D9 FB ; # small y
DA E7 ; # small z
DB F8 ; # small sh
DC FD ; # small e
DD F9 ; # small shch
DE F7 ; # small ch
DF FA ; # small hard sign
E0 DE ; # capital YU
E1 C0 ; # capital A
E2 C1 ; # capital B
E3 D6 ; # capital TS
E4 C4 ; # capital D
E5 C5 ; # capital YE
E6 D4 ; # capital F
E7 C3 ; # capital G
E8 D5 ; # capital KH
E9 C8 ; # capital I
EA C9 ; # capital J
EB CA ; # capital K
EC CB ; # capital L
ED CC ; # capital M
EE CD ; # capital N
EF CE ; # capital O
F0 CF ; # capital P
F1 DF ; # capital YA
F2 D0 ; # capital R
F3 D1 ; # capital S
F4 D2 ; # capital T
F5 D3 ; # capital U
F6 C6 ; # capital ZH
F7 C2 ; # capital V
F8 DC ; # capital soft sign
F9 DB ; # capital Y
FA C7 ; # capital Z
FB D8 ; # capital SH
FC DD ; # capital E
FD D9 ; # capital SHCH
FE D7 ; # capital CH
FF DA ; # capital hard sign
}

View File

@@ -0,0 +1,99 @@
types {
text/html html htm shtml;
text/css css;
text/xml xml;
image/gif gif;
image/jpeg jpeg jpg;
application/javascript js;
application/atom+xml atom;
application/rss+xml rss;
text/mathml mml;
text/plain txt;
text/vnd.sun.j2me.app-descriptor jad;
text/vnd.wap.wml wml;
text/x-component htc;
image/avif avif;
image/png png;
image/svg+xml svg svgz;
image/tiff tif tiff;
image/vnd.wap.wbmp wbmp;
image/webp webp;
image/x-icon ico;
image/x-jng jng;
image/x-ms-bmp bmp;
font/woff woff;
font/woff2 woff2;
application/java-archive jar war ear;
application/json json;
application/mac-binhex40 hqx;
application/msword doc;
application/pdf pdf;
application/postscript ps eps ai;
application/rtf rtf;
application/vnd.apple.mpegurl m3u8;
application/vnd.google-earth.kml+xml kml;
application/vnd.google-earth.kmz kmz;
application/vnd.ms-excel xls;
application/vnd.ms-fontobject eot;
application/vnd.ms-powerpoint ppt;
application/vnd.oasis.opendocument.graphics odg;
application/vnd.oasis.opendocument.presentation odp;
application/vnd.oasis.opendocument.spreadsheet ods;
application/vnd.oasis.opendocument.text odt;
application/vnd.openxmlformats-officedocument.presentationml.presentation
pptx;
application/vnd.openxmlformats-officedocument.spreadsheetml.sheet
xlsx;
application/vnd.openxmlformats-officedocument.wordprocessingml.document
docx;
application/vnd.wap.wmlc wmlc;
application/wasm wasm;
application/x-7z-compressed 7z;
application/x-cocoa cco;
application/x-java-archive-diff jardiff;
application/x-java-jnlp-file jnlp;
application/x-makeself run;
application/x-perl pl pm;
application/x-pilot prc pdb;
application/x-rar-compressed rar;
application/x-redhat-package-manager rpm;
application/x-sea sea;
application/x-shockwave-flash swf;
application/x-stuffit sit;
application/x-tcl tcl tk;
application/x-x509-ca-cert der pem crt;
application/x-xpinstall xpi;
application/xhtml+xml xhtml;
application/xspf+xml xspf;
application/zip zip;
application/octet-stream bin exe dll;
application/octet-stream deb;
application/octet-stream dmg;
application/octet-stream iso img;
application/octet-stream msi msp msm;
audio/midi mid midi kar;
audio/mpeg mp3;
audio/ogg ogg;
audio/x-m4a m4a;
audio/x-realaudio ra;
video/3gpp 3gpp 3gp;
video/mp2t ts;
video/mp4 mp4;
video/mpeg mpeg mpg;
video/quicktime mov;
video/webm webm;
video/x-flv flv;
video/x-m4v m4v;
video/x-mng mng;
video/x-ms-asf asx asf;
video/x-ms-wmv wmv;
video/x-msvideo avi;
}

View File

@@ -0,0 +1,49 @@
events {
worker_connections 1024;
}
http {
include mime.types;
default_type application/octet-stream;
sendfile on;
keepalive_timeout 65;
# (선택) 로그 위치
access_log logs/access.log;
error_log logs/error.log warn;
# 웹소켓용
map $http_upgrade $connection_upgrade {
default upgrade;
'' close;
}
server {
listen 80;
server_name localhost;
# Nuxt(개발서버 3000)
location / {
proxy_pass http://localhost:3000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
}
# Spring Boot(8080) — /service 접두어 제거
location /service/ {
proxy_pass http://localhost:8080; # 끝에 슬래시 넣으면 /service가 제거됨
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
# (선택) 업로드 크게 받을 때
# client_max_body_size 50m;
}
}

View File

@@ -0,0 +1,17 @@
scgi_param REQUEST_METHOD $request_method;
scgi_param REQUEST_URI $request_uri;
scgi_param QUERY_STRING $query_string;
scgi_param CONTENT_TYPE $content_type;
scgi_param DOCUMENT_URI $document_uri;
scgi_param DOCUMENT_ROOT $document_root;
scgi_param SCGI 1;
scgi_param SERVER_PROTOCOL $server_protocol;
scgi_param REQUEST_SCHEME $scheme;
scgi_param HTTPS $https if_not_empty;
scgi_param REMOTE_ADDR $remote_addr;
scgi_param REMOTE_PORT $remote_port;
scgi_param SERVER_PORT $server_port;
scgi_param SERVER_NAME $server_name;

View File

@@ -0,0 +1,17 @@
uwsgi_param QUERY_STRING $query_string;
uwsgi_param REQUEST_METHOD $request_method;
uwsgi_param CONTENT_TYPE $content_type;
uwsgi_param CONTENT_LENGTH $content_length;
uwsgi_param REQUEST_URI $request_uri;
uwsgi_param PATH_INFO $document_uri;
uwsgi_param DOCUMENT_ROOT $document_root;
uwsgi_param SERVER_PROTOCOL $server_protocol;
uwsgi_param REQUEST_SCHEME $scheme;
uwsgi_param HTTPS $https if_not_empty;
uwsgi_param REMOTE_ADDR $remote_addr;
uwsgi_param REMOTE_PORT $remote_port;
uwsgi_param SERVER_PORT $server_port;
uwsgi_param SERVER_NAME $server_name;

126
nginx-1.28.0/conf/win-utf Normal file
View File

@@ -0,0 +1,126 @@
# This map is not a full windows-1251 <> utf8 map: it does not
# contain Serbian and Macedonian letters. If you need a full map,
# use contrib/unicode2nginx/win-utf map instead.
charset_map windows-1251 utf-8 {
82 E2809A ; # single low-9 quotation mark
84 E2809E ; # double low-9 quotation mark
85 E280A6 ; # ellipsis
86 E280A0 ; # dagger
87 E280A1 ; # double dagger
88 E282AC ; # euro
89 E280B0 ; # per mille
91 E28098 ; # left single quotation mark
92 E28099 ; # right single quotation mark
93 E2809C ; # left double quotation mark
94 E2809D ; # right double quotation mark
95 E280A2 ; # bullet
96 E28093 ; # en dash
97 E28094 ; # em dash
99 E284A2 ; # trade mark sign
A0 C2A0 ; # &nbsp;
A1 D18E ; # capital Byelorussian short U
A2 D19E ; # small Byelorussian short u
A4 C2A4 ; # currency sign
A5 D290 ; # capital Ukrainian soft G
A6 C2A6 ; # borken bar
A7 C2A7 ; # section sign
A8 D081 ; # capital YO
A9 C2A9 ; # (C)
AA D084 ; # capital Ukrainian YE
AB C2AB ; # left-pointing double angle quotation mark
AC C2AC ; # not sign
AD C2AD ; # soft hyphen
AE C2AE ; # (R)
AF D087 ; # capital Ukrainian YI
B0 C2B0 ; # &deg;
B1 C2B1 ; # plus-minus sign
B2 D086 ; # capital Ukrainian I
B3 D196 ; # small Ukrainian i
B4 D291 ; # small Ukrainian soft g
B5 C2B5 ; # micro sign
B6 C2B6 ; # pilcrow sign
B7 C2B7 ; # &middot;
B8 D191 ; # small yo
B9 E28496 ; # numero sign
BA D194 ; # small Ukrainian ye
BB C2BB ; # right-pointing double angle quotation mark
BF D197 ; # small Ukrainian yi
C0 D090 ; # capital A
C1 D091 ; # capital B
C2 D092 ; # capital V
C3 D093 ; # capital G
C4 D094 ; # capital D
C5 D095 ; # capital YE
C6 D096 ; # capital ZH
C7 D097 ; # capital Z
C8 D098 ; # capital I
C9 D099 ; # capital J
CA D09A ; # capital K
CB D09B ; # capital L
CC D09C ; # capital M
CD D09D ; # capital N
CE D09E ; # capital O
CF D09F ; # capital P
D0 D0A0 ; # capital R
D1 D0A1 ; # capital S
D2 D0A2 ; # capital T
D3 D0A3 ; # capital U
D4 D0A4 ; # capital F
D5 D0A5 ; # capital KH
D6 D0A6 ; # capital TS
D7 D0A7 ; # capital CH
D8 D0A8 ; # capital SH
D9 D0A9 ; # capital SHCH
DA D0AA ; # capital hard sign
DB D0AB ; # capital Y
DC D0AC ; # capital soft sign
DD D0AD ; # capital E
DE D0AE ; # capital YU
DF D0AF ; # capital YA
E0 D0B0 ; # small a
E1 D0B1 ; # small b
E2 D0B2 ; # small v
E3 D0B3 ; # small g
E4 D0B4 ; # small d
E5 D0B5 ; # small ye
E6 D0B6 ; # small zh
E7 D0B7 ; # small z
E8 D0B8 ; # small i
E9 D0B9 ; # small j
EA D0BA ; # small k
EB D0BB ; # small l
EC D0BC ; # small m
ED D0BD ; # small n
EE D0BE ; # small o
EF D0BF ; # small p
F0 D180 ; # small r
F1 D181 ; # small s
F2 D182 ; # small t
F3 D183 ; # small u
F4 D184 ; # small f
F5 D185 ; # small kh
F6 D186 ; # small ts
F7 D187 ; # small ch
F8 D188 ; # small sh
F9 D189 ; # small shch
FA D18A ; # small hard sign
FB D18B ; # small y
FC D18C ; # small soft sign
FD D18D ; # small e
FE D18E ; # small yu
FF D18F ; # small ya
}

View File

@@ -0,0 +1,21 @@
geo2nginx.pl by Andrei Nigmatulin
The perl script to convert CSV geoip database ( free download
at http://www.maxmind.com/app/geoip_country ) to format, suitable
for use by the ngx_http_geo_module.
unicode2nginx by Maxim Dounin
The perl script to convert unicode mappings ( available
at http://www.unicode.org/Public/MAPPINGS/ ) to the nginx
configuration file format.
Two generated full maps for windows-1251 and koi8-r.
vim by Evan Miller
Syntax highlighting of nginx configuration for vim, to be
placed into ~/.vim/.

View File

@@ -0,0 +1,58 @@
#!/usr/bin/perl -w
# (c) Andrei Nigmatulin, 2005
#
# this script provided "as is", without any warranties. use it at your own risk.
#
# special thanx to Andrew Sitnikov for perl port
#
# this script converts CSV geoip database (free download at http://www.maxmind.com/app/geoip_country)
# to format, suitable for use with nginx_http_geo module (http://sysoev.ru/nginx)
#
# for example, line with ip range
#
# "62.16.68.0","62.16.127.255","1041253376","1041268735","RU","Russian Federation"
#
# will be converted to four subnetworks:
#
# 62.16.68.0/22 RU;
# 62.16.72.0/21 RU;
# 62.16.80.0/20 RU;
# 62.16.96.0/19 RU;
use warnings;
use strict;
while( <STDIN> ){
if (/"[^"]+","[^"]+","([^"]+)","([^"]+)","([^"]+)"/){
print_subnets($1, $2, $3);
}
}
sub print_subnets {
my ($a1, $a2, $c) = @_;
my $l;
while ($a1 <= $a2) {
for ($l = 0; ($a1 & (1 << $l)) == 0 && ($a1 + ((1 << ($l + 1)) - 1)) <= $a2; $l++){};
print long2ip($a1) . "/" . (32 - $l) . " " . $c . ";\n";
$a1 += (1 << $l);
}
}
sub long2ip {
my $ip = shift;
my $str = 0;
$str = ($ip & 255);
$ip >>= 8;
$str = ($ip & 255).".$str";
$ip >>= 8;
$str = ($ip & 255).".$str";
$ip >>= 8;
$str = ($ip & 255).".$str";
}

View File

@@ -0,0 +1,131 @@
charset_map koi8-r utf-8 {
80 E29480 ; # BOX DRAWINGS LIGHT HORIZONTAL
81 E29482 ; # BOX DRAWINGS LIGHT VERTICAL
82 E2948C ; # BOX DRAWINGS LIGHT DOWN AND RIGHT
83 E29490 ; # BOX DRAWINGS LIGHT DOWN AND LEFT
84 E29494 ; # BOX DRAWINGS LIGHT UP AND RIGHT
85 E29498 ; # BOX DRAWINGS LIGHT UP AND LEFT
86 E2949C ; # BOX DRAWINGS LIGHT VERTICAL AND RIGHT
87 E294A4 ; # BOX DRAWINGS LIGHT VERTICAL AND LEFT
88 E294AC ; # BOX DRAWINGS LIGHT DOWN AND HORIZONTAL
89 E294B4 ; # BOX DRAWINGS LIGHT UP AND HORIZONTAL
8A E294BC ; # BOX DRAWINGS LIGHT VERTICAL AND HORIZONTAL
8B E29680 ; # UPPER HALF BLOCK
8C E29684 ; # LOWER HALF BLOCK
8D E29688 ; # FULL BLOCK
8E E2968C ; # LEFT HALF BLOCK
8F E29690 ; # RIGHT HALF BLOCK
90 E29691 ; # LIGHT SHADE
91 E29692 ; # MEDIUM SHADE
92 E29693 ; # DARK SHADE
93 E28CA0 ; # TOP HALF INTEGRAL
94 E296A0 ; # BLACK SQUARE
95 E28899 ; # BULLET OPERATOR
96 E2889A ; # SQUARE ROOT
97 E28988 ; # ALMOST EQUAL TO
98 E289A4 ; # LESS-THAN OR EQUAL TO
99 E289A5 ; # GREATER-THAN OR EQUAL TO
9A C2A0 ; # NO-BREAK SPACE
9B E28CA1 ; # BOTTOM HALF INTEGRAL
9C C2B0 ; # DEGREE SIGN
9D C2B2 ; # SUPERSCRIPT TWO
9E C2B7 ; # MIDDLE DOT
9F C3B7 ; # DIVISION SIGN
A0 E29590 ; # BOX DRAWINGS DOUBLE HORIZONTAL
A1 E29591 ; # BOX DRAWINGS DOUBLE VERTICAL
A2 E29592 ; # BOX DRAWINGS DOWN SINGLE AND RIGHT DOUBLE
A3 D191 ; # CYRILLIC SMALL LETTER IO
A4 E29593 ; # BOX DRAWINGS DOWN DOUBLE AND RIGHT SINGLE
A5 E29594 ; # BOX DRAWINGS DOUBLE DOWN AND RIGHT
A6 E29595 ; # BOX DRAWINGS DOWN SINGLE AND LEFT DOUBLE
A7 E29596 ; # BOX DRAWINGS DOWN DOUBLE AND LEFT SINGLE
A8 E29597 ; # BOX DRAWINGS DOUBLE DOWN AND LEFT
A9 E29598 ; # BOX DRAWINGS UP SINGLE AND RIGHT DOUBLE
AA E29599 ; # BOX DRAWINGS UP DOUBLE AND RIGHT SINGLE
AB E2959A ; # BOX DRAWINGS DOUBLE UP AND RIGHT
AC E2959B ; # BOX DRAWINGS UP SINGLE AND LEFT DOUBLE
AD E2959C ; # BOX DRAWINGS UP DOUBLE AND LEFT SINGLE
AE E2959D ; # BOX DRAWINGS DOUBLE UP AND LEFT
AF E2959E ; # BOX DRAWINGS VERTICAL SINGLE AND RIGHT DOUBLE
B0 E2959F ; # BOX DRAWINGS VERTICAL DOUBLE AND RIGHT SINGLE
B1 E295A0 ; # BOX DRAWINGS DOUBLE VERTICAL AND RIGHT
B2 E295A1 ; # BOX DRAWINGS VERTICAL SINGLE AND LEFT DOUBLE
B3 D081 ; # CYRILLIC CAPITAL LETTER IO
B4 E295A2 ; # BOX DRAWINGS VERTICAL DOUBLE AND LEFT SINGLE
B5 E295A3 ; # BOX DRAWINGS DOUBLE VERTICAL AND LEFT
B6 E295A4 ; # BOX DRAWINGS DOWN SINGLE AND HORIZONTAL DOUBLE
B7 E295A5 ; # BOX DRAWINGS DOWN DOUBLE AND HORIZONTAL SINGLE
B8 E295A6 ; # BOX DRAWINGS DOUBLE DOWN AND HORIZONTAL
B9 E295A7 ; # BOX DRAWINGS UP SINGLE AND HORIZONTAL DOUBLE
BA E295A8 ; # BOX DRAWINGS UP DOUBLE AND HORIZONTAL SINGLE
BB E295A9 ; # BOX DRAWINGS DOUBLE UP AND HORIZONTAL
BC E295AA ; # BOX DRAWINGS VERTICAL SINGLE AND HORIZONTAL DOUBLE
BD E295AB ; # BOX DRAWINGS VERTICAL DOUBLE AND HORIZONTAL SINGLE
BE E295AC ; # BOX DRAWINGS DOUBLE VERTICAL AND HORIZONTAL
BF C2A9 ; # COPYRIGHT SIGN
C0 D18E ; # CYRILLIC SMALL LETTER YU
C1 D0B0 ; # CYRILLIC SMALL LETTER A
C2 D0B1 ; # CYRILLIC SMALL LETTER BE
C3 D186 ; # CYRILLIC SMALL LETTER TSE
C4 D0B4 ; # CYRILLIC SMALL LETTER DE
C5 D0B5 ; # CYRILLIC SMALL LETTER IE
C6 D184 ; # CYRILLIC SMALL LETTER EF
C7 D0B3 ; # CYRILLIC SMALL LETTER GHE
C8 D185 ; # CYRILLIC SMALL LETTER HA
C9 D0B8 ; # CYRILLIC SMALL LETTER I
CA D0B9 ; # CYRILLIC SMALL LETTER SHORT I
CB D0BA ; # CYRILLIC SMALL LETTER KA
CC D0BB ; # CYRILLIC SMALL LETTER EL
CD D0BC ; # CYRILLIC SMALL LETTER EM
CE D0BD ; # CYRILLIC SMALL LETTER EN
CF D0BE ; # CYRILLIC SMALL LETTER O
D0 D0BF ; # CYRILLIC SMALL LETTER PE
D1 D18F ; # CYRILLIC SMALL LETTER YA
D2 D180 ; # CYRILLIC SMALL LETTER ER
D3 D181 ; # CYRILLIC SMALL LETTER ES
D4 D182 ; # CYRILLIC SMALL LETTER TE
D5 D183 ; # CYRILLIC SMALL LETTER U
D6 D0B6 ; # CYRILLIC SMALL LETTER ZHE
D7 D0B2 ; # CYRILLIC SMALL LETTER VE
D8 D18C ; # CYRILLIC SMALL LETTER SOFT SIGN
D9 D18B ; # CYRILLIC SMALL LETTER YERU
DA D0B7 ; # CYRILLIC SMALL LETTER ZE
DB D188 ; # CYRILLIC SMALL LETTER SHA
DC D18D ; # CYRILLIC SMALL LETTER E
DD D189 ; # CYRILLIC SMALL LETTER SHCHA
DE D187 ; # CYRILLIC SMALL LETTER CHE
DF D18A ; # CYRILLIC SMALL LETTER HARD SIGN
E0 D0AE ; # CYRILLIC CAPITAL LETTER YU
E1 D090 ; # CYRILLIC CAPITAL LETTER A
E2 D091 ; # CYRILLIC CAPITAL LETTER BE
E3 D0A6 ; # CYRILLIC CAPITAL LETTER TSE
E4 D094 ; # CYRILLIC CAPITAL LETTER DE
E5 D095 ; # CYRILLIC CAPITAL LETTER IE
E6 D0A4 ; # CYRILLIC CAPITAL LETTER EF
E7 D093 ; # CYRILLIC CAPITAL LETTER GHE
E8 D0A5 ; # CYRILLIC CAPITAL LETTER HA
E9 D098 ; # CYRILLIC CAPITAL LETTER I
EA D099 ; # CYRILLIC CAPITAL LETTER SHORT I
EB D09A ; # CYRILLIC CAPITAL LETTER KA
EC D09B ; # CYRILLIC CAPITAL LETTER EL
ED D09C ; # CYRILLIC CAPITAL LETTER EM
EE D09D ; # CYRILLIC CAPITAL LETTER EN
EF D09E ; # CYRILLIC CAPITAL LETTER O
F0 D09F ; # CYRILLIC CAPITAL LETTER PE
F1 D0AF ; # CYRILLIC CAPITAL LETTER YA
F2 D0A0 ; # CYRILLIC CAPITAL LETTER ER
F3 D0A1 ; # CYRILLIC CAPITAL LETTER ES
F4 D0A2 ; # CYRILLIC CAPITAL LETTER TE
F5 D0A3 ; # CYRILLIC CAPITAL LETTER U
F6 D096 ; # CYRILLIC CAPITAL LETTER ZHE
F7 D092 ; # CYRILLIC CAPITAL LETTER VE
F8 D0AC ; # CYRILLIC CAPITAL LETTER SOFT SIGN
F9 D0AB ; # CYRILLIC CAPITAL LETTER YERU
FA D097 ; # CYRILLIC CAPITAL LETTER ZE
FB D0A8 ; # CYRILLIC CAPITAL LETTER SHA
FC D0AD ; # CYRILLIC CAPITAL LETTER E
FD D0A9 ; # CYRILLIC CAPITAL LETTER SHCHA
FE D0A7 ; # CYRILLIC CAPITAL LETTER CHE
FF D0AA ; # CYRILLIC CAPITAL LETTER HARD SIGN
}

View File

@@ -0,0 +1,48 @@
#!/usr/bin/perl -w
# Convert unicode mappings to nginx configuration file format.
# You may find useful mappings in various places, including
# unicode.org official site:
#
# http://www.unicode.org/Public/MAPPINGS/VENDORS/MICSFT/WINDOWS/CP1251.TXT
# http://www.unicode.org/Public/MAPPINGS/VENDORS/MISC/KOI8-R.TXT
# Needs perl 5.6 or later.
# Written by Maxim Dounin, mdounin@mdounin.ru
###############################################################################
require 5.006;
while (<>) {
# Skip comments and empty lines
next if /^#/;
next if /^\s*$/;
chomp;
# Convert mappings
if (/^\s*0x(..)\s*0x(....)\s*(#.*)/) {
# Mapping <from-code> <unicode-code> "#" <unicode-name>
my $cs_code = $1;
my $un_code = $2;
my $un_name = $3;
# Produce UTF-8 sequence from character code;
my $un_utf8 = join('',
map { sprintf("%02X", $_) }
unpack("U0C*", pack("U", hex($un_code)))
);
print " $cs_code $un_utf8 ; $un_name\n";
} else {
warn "Unrecognized line: '$_'";
}
}
###############################################################################

View File

@@ -0,0 +1,130 @@
charset_map windows-1251 utf-8 {
80 D082 ; #CYRILLIC CAPITAL LETTER DJE
81 D083 ; #CYRILLIC CAPITAL LETTER GJE
82 E2809A ; #SINGLE LOW-9 QUOTATION MARK
83 D193 ; #CYRILLIC SMALL LETTER GJE
84 E2809E ; #DOUBLE LOW-9 QUOTATION MARK
85 E280A6 ; #HORIZONTAL ELLIPSIS
86 E280A0 ; #DAGGER
87 E280A1 ; #DOUBLE DAGGER
88 E282AC ; #EURO SIGN
89 E280B0 ; #PER MILLE SIGN
8A D089 ; #CYRILLIC CAPITAL LETTER LJE
8B E280B9 ; #SINGLE LEFT-POINTING ANGLE QUOTATION MARK
8C D08A ; #CYRILLIC CAPITAL LETTER NJE
8D D08C ; #CYRILLIC CAPITAL LETTER KJE
8E D08B ; #CYRILLIC CAPITAL LETTER TSHE
8F D08F ; #CYRILLIC CAPITAL LETTER DZHE
90 D192 ; #CYRILLIC SMALL LETTER DJE
91 E28098 ; #LEFT SINGLE QUOTATION MARK
92 E28099 ; #RIGHT SINGLE QUOTATION MARK
93 E2809C ; #LEFT DOUBLE QUOTATION MARK
94 E2809D ; #RIGHT DOUBLE QUOTATION MARK
95 E280A2 ; #BULLET
96 E28093 ; #EN DASH
97 E28094 ; #EM DASH
99 E284A2 ; #TRADE MARK SIGN
9A D199 ; #CYRILLIC SMALL LETTER LJE
9B E280BA ; #SINGLE RIGHT-POINTING ANGLE QUOTATION MARK
9C D19A ; #CYRILLIC SMALL LETTER NJE
9D D19C ; #CYRILLIC SMALL LETTER KJE
9E D19B ; #CYRILLIC SMALL LETTER TSHE
9F D19F ; #CYRILLIC SMALL LETTER DZHE
A0 C2A0 ; #NO-BREAK SPACE
A1 D08E ; #CYRILLIC CAPITAL LETTER SHORT U
A2 D19E ; #CYRILLIC SMALL LETTER SHORT U
A3 D088 ; #CYRILLIC CAPITAL LETTER JE
A4 C2A4 ; #CURRENCY SIGN
A5 D290 ; #CYRILLIC CAPITAL LETTER GHE WITH UPTURN
A6 C2A6 ; #BROKEN BAR
A7 C2A7 ; #SECTION SIGN
A8 D081 ; #CYRILLIC CAPITAL LETTER IO
A9 C2A9 ; #COPYRIGHT SIGN
AA D084 ; #CYRILLIC CAPITAL LETTER UKRAINIAN IE
AB C2AB ; #LEFT-POINTING DOUBLE ANGLE QUOTATION MARK
AC C2AC ; #NOT SIGN
AD C2AD ; #SOFT HYPHEN
AE C2AE ; #REGISTERED SIGN
AF D087 ; #CYRILLIC CAPITAL LETTER YI
B0 C2B0 ; #DEGREE SIGN
B1 C2B1 ; #PLUS-MINUS SIGN
B2 D086 ; #CYRILLIC CAPITAL LETTER BYELORUSSIAN-UKRAINIAN I
B3 D196 ; #CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I
B4 D291 ; #CYRILLIC SMALL LETTER GHE WITH UPTURN
B5 C2B5 ; #MICRO SIGN
B6 C2B6 ; #PILCROW SIGN
B7 C2B7 ; #MIDDLE DOT
B8 D191 ; #CYRILLIC SMALL LETTER IO
B9 E28496 ; #NUMERO SIGN
BA D194 ; #CYRILLIC SMALL LETTER UKRAINIAN IE
BB C2BB ; #RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK
BC D198 ; #CYRILLIC SMALL LETTER JE
BD D085 ; #CYRILLIC CAPITAL LETTER DZE
BE D195 ; #CYRILLIC SMALL LETTER DZE
BF D197 ; #CYRILLIC SMALL LETTER YI
C0 D090 ; #CYRILLIC CAPITAL LETTER A
C1 D091 ; #CYRILLIC CAPITAL LETTER BE
C2 D092 ; #CYRILLIC CAPITAL LETTER VE
C3 D093 ; #CYRILLIC CAPITAL LETTER GHE
C4 D094 ; #CYRILLIC CAPITAL LETTER DE
C5 D095 ; #CYRILLIC CAPITAL LETTER IE
C6 D096 ; #CYRILLIC CAPITAL LETTER ZHE
C7 D097 ; #CYRILLIC CAPITAL LETTER ZE
C8 D098 ; #CYRILLIC CAPITAL LETTER I
C9 D099 ; #CYRILLIC CAPITAL LETTER SHORT I
CA D09A ; #CYRILLIC CAPITAL LETTER KA
CB D09B ; #CYRILLIC CAPITAL LETTER EL
CC D09C ; #CYRILLIC CAPITAL LETTER EM
CD D09D ; #CYRILLIC CAPITAL LETTER EN
CE D09E ; #CYRILLIC CAPITAL LETTER O
CF D09F ; #CYRILLIC CAPITAL LETTER PE
D0 D0A0 ; #CYRILLIC CAPITAL LETTER ER
D1 D0A1 ; #CYRILLIC CAPITAL LETTER ES
D2 D0A2 ; #CYRILLIC CAPITAL LETTER TE
D3 D0A3 ; #CYRILLIC CAPITAL LETTER U
D4 D0A4 ; #CYRILLIC CAPITAL LETTER EF
D5 D0A5 ; #CYRILLIC CAPITAL LETTER HA
D6 D0A6 ; #CYRILLIC CAPITAL LETTER TSE
D7 D0A7 ; #CYRILLIC CAPITAL LETTER CHE
D8 D0A8 ; #CYRILLIC CAPITAL LETTER SHA
D9 D0A9 ; #CYRILLIC CAPITAL LETTER SHCHA
DA D0AA ; #CYRILLIC CAPITAL LETTER HARD SIGN
DB D0AB ; #CYRILLIC CAPITAL LETTER YERU
DC D0AC ; #CYRILLIC CAPITAL LETTER SOFT SIGN
DD D0AD ; #CYRILLIC CAPITAL LETTER E
DE D0AE ; #CYRILLIC CAPITAL LETTER YU
DF D0AF ; #CYRILLIC CAPITAL LETTER YA
E0 D0B0 ; #CYRILLIC SMALL LETTER A
E1 D0B1 ; #CYRILLIC SMALL LETTER BE
E2 D0B2 ; #CYRILLIC SMALL LETTER VE
E3 D0B3 ; #CYRILLIC SMALL LETTER GHE
E4 D0B4 ; #CYRILLIC SMALL LETTER DE
E5 D0B5 ; #CYRILLIC SMALL LETTER IE
E6 D0B6 ; #CYRILLIC SMALL LETTER ZHE
E7 D0B7 ; #CYRILLIC SMALL LETTER ZE
E8 D0B8 ; #CYRILLIC SMALL LETTER I
E9 D0B9 ; #CYRILLIC SMALL LETTER SHORT I
EA D0BA ; #CYRILLIC SMALL LETTER KA
EB D0BB ; #CYRILLIC SMALL LETTER EL
EC D0BC ; #CYRILLIC SMALL LETTER EM
ED D0BD ; #CYRILLIC SMALL LETTER EN
EE D0BE ; #CYRILLIC SMALL LETTER O
EF D0BF ; #CYRILLIC SMALL LETTER PE
F0 D180 ; #CYRILLIC SMALL LETTER ER
F1 D181 ; #CYRILLIC SMALL LETTER ES
F2 D182 ; #CYRILLIC SMALL LETTER TE
F3 D183 ; #CYRILLIC SMALL LETTER U
F4 D184 ; #CYRILLIC SMALL LETTER EF
F5 D185 ; #CYRILLIC SMALL LETTER HA
F6 D186 ; #CYRILLIC SMALL LETTER TSE
F7 D187 ; #CYRILLIC SMALL LETTER CHE
F8 D188 ; #CYRILLIC SMALL LETTER SHA
F9 D189 ; #CYRILLIC SMALL LETTER SHCHA
FA D18A ; #CYRILLIC SMALL LETTER HARD SIGN
FB D18B ; #CYRILLIC SMALL LETTER YERU
FC D18C ; #CYRILLIC SMALL LETTER SOFT SIGN
FD D18D ; #CYRILLIC SMALL LETTER E
FE D18E ; #CYRILLIC SMALL LETTER YU
FF D18F ; #CYRILLIC SMALL LETTER YA
}

View File

@@ -0,0 +1,4 @@
au BufRead,BufNewFile *.nginx set ft=nginx
au BufRead,BufNewFile */etc/nginx/* set ft=nginx
au BufRead,BufNewFile */usr/local/nginx/conf/* set ft=nginx
au BufRead,BufNewFile nginx.conf set ft=nginx

View File

@@ -0,0 +1 @@
setlocal commentstring=#\ %s

View File

@@ -0,0 +1,11 @@
if exists("b:did_indent")
finish
endif
let b:did_indent = 1
setlocal indentexpr=
" cindent actually works for nginx' simple file structure
setlocal cindent
" Just make sure that the comments are not reset as defs would be.
setlocal cinkeys-=0#

File diff suppressed because it is too large Load Diff

9290
nginx-1.28.0/docs/CHANGES Normal file

File diff suppressed because it is too large Load Diff

9453
nginx-1.28.0/docs/CHANGES.ru Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,126 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio economic status,
nationality, personal appearance, race, caste, color, religion, or sexual
identity and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
Examples of behavior that contributes to a positive environment for our
community include:
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes
and learning from the experience
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
- The use of sexualized language or imagery, and sexual attention or advances
of any kind
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email address,
without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards
of acceptable behavior and will take appropriate and fair corrective action
in response to any behavior that they deem inappropriate, threatening,
offensive, or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for
moderation decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official email address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
<nginx-oss-community@f5.com>. All complaints will be reviewed and investigated
promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external
channels like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the
[Contributor Covenant](https://www.contributor-covenant.org), version 2.1,
available at
<https://www.contributor-covenant.org/version/2/1/code_of_conduct.html>.
Community Impact Guidelines were inspired by
[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/inclusion).
For answers to common questions about this code of conduct, see the FAQ at
<https://www.contributor-covenant.org/faq>. Translations are available at
<https://www.contributor-covenant.org/translations>.

View File

@@ -0,0 +1,110 @@
# Contributing Guidelines
The following is a set of guidelines for contributing to nginx project.
We really appreciate that you are considering contributing!
## Table of Contents
- [Ask a Question](#ask-a-question)
- [Report a Bug](#report-a-bug)
- [Suggest a Feature or Enhancement](#suggest-a-feature-or-enhancement)
- [Open a Discussion](#open-a-discussion)
- [Submit a Pull Request](#submit-a-pull-request)
- [Issue Lifecycle](#issue-lifecycle)
## Ask a Question
To ask a question, open an issue on GitHub with the label `question`.
## Report a Bug
To report a bug, open an issue on GitHub with the label `bug` using the
available bug report issue template. Before reporting a bug, make sure the
issue has not already been reported.
## Suggest a Feature or Enhancement
To suggest a feature or enhancement, open an issue on GitHub with the label
`feature` or `enhancement` using the available feature request issue template.
Please ensure the feature or enhancement has not already been suggested.
## Open a Discussion
If you want to engage in a conversation with the community and maintainers,
we encourage you to use
[GitHub Discussions](https://github.com/nginx/nginx/discussions).
## Submit a Pull Request
Follow this plan to contribute a change to NGINX source code:
- Fork the NGINX repository
- Create a branch
- Implement your changes in this branch
- Submit a pull request (PR) when your changes are tested and ready for review
Refer to
[NGINX Development Guide](https://nginx.org/en/docs/dev/development_guide.html)
for questions about NGINX programming.
### Formatting Changes
- Changes should be formatted according to the
[code style](https://nginx.org/en/docs/dev/development_guide.html#code_style)
used by NGINX; sometimes, there is no clear rule, in which case examine how
existing NGINX sources are formatted and mimic this style; changes will more
likely be accepted if style corresponds to the surrounding code
- Keep a clean, concise and meaningful commit history on your branch, rebasing
locally and breaking changes logically into commits before submitting a PR
- Each commit message should have a single-line subject line followed by verbose
description after an empty line
- Limit the subject line to 67 characters, and the rest of the commit message
to 76 characters
- Use subject line prefixes for commits that affect a specific portion of the
code; examples include "Upstream:", "QUIC:", or "Core:"; see the commit history
to get an idea of the prefixes used
- Reference issues in the the subject line; if the commit fixes an issue,
[name it](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue)
accordingly
### Before Submitting
- The proposed changes should work properly on a wide range of
[supported platforms](https://nginx.org/en/index.html#tested_os_and_platforms)
- Try to make it clear why the suggested change is needed, and provide a use
case, if possible
- Passing your changes through the test suite is a good way to ensure that they
do not cause a regression; the repository with tests can be cloned with the
following command:
```bash
git clone https://github.com/nginx/nginx-tests.git
```
- Submitting a change implies granting project a permission to use it under the
[BSD-2-Clause license](https://github.com/nginx/nginx/blob/master/LICENSE)
## Issue Lifecycle
To ensure a balance between work carried out by the NGINX engineering team
while encouraging community involvement on this project, we use the following
issue lifecycle:
- A new issue is created by a community member
- An owner on the NGINX engineering team is assigned to the issue; this
owner shepherds the issue through the subsequent stages in the issue lifecycle
- The owner assigns one or more
[labels](https://github.com/nginx/nginx/issues/labels) to the issue
- The owner, in collaboration with the wider team (product management and
engineering), determines what milestone to attach to an issue;
generally, milestones correspond to product releases

24
nginx-1.28.0/docs/LICENSE Normal file
View File

@@ -0,0 +1,24 @@
Copyright (C) 2002-2021 Igor Sysoev
Copyright (C) 2011-2025 Nginx, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.

View File

@@ -0,0 +1,177 @@
Apache License
Version 2.0, January 2004
https://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -0,0 +1,94 @@
PCRE2 LICENCE
-------------
PCRE2 is a library of functions to support regular expressions whose syntax
and semantics are as close as possible to those of the Perl 5 language.
Releases 10.00 and above of PCRE2 are distributed under the terms of the "BSD"
licence, as specified below, with one exemption for certain binary
redistributions. The documentation for PCRE2, supplied in the "doc" directory,
is distributed under the same terms as the software itself. The data in the
testdata directory is not copyrighted and is in the public domain.
The basic library functions are written in C and are freestanding. Also
included in the distribution is a just-in-time compiler that can be used to
optimize pattern matching. This is an optional feature that can be omitted when
the library is built.
THE BASIC LIBRARY FUNCTIONS
---------------------------
Written by: Philip Hazel
Email local part: Philip.Hazel
Email domain: gmail.com
Retired from University of Cambridge Computing Service,
Cambridge, England.
Copyright (c) 1997-2021 University of Cambridge
All rights reserved.
PCRE2 JUST-IN-TIME COMPILATION SUPPORT
--------------------------------------
Written by: Zoltan Herczeg
Email local part: hzmester
Email domain: freemail.hu
Copyright(c) 2010-2021 Zoltan Herczeg
All rights reserved.
STACK-LESS JUST-IN-TIME COMPILER
--------------------------------
Written by: Zoltan Herczeg
Email local part: hzmester
Email domain: freemail.hu
Copyright(c) 2009-2021 Zoltan Herczeg
All rights reserved.
THE "BSD" LICENCE
-----------------
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notices,
this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notices, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
* Neither the name of the University of Cambridge nor the names of any
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
EXEMPTION FOR BINARY LIBRARY-LIKE PACKAGES
------------------------------------------
The second condition in the BSD licence (covering binary redistributions) does
not apply all the way down a chain of software. If binary package A includes
PCRE2, it must respect the condition, but if package B is software that
includes package A, the condition is not imposed on package B unless it uses
PCRE2 independently.
End

230
nginx-1.28.0/docs/README.md Normal file
View File

@@ -0,0 +1,230 @@
<picture>
<source media="(prefers-color-scheme: dark)" srcset="https://github.com/user-attachments/assets/9335b488-ffcc-4157-8364-2370a0b70ad0">
<source media="(prefers-color-scheme: light)" srcset="https://github.com/user-attachments/assets/3a7eeb08-1133-47f5-859c-fad4f5a6a013">
<img alt="NGINX Banner">
</picture>
NGINX (pronounced "engine x" or "en-jin-eks") is the world's most popular Web Server, high performance Load Balancer, Reverse Proxy, API Gateway and Content Cache.
NGINX is free and open source software, distributed under the terms of a simplified [2-clause BSD-like license](LICENSE).
Enterprise distributions, commercial support and training are available from [F5, Inc](https://www.f5.com/products/nginx).
> [!IMPORTANT]
> The goal of this README is to provide a basic, structured introduction to NGINX for novice users. Please refer to the [full NGINX documentation](https://nginx.org/en/docs/) for detailed information on [installing](https://nginx.org/en/docs/install.html), [building](https://nginx.org/en/docs/configure.html), [configuring](https://nginx.org/en/docs/dirindex.html), [debugging](https://nginx.org/en/docs/debugging_log.html), and more. These documentation pages also contain a more detailed [Beginners Guide](https://nginx.org/en/docs/beginners_guide.html), How-Tos, [Development guide](https://nginx.org/en/docs/dev/development_guide.html), and a complete module and [directive reference](https://nginx.org/en/docs/dirindex.html).
# Table of contents
- [How it works](#how-it-works)
- [Modules](#modules)
- [Configurations](#configurations)
- [Runtime](#runtime)
- [Downloading and installing](#downloading-and-installing)
- [Stable and Mainline binaries](#stable-and-mainline-binaries)
- [Linux binary installation process](#linux-binary-installation-process)
- [FreeBSD installation process](#freebsd-installation-process)
- [Windows executables](#windows-executables)
- [Dynamic modules](#dynamic-modules)
- [Getting started with NGINX](#getting-started-with-nginx)
- [Installing SSL certificates and enabling TLS encryption](#installing-ssl-certificates-and-enabling-tls-encryption)
- [Load Balancing](#load-balancing)
- [Rate limiting](#rate-limiting)
- [Content caching](#content-caching)
- [Building from source](#building-from-source)
- [Installing dependencies](#installing-dependencies)
- [Cloning the NGINX GitHub repository](#cloning-the-nginx-github-repository)
- [Configuring the build](#configuring-the-build)
- [Compiling](#compiling)
- [Location of binary and installation](#location-of-binary-and-installation)
- [Running and testing the installed binary](#running-and-testing-the-installed-binary)
- [Asking questions and reporting issues](#asking-questions-and-reporting-issues)
- [Contributing code](#contributing-code)
- [Additional help and resources](#additional-help-and-resources)
- [Changelog](#changelog)
- [License](#license)
# How it works
NGINX is installed software with binary packages available for all major operating systems and Linux distributions. See [Tested OS and Platforms](https://nginx.org/en/#tested_os_and_platforms) for a full list of compatible systems.
> [!IMPORTANT]
> While nearly all popular Linux-based operating systems are distributed with a community version of nginx, we highly advise installation and usage of official [packages](https://nginx.org/en/linux_packages.html) or sources from this repository. Doing so ensures that you're using the most recent release or source code, including the latest feature-set, fixes and security patches.
## Modules
NGINX is comprised of individual modules, each extending core functionality by providing additional, configurable features. See "Modules reference" at the bottom of [nginx documentation](https://nginx.org/en/docs/) for a complete list of official modules.
NGINX modules can be built and distributed as static or dynamic modules. Static modules are defined at build-time, compiled, and distributed in the resulting binaries. See [Dynamic Modules](#dynamic-modules) for more information on how they work, as well as, how to obtain, install, and configure them.
> [!TIP]
> You can issue the following command to see which static modules your NGINX binaries were built with:
```bash
nginx -V
```
> See [Configuring the build](#configuring-the-build) for information on how to include specific Static modules into your nginx build.
## Configurations
NGINX is highly flexible and configurable. Provisioning the software is achieved via text-based config file(s) accepting parameters called "[Directives](https://nginx.org/en/docs/dirindex.html)". See [Configuration File's Structure](https://nginx.org/en/docs/beginners_guide.html#conf_structure) for a comprehensive description of how NGINX configuration files work.
> [!NOTE]
> The set of directives available to your distribution of NGINX is dependent on which [modules](#modules) have been made available to it.
## Runtime
Rather than running in a single, monolithic process, NGINX is architected to scale beyond Operating System process limitations by operating as a collection of processes. They include:
- A "master" process that maintains worker processes, as well as, reads and evaluates configuration files.
- One or more "worker" processes that process data (eg. HTTP requests).
The number of [worker processes](https://nginx.org/en/docs/ngx_core_module.html#worker_processes) is defined in the configuration file and may be fixed for a given configuration or automatically adjusted to the number of available CPU cores. In most cases, the latter option optimally balances load across available system resources, as NGINX is designed to efficiently distribute work across all worker processes.
> [!TIP]
> Processes synchronize data through shared memory. For this reason, many NGINX directives require the allocation of shared memory zones. As an example, when configuring [rate limiting](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req), connecting clients may need to be tracked in a [common memory zone](https://nginx.org/en/docs/http/ngx_http_limit_req_module.html#limit_req_zone) so all worker processes can know how many times a particular client has accessed the server in a span of time.
# Downloading and installing
Follow these steps to download and install precompiled NGINX binaries. You may also choose to [build NGINX locally from source code](#building-from-source).
## Stable and Mainline binaries
NGINX binaries are built and distributed in two versions: stable and mainline. Stable binaries are built from stable branches and only contain critical fixes backported from the mainline version. Mainline binaries are built from the [master branch](https://github.com/nginx/nginx/tree/master) and contain the latest features and bugfixes. You'll need to [decide which is appropriate for your purposes](https://docs.nginx.com/nginx/admin-guide/installing-nginx/installing-nginx-open-source/#choosing-between-a-stable-or-a-mainline-version).
## Linux binary installation process
The NGINX binary installation process takes advantage of package managers native to specific Linux distributions. For this reason, first-time installations involve adding the official NGINX package repository to your system's package manager. Follow [these steps](https://nginx.org/en/linux_packages.html) to download, verify, and install NGINX binaries using the package manager appropriate for your Linux distribution.
### Upgrades
Future upgrades to the latest version can be managed using the same package manager without the need to manually download and verify binaries.
## FreeBSD installation process
For more information on installing NGINX on FreeBSD system, visit https://nginx.org/en/docs/install.html
## Windows executables
Windows executables for mainline and stable releases can be found on the main [NGINX download page](https://nginx.org/en/download.html). Note that the current implementation of NGINX for Windows is at the Proof-of-Concept stage and should only be used for development and testing purposes. For additional information, please see [nginx for Windows](https://nginx.org/en/docs/windows.html).
## Dynamic modules
NGINX version 1.9.11 added support for [Dynamic Modules](https://nginx.org/en/docs/ngx_core_module.html#load_module). Unlike Static modules, dynamically built modules can be downloaded, installed, and configured after the core NGINX binaries have been built. [Official dynamic module binaries](https://nginx.org/en/linux_packages.html#dynmodules) are available from the same package repository as the core NGINX binaries described in previous steps.
> [!TIP]
> [NGINX JavaScript (njs)](https://github.com/nginx/njs), is a popular NGINX dynamic module that enables the extension of core NGINX functionality using familiar JavaScript syntax.
> [!IMPORTANT]
> If desired, dynamic modules can also be built statically into NGINX at compile time.
# Getting started with NGINX
For a gentle introduction to NGINX basics, please see our [Beginners Guide](https://nginx.org/en/docs/beginners_guide.html).
## Installing SSL certificates and enabling TLS encryption
See [Configuring HTTPS servers](https://nginx.org/en/docs/http/configuring_https_servers.html) for a quick guide on how to enable secure traffic to your NGINX installation.
## Load Balancing
For a quick start guide on configuring NGINX as a Load Balancer, please see [Using nginx as HTTP load balancer](https://nginx.org/en/docs/http/load_balancing.html).
## Rate limiting
See our [Rate Limiting with NGINX](https://blog.nginx.org/blog/rate-limiting-nginx) blog post for an overview of core concepts for provisioning NGINX as an API Gateway.
## Content caching
See [A Guide to Caching with NGINX and NGINX Plus](https://blog.nginx.org/blog/nginx-caching-guide) blog post for an overview of how to use NGINX as a content cache (e.g. edge server of a content delivery network).
# Building from source
The following steps can be used to build NGINX from source code available in this repository.
## Installing dependencies
Most Linux distributions will require several dependencies to be installed in order to build NGINX. The following instructions are specific to the `apt` package manager, widely available on most Ubuntu/Debian distributions and their derivatives.
> [!TIP]
> It is always a good idea to update your package repository lists prior to installing new packages.
> ```bash
> sudo apt update
> ```
### Installing compiler and make utility
Use the following command to install the GNU C compiler and Make utility.
```bash
sudo apt install gcc make
```
### Installing dependency libraries
```bash
sudo apt install libpcre3-dev zlib1g-dev
```
> [!WARNING]
> This is the minimal set of dependency libraries needed to build NGINX with rewriting and gzip capabilities. Other dependencies may be required if you choose to build NGINX with additional modules. Monitor the output of the `configure` command discussed in the following sections for information on which modules may be missing. For example, if you plan to use SSL certificates to encrypt traffic with TLS, you'll need to install the OpenSSL library. To do so, issue the following command.
>```bash
>sudo apt install libssl-dev
## Cloning the NGINX GitHub repository
Using your preferred method, clone the NGINX repository into your development directory. See [Cloning a GitHub Repository](https://docs.github.com/en/repositories/creating-and-managing-repositories/cloning-a-repository) for additional help.
```bash
git clone https://github.com/nginx/nginx.git
```
## Configuring the build
Prior to building NGINX, you must run the `configure` script with [appropriate flags](https://nginx.org/en/docs/configure.html). This will generate a Makefile in your NGINX source root directory that can then be used to compile NGINX with [options specified during configuration](https://nginx.org/en/docs/configure.html).
From the NGINX source code repository's root directory:
```bash
auto/configure
```
> [!IMPORTANT]
> Configuring the build without any flags will compile NGINX with the default set of options. Please refer to https://nginx.org/en/docs/configure.html for a full list of available build configuration options.
## Compiling
The `configure` script will generate a `Makefile` in the NGINX source root directory upon successful execution. To compile NGINX into a binary, issue the following command from that same directory:
```bash
make
```
## Location of binary and installation
After successful compilation, a binary will be generated at `<NGINX_SRC_ROOT_DIR>/objs/nginx`. To install this binary, issue the following command from the source root directory:
```bash
sudo make install
```
> [!IMPORTANT]
> The binary will be installed into the `/usr/local/nginx/` directory.
## Running and testing the installed binary
To run the installed binary, issue the following command:
```bash
sudo /usr/local/nginx/sbin/nginx
```
You may test NGINX operation using `curl`.
```bash
curl localhost
```
The output of which should start with:
```html
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
```
# Asking questions and reporting issues
We encourage you to engage with us.
- [NGINX GitHub Discussions](https://github.com/nginx/nginx/discussions), is the go-to place to start asking questions and sharing your thoughts.
- Our [GitHub Issues](https://github.com/nginx/nginx/issues) page offers space to submit and discuss specific issues, report bugs, and suggest enhancements.
# Contributing code
Please see the [Contributing](CONTRIBUTING.md) guide for information on how to contribute code.
# Additional help and resources
- See the [NGINX Community Blog](https://blog.nginx.org/) for more tips, tricks and HOW-TOs related to NGINX and related projects.
- Access [nginx.org](https://nginx.org/), your go-to source for all documentation, information and software related to the NGINX suite of projects.
# Changelog
See our [changelog](https://nginx.org/en/CHANGES) to keep track of updates.
# License
[2-clause BSD-like license](LICENSE)
---
Additional documentation available at: https://nginx.org/en/docs

View File

@@ -0,0 +1,103 @@
# Security Policy
This document provides an overview of security concerns related to nginx
deployments, focusing on confidentiality, integrity, availability, and the
implications of configurations and misconfigurations.
## Reporting a Vulnerability
Please report any vulnerabilities via one of the following methods
(in order of preference):
1. [Report a vulnerability](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability)
within this repository. We are using the GitHub workflow that allows us to
manage vulnerabilities in a private manner and interact with reporters
securely.
2. [Report directly to F5](https://www.f5.com/services/support/report-a-vulnerability).
3. Report via email to security-alert@nginx.org.
This method will be deprecated in the future.
### Vulnerability Disclosure and Fix Process
The nginx team expects that all suspected vulnerabilities be reported
privately via the
[Reporting a Vulnerability](SECURITY.md#reporting-a-vulnerability) guidelines.
If a publicly released vulnerability is reported, we
may request to handle it according to the private disclosure process.
If the reporter agrees, we will follow the private disclosure process.
Security fixes will be applied to all supported stable releases, as well
as the mainline version, as applicable. We recommend using the most recent
mainline or stable release of nginx. Fixes are created and tested by the core
team using a GitHub private fork for security. If necessary, the reporter
may be invited to contribute to the fork and assist with the solution.
The nginx team is committed to responsible information disclosure with
sufficient detail, such as the CVSS score and vector. Privately disclosed
vulnerabilities are embargoed by default until the fix is released.
Communications and fixes remain private until made public. As nginx is
supported by F5, we generally follow the
[F5 security vulnerability response policy](https://my.f5.com/manage/s/article/K4602).
### Vulnerability Disclosure and Fix Service Level Objectives
- We will acknowledge all vulnerability reports within 1 to 3 days.
- Fixes will be developed and released within 90 days from the date of
disclosure. If an extension is needed, we will work with the disclosing person.
- Publicly disclosed (i.e., Zero-Day vulnerabilities) will be addressed ASAP.
## Confidentiality, Integrity, and Availability
### Confidentiality and Integrity
Vulnerabilities compromising data confidentiality or integrity are considered
the highest priority. Any issue leading to unauthorized data access, leaks, or
manipulation will trigger the security release process.
### Availability
Availability issues must meet the following criteria to trigger the security
release process:
- Is present in a standard module included with nginx.
- Arises from traffic that the module is designed to handle.
- Resource exhaustion issues are not mitigated by existing timeout, rate
limiting, or buffer size configurations, or applying changes is impractical.
- Results in highly asymmetric, extreme resource consumption.
Availability issues excluded from the security release process:
- Local file content or upstream response content resulting only in worker
process termination.
- Issues with experimental features which result only in availability impact.
## Trusted Configurations and Misconfigurations
In nginx, configuration files, modules, certificate/key pairs, nginx JavaScript,
and local file content are considered trusted sources. Issues arising from
loading or execution of these trusted components are not considered
vulnerabilities. Operators are responsible for securing and maintaining the
integrity of these sources. Misconfigurations can create vulnerabilities, and
operators should implement configurations according to best practices, review
them regularly, and apply security updates.
## Data Plane vs. Control Plane
The data plane handles traffic through nginx, directly interacting with user
data. nginx inherently trusts the content and instructions from upstream
servers. The control plane governs configuration, management, and orchestration.
Misconfigurations or vulnerabilities in the control plane can cause improper
behavior in the data plane.
## Modules Under Scope
The policy applies to all nginx modules included in this repository. Security
considerations and attack vectors for each module will be identified, with
recommended configurations to mitigate risks.
## Debug Logging and Core Files
Debug logs and core files produced by nginx may contain un-sanitized data,
including sensitive information like client requests, server configurations,
and private key material. These artifacts must be handled carefully to avoid
exposing confidential data.

View File

@@ -0,0 +1,20 @@
(C) 1995-2024 Jean-loup Gailly and Mark Adler
This software is provided 'as-is', without any express or implied
warranty. In no event will the authors be held liable for any damages
arising from the use of this software.
Permission is granted to anyone to use this software for any purpose,
including commercial applications, and to alter it and redistribute it
freely, subject to the following restrictions:
1. The origin of this software must not be misrepresented; you must not
claim that you wrote the original software. If you use this software
in a product, an acknowledgment in the product documentation would be
appreciated but is not required.
2. Altered source versions must be plainly marked as such, and must not be
misrepresented as being the original software.
3. This notice may not be removed or altered from any source distribution.
Jean-loup Gailly Mark Adler
jloup@gzip.org madler@alumni.caltech.edu

View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html>
<head>
<title>Error</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>An error occurred.</h1>
<p>Sorry, the page you are looking for is currently unavailable.<br/>
Please try again later.</p>
<p>If you are the system administrator of this resource then you should check
the error log for details.</p>
<p><em>Faithfully yours, nginx.</em></p>
</body>
</html>

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>Welcome to nginx!</title>
<style>
html { color-scheme: light dark; }
body { width: 35em; margin: 0 auto;
font-family: Tahoma, Verdana, Arial, sans-serif; }
</style>
</head>
<body>
<h1>Welcome to nginx!</h1>
<p>If you see this page, the nginx web server is successfully installed and
working. Further configuration is required.</p>
<p>For online documentation and support please refer to
<a href="http://nginx.org/">nginx.org</a>.<br/>
Commercial support is available at
<a href="http://nginx.com/">nginx.com</a>.</p>
<p><em>Thank you for using nginx.</em></p>
</body>
</html>

View File

BIN
nginx-1.28.0/nginx.exe Normal file

Binary file not shown.

View File

View File

@@ -1,12 +0,0 @@
apiVersion: skaffold/v4beta13
kind: Config
metadata:
name: bio-backend
build:
artifacts:
- image: backend-0.0.1
docker:
dockerfile: Dockerfile
manifests:
rawYaml:
- k8s/deployment.yaml

View File

@@ -2,10 +2,12 @@ package com.bio.bio_backend;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing; import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
@SpringBootApplication @SpringBootApplication
@EnableJpaAuditing @EnableJpaAuditing
@EnableConfigurationProperties
public class BioBackendApplication { public class BioBackendApplication {
public static void main(String[] args) { public static void main(String[] args) {

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_TARGET_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,101 @@
package com.bio.bio_backend.domain.base.file.controller;
import com.bio.bio_backend.domain.base.file.dto.FileUploadRequestDto;
import com.bio.bio_backend.domain.base.file.dto.FileUploadResponseDto;
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 com.bio.bio_backend.global.dto.ApiResponseDto;
import com.bio.bio_backend.global.constants.ApiResponseCode;
import com.bio.bio_backend.global.annotation.LogExecution;
import com.bio.bio_backend.global.utils.FileUtils;
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.core.io.ByteArrayResource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import com.bio.bio_backend.domain.base.file.entity.File;
@Tag(name = "File", description = "파일 업로드/다운로드 API")
@RestController
@RequestMapping("/files")
@RequiredArgsConstructor
@Slf4j
public class FileController {
private final FileService fileService;
@LogExecution("파일 업로드")
@Operation(summary = "파일 업로드", description = "단일 파일을 업로드합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "파일 업로드 성공"),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content(schema = @Schema(implementation = ApiResponseDto.class))),
@ApiResponse(responseCode = "500", description = "파일 업로드 실패", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
})
@PostMapping("/upload")
public ResponseEntity<ApiResponseDto<FileUploadResponseDto>> uploadFile(
@ModelAttribute FileUploadRequestDto requestDto) {
FileUploadResponseDto responseDto = fileService.uploadFile(requestDto);
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.FILE_UPLOAD_SUCCESS, responseDto));
}
@LogExecution("다중 파일 업로드")
@Operation(summary = "다중 파일 업로드", description = "여러 파일을 동시에 업로드합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "다중 파일 업로드 성공"),
@ApiResponse(responseCode = "400", description = "잘못된 요청 데이터", content = @Content(schema = @Schema(implementation = ApiResponseDto.class))),
@ApiResponse(responseCode = "500", description = "다중 파일 업로드 실패", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
})
@PostMapping("/upload-multiple")
public ResponseEntity<ApiResponseDto<MultipleFileUploadResponseDto>> uploadMultipleFiles(
@ModelAttribute MultipleFileUploadRequestDto requestDto) {
MultipleFileUploadResponseDto responseDto = fileService.uploadMultipleFiles(requestDto);
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.FILE_UPLOAD_SUCCESS, responseDto));
}
@LogExecution("파일 다운로드")
@Operation(summary = "파일 다운로드", description = "파일 ID로 파일을 다운로드합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "파일 다운로드 성공"),
@ApiResponse(responseCode = "404", description = "파일을 찾을 수 없음", content = @Content(schema = @Schema(implementation = ApiResponseDto.class))),
@ApiResponse(responseCode = "500", description = "파일 다운로드 실패", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
})
@GetMapping("/download/{oid}")
public ResponseEntity<ByteArrayResource> downloadFile(@PathVariable Long oid) {
// 파일 정보 먼저 조회
File file = fileService.getFileByOid(oid);
byte[] fileData = fileService.downloadFile(oid);
ByteArrayResource resource = new ByteArrayResource(fileData);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=\"" + file.getOriginalFileName() + "\"")
.header(HttpHeaders.CONTENT_TYPE, file.getContentType())
.body(resource);
}
@LogExecution("파일 논리적 삭제")
@Operation(summary = "파일 논리적 삭제", description = "파일 ID로 파일을 논리적으로 삭제합니다. (use_flag를 false로 변경)")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "파일 논리적 삭제 성공"),
@ApiResponse(responseCode = "404", description = "파일을 찾을 수 없음", content = @Content(schema = @Schema(implementation = ApiResponseDto.class))),
@ApiResponse(responseCode = "500", description = "파일 논리적 삭제 실패", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
})
@DeleteMapping("/{oid}")
public ResponseEntity<ApiResponseDto<Void>> deleteFile(@PathVariable Long oid) {
fileService.deleteFile(oid);
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.FILE_DELETE_SUCCESS));
}
}

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

@@ -0,0 +1,10 @@
package com.bio.bio_backend.domain.base.file.dto;
import lombok.Data;
import org.springframework.web.multipart.MultipartFile;
@Data
public class FileUploadRequestDto {
private MultipartFile file;
private String description;
}

View File

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

View File

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

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 MultipleFileUploadResponseDto {
private List<FileUploadResponseDto> files;
private Long groupOid;
private int totalCount;
private int successCount;
private int failureCount;
private List<String> errorMessages;
}

View File

@@ -0,0 +1,41 @@
package com.bio.bio_backend.domain.base.file.entity;
import com.bio.bio_backend.global.constants.AppConstants;
import com.bio.bio_backend.global.entity.BaseEntity;
import jakarta.persistence.*;
import lombok.*;
@Entity
@Table(name = AppConstants.TABLE_PREFIX + "file")
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class File extends BaseEntity {
@Column(nullable = false)
private String originalFileName;
@Column(nullable = false)
private String storedFileName;
@Column(nullable = false)
private String filePath;
@Column(nullable = false)
private Long fileSize;
@Column(nullable = false)
private String contentType;
@Column
private String description;
@Column
private Long groupOid;
@Column(nullable = false)
@Builder.Default
private Boolean useFlag = true;
}

View File

@@ -0,0 +1,20 @@
package com.bio.bio_backend.domain.base.file.repository;
import com.bio.bio_backend.domain.base.file.entity.File;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;
import java.util.List;
import java.util.Optional;
@Repository
public interface FileRepository extends JpaRepository<File, Long> {
// use_flag가 true인 파일만 조회
Optional<File> findByOidAndUseFlagTrue(Long id);
// use_flag가 true인 파일만 조회 (List 형태로 필요시 사용)
@Query("SELECT f FROM File f WHERE f.useFlag = true")
List<File> findAllActiveFiles();
}

View File

@@ -0,0 +1,15 @@
package com.bio.bio_backend.domain.base.file.service;
import com.bio.bio_backend.domain.base.file.dto.FileUploadRequestDto;
import com.bio.bio_backend.domain.base.file.dto.FileUploadResponseDto;
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.entity.File;
public interface FileService {
FileUploadResponseDto uploadFile(FileUploadRequestDto requestDto);
MultipleFileUploadResponseDto uploadMultipleFiles(MultipleFileUploadRequestDto requestDto);
File getFileByOid(Long oid);
byte[] downloadFile(Long oid);
void deleteFile(Long oid); // 논리적 삭제 (use_flag를 false로 변경)
}

View File

@@ -0,0 +1,207 @@
package com.bio.bio_backend.domain.base.file.service;
import com.bio.bio_backend.domain.base.file.dto.FileUploadRequestDto;
import com.bio.bio_backend.domain.base.file.dto.FileUploadResponseDto;
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.entity.File;
import com.bio.bio_backend.domain.base.file.repository.FileRepository;
import com.bio.bio_backend.global.exception.ApiException;
import com.bio.bio_backend.global.constants.ApiResponseCode;
import com.bio.bio_backend.global.utils.FileUtils;
import com.bio.bio_backend.global.utils.OidUtils;
import com.bio.bio_backend.global.utils.SecurityUtils;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import static com.bio.bio_backend.global.utils.OidUtils.generateOid;
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional(readOnly = true)
public class FileServiceImpl implements FileService {
private final FileRepository fileRepository;
@Value("${app.file.upload.path}")
private String uploadPath;
@Value("${server.servlet.context-path}")
private String contextPath;
@Override
@Transactional
public FileUploadResponseDto uploadFile(FileUploadRequestDto requestDto) {
MultipartFile multipartFile = requestDto.getFile();
try {
// 파일 유효성 검사
FileUtils.validateFile(multipartFile);
} catch (IllegalArgumentException e) {
throw new ApiException(ApiResponseCode.FILE_EMPTY);
}
// 파일 업로드 처리
File savedFile = processFileUpload(multipartFile, requestDto.getDescription(), generateOid());
// 응답 DTO 생성 및 반환
return FileUploadResponseDto.builder()
.oid(savedFile.getOid())
.groupOid(savedFile.getGroupOid())
.originalFileName(savedFile.getOriginalFileName())
.downloadUrl(contextPath + "/files/download/" + savedFile.getOid())
.build();
}
@Override
@Transactional
public MultipleFileUploadResponseDto uploadMultipleFiles(MultipleFileUploadRequestDto requestDto) {
List<MultipartFile> files = requestDto.getFiles();
try {
// 파일 리스트 유효성 검사
FileUtils.validateFileList(files);
} catch (IllegalArgumentException e) {
throw new ApiException(ApiResponseCode.FILE_EMPTY);
}
List<FileUploadResponseDto> uploadedFiles = new ArrayList<>();
List<String> errorMessages = new ArrayList<>();
int successCount = 0;
int failureCount = 0;
Long groupOid = generateOid();
for (MultipartFile multipartFile : files) {
try {
// 개별 파일 유효성 검사
if (multipartFile.isEmpty()) {
String errorMsg = "파일 '" + multipartFile.getOriginalFilename() + "'이 비어있습니다.";
errorMessages.add(errorMsg);
failureCount++;
continue;
}
// 단일 파일 업로드 처리
File savedFile = processFileUpload(multipartFile, requestDto.getDescription(), groupOid);
FileUploadResponseDto uploadedFile = FileUploadResponseDto.builder()
.oid(savedFile.getOid())
.originalFileName(savedFile.getOriginalFileName())
.downloadUrl(contextPath + "/files/download/" + savedFile.getOid())
.build();
uploadedFiles.add(uploadedFile);
successCount++;
log.info("파일 업로드 성공: {}", multipartFile.getOriginalFilename());
} catch (Exception ex) {
String fileName = multipartFile.getOriginalFilename() != null ? multipartFile.getOriginalFilename() : "알 수 없는 파일";
log.error("파일 업로드 실패: {}", fileName, ex);
errorMessages.add("파일 '" + fileName + "' 업로드 실패: " + ex.getMessage());
failureCount++;
}
}
return MultipleFileUploadResponseDto.builder()
.groupOid(groupOid)
.files(uploadedFiles)
.totalCount(files.size())
.successCount(successCount)
.failureCount(failureCount)
.errorMessages(errorMessages)
.build();
}
/**
* 파일 업로드 처리
*/
private File processFileUpload(MultipartFile multipartFile, String description, Long groupOid) {
String originalFileName = FileUtils.cleanFileName(multipartFile.getOriginalFilename());
try {
// 항상 년월일 기반으로 폴더 생성 (예: uploads/2024/01/15/)
Path uploadDir = FileUtils.createYearMonthUploadDirectory(uploadPath);
log.debug("년월 기반 폴더 사용: {}", uploadDir);
// 파일명 및 확장자 처리
String fileExtension = FileUtils.extractFileExtension(originalFileName);
String storedFileName = FileUtils.generateUniqueFileName(fileExtension);
// 파일 저장
Path targetLocation = FileUtils.saveFileToDisk(multipartFile, uploadDir, storedFileName);
// DB에 파일 정보 저장
File file = createFileEntity(originalFileName, storedFileName, targetLocation, multipartFile, description, groupOid);
return fileRepository.save(file);
} catch (IOException ex) {
log.error("파일 업로드 실패: {}", originalFileName, ex);
throw new ApiException(ApiResponseCode.FILE_UPLOAD_FAILED, ex);
}
}
private File createFileEntity(String originalFileName, String storedFileName, Path targetLocation,
MultipartFile multipartFile, String description, Long groupOid) {
return File.builder()
.originalFileName(originalFileName)
.storedFileName(storedFileName)
.filePath(targetLocation.toString())
.fileSize(multipartFile.getSize())
.contentType(multipartFile.getContentType())
.description(description)
.groupOid(groupOid)
.build();
}
@Override
public File getFileByOid(Long oid) {
return fileRepository.findByOidAndUseFlagTrue(oid)
.orElseThrow(() -> new ApiException(ApiResponseCode.FILE_NOT_FOUND));
}
@Override
public byte[] downloadFile(Long oid) {
File file = fileRepository.findByOidAndUseFlagTrue(oid)
.orElseThrow(() -> new ApiException(ApiResponseCode.FILE_NOT_FOUND));
try {
Path filePath = Paths.get(file.getFilePath());
return Files.readAllBytes(filePath);
} catch (IOException ex) {
log.error("파일 다운로드 실패: {}", file.getOriginalFileName(), ex);
throw new ApiException(ApiResponseCode.FILE_DOWNLOAD_FAILED, ex);
}
}
@Override
@Transactional
public void deleteFile(Long oid) {
File file = fileRepository.findByOidAndUseFlagTrue(oid)
.orElseThrow(() -> new ApiException(ApiResponseCode.FILE_NOT_FOUND));
String currentUserId = SecurityUtils.getCurrentUserId();
// 현재 사용자가 파일 소유자인지 확인
if (currentUserId == null || !currentUserId.equals(file.getCreatedId())) {
throw new ApiException(ApiResponseCode.COMMON_FORBIDDEN);
}
// 논리적 삭제: use_flag를 false로 변경
file.setUseFlag(false);
fileRepository.save(file);
log.info("파일 논리적 삭제 완료: oid={}, fileName={}", oid, file.getOriginalFileName());
}
}

View File

@@ -0,0 +1,80 @@
package com.bio.bio_backend.domain.base.member.controller;
import com.bio.bio_backend.global.dto.ApiResponseDto;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import jakarta.validation.Valid;
import com.bio.bio_backend.domain.base.member.dto.MemberDto;
import com.bio.bio_backend.domain.base.member.dto.CreateMemberRequestDto;
import com.bio.bio_backend.domain.base.member.dto.CreateMemberResponseDto;
import com.bio.bio_backend.domain.base.member.service.MemberService;
import com.bio.bio_backend.domain.base.member.mapper.MemberMapper;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import io.swagger.v3.oas.annotations.Operation;
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 com.bio.bio_backend.global.constants.ApiResponseCode;
import com.bio.bio_backend.global.annotation.LogExecution;
import com.bio.bio_backend.global.utils.SecurityUtils;
import com.bio.bio_backend.global.utils.JwtUtils;
import jakarta.servlet.http.HttpServletResponse;
@Tag(name = "Member", description = "회원 관련 API")
@RestController
@RequestMapping("/members")
@RequiredArgsConstructor
@Slf4j
public class MemberController {
private final MemberService memberService;
private final MemberMapper memberMapper;
private final JwtUtils jwtUtils;
@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("/register")
public ResponseEntity<ApiResponseDto<CreateMemberResponseDto>> createMember(@RequestBody @Valid CreateMemberRequestDto requestDto) {
MemberDto createdMember = memberService.createMember(memberMapper.toMemberDto(requestDto));
CreateMemberResponseDto responseDto = memberMapper.toCreateMemberResponseDto(createdMember);
ApiResponseDto<CreateMemberResponseDto> 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 = "401", description = "인증 실패", content = @Content(schema = @Schema(implementation = ApiResponseDto.class)))
})
@PostMapping("/logout")
public ResponseEntity<ApiResponseDto<Void>> logout(HttpServletResponse response) {
try {
String userId = SecurityUtils.getCurrentUserId();
memberService.deleteRefreshToken(userId);
// 모든 토큰 쿠키 삭제
jwtUtils.deleteAllTokenCookies(response);
log.info("사용자 로그아웃 완료: {}", userId);
return ResponseEntity.ok(ApiResponseDto.success(ApiResponseCode.COMMON_SUCCESS));
} catch (Exception e) {
log.error("로그아웃 처리 중 오류 발생: {}", e.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR)
.body(ApiResponseDto.fail(ApiResponseCode.COMMON_INTERNAL_SERVER_ERROR));
}
}
}

View File

@@ -0,0 +1,29 @@
package com.bio.bio_backend.domain.base.member.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Email;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CreateMemberRequestDto {
@NotBlank(message = "아이디는 필수입니다.")
private String userId;
@NotBlank(message = "비밀번호는 필수입니다.")
private String password;
@NotBlank(message = "사용자명은 필수입니다.")
private String name;
@NotBlank(message = "이메일은 필수입니다.")
@Email(message = "올바른 이메일 형식이 아닙니다.")
private String email;
}

View File

@@ -0,0 +1,21 @@
package com.bio.bio_backend.domain.base.member.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CreateMemberResponseDto {
private Long oid;
private String userId;
private Boolean useFlag;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
}

View File

@@ -0,0 +1,21 @@
package com.bio.bio_backend.domain.base.member.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import jakarta.validation.constraints.NotBlank;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginRequestDto {
@NotBlank(message = "아이디는 필수입니다.")
private String userId;
@NotBlank(message = "비밀번호는 필수입니다.")
private String password;
}

View File

@@ -0,0 +1,19 @@
package com.bio.bio_backend.domain.base.member.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.time.LocalDateTime;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class LoginResponseDto {
private String userId;
private String name;
private LocalDateTime lastLoginAt;
}

View File

@@ -0,0 +1,52 @@
package com.bio.bio_backend.domain.base.member.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import java.time.LocalDateTime;
import java.util.Collection;
import java.util.Collections;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MemberDto implements UserDetails {
private Long oid;
private String userId;
private String password;
private String name;
private String email;
private Boolean useFlag;
private String refreshToken;
private String loginIp;
private LocalDateTime lastLoginAt;
private LocalDateTime createdAt;
private LocalDateTime updatedAt;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return Collections.singletonList(new SimpleGrantedAuthority("ROLE_USER"));
}
@Override
public String getUsername() {
return this.userId;
}
@Override
public boolean isAccountNonLocked() {
return this.useFlag != null && this.useFlag;
}
@Override
public boolean isEnabled() {
return this.useFlag != null && this.useFlag;
}
}

View File

@@ -0,0 +1,54 @@
package com.bio.bio_backend.domain.base.member.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;
import java.time.LocalDateTime;
@Entity
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(
name = AppConstants.TABLE_PREFIX + "member",
indexes = {
@Index(name = "idx_member_user_id", columnList = "user_id")
}
)
public class Member extends BaseEntity {
@Column(name = "user_id", nullable = false, length = 100)
private String userId;
@Column(name = "password", nullable = false, length = 100)
private String password;
@Column(name = "name", nullable = false, length = 100)
private String name;
@Column(name = "email", nullable = false, length = 255)
private String email;
@Column(name = "use_flag", nullable = false)
@Builder.Default
private Boolean useFlag = true;
@Column(name = "refresh_token", length = 1024)
private String refreshToken;
@Column(name = "login_ip", length = 45) // IPv6 지원을 위해 45자
private String loginIp;
@Column(name = "last_login_at")
private LocalDateTime lastLoginAt;
}

View File

@@ -0,0 +1,60 @@
package com.bio.bio_backend.domain.base.member.mapper;
import com.bio.bio_backend.domain.base.member.dto.CreateMemberRequestDto;
import com.bio.bio_backend.domain.base.member.dto.CreateMemberResponseDto;
import com.bio.bio_backend.domain.base.member.dto.LoginResponseDto;
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.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 MemberMapper {
/**
* CreateMemberRequestDto를 MemberDto로 변환
* 기본값 설정: useFlag = true
*/
@Mapping(target = "oid", ignore = true)
@Mapping(target = "useFlag", constant = "true")
@Mapping(target = "refreshToken", ignore = true)
@Mapping(target = "loginIp", ignore = true)
@Mapping(target = "lastLoginAt", ignore = true)
@Mapping(target = "createdAt", ignore = true)
@Mapping(target = "updatedAt", ignore = true)
MemberDto toMemberDto(CreateMemberRequestDto requestDto);
/**
* Member 엔티티를 MemberDto로 변환
*/
MemberDto toMemberDto(Member member);
/**
* MemberDto를 Member 엔티티로 변환
*/
Member toMember(MemberDto memberDto);
/**
* Member 엔티티 리스트를 MemberDto 리스트로 변환
*/
List<MemberDto> toMemberDtoList(List<Member> members);
/**
* MemberDto를 CreateMemberResponseDto로 변환
*/
CreateMemberResponseDto toCreateMemberResponseDto(MemberDto memberDto);
/**
* MemberDto의 값으로 기존 Member 엔티티 업데이트 (null이 아닌 필드만)
*/
@IgnoreBaseEntityMapping
void updateMemberFromDto(MemberDto memberDto, @org.mapstruct.MappingTarget Member member);
/**
* MemberDto를 LoginResponseDto로 변환
*/
LoginResponseDto toLoginResponseDto(MemberDto memberDto);
}

View File

@@ -1,16 +1,17 @@
package com.bio.bio_backend.domain.user.member.repository; package com.bio.bio_backend.domain.base.member.repository;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import com.bio.bio_backend.domain.user.member.entity.Member; import com.bio.bio_backend.domain.base.member.entity.Member;
import java.util.List;
@Repository @Repository
public interface MemberRepository extends JpaRepository<Member, Long> { public interface MemberRepository extends JpaRepository<Member, Long>, MemberRepositoryCustom {
// 사용자 ID로 회원 조회
Member findByUserId(String userId);
// 사용자 ID 존재 여부 확인 // 사용자 ID 존재 여부 확인
boolean existsByUserId(String userId); boolean existsByUserId(String userId);
}
// 활성화된 회원 목록 조회
List<Member> findByUseFlagTrue();
}

View File

@@ -0,0 +1,19 @@
package com.bio.bio_backend.domain.base.member.repository;
import com.bio.bio_backend.domain.base.member.entity.Member;
import java.util.Optional;
/**
* QueryDSL을 활용한 커스텀 쿼리 메서드들을 정의하는 인터페이스
* 복잡한 쿼리나 동적 쿼리가 필요한 경우 이 인터페이스를 구현하여 사용합니다.
*/
public interface MemberRepositoryCustom {
/**
* 활성화된 사용자 중에서 사용자 ID로 검색하여 조회합니다.
*
* @param userId 사용자 ID
* @return Optional<Member> 회원 정보 (없으면 empty)
*/
Optional<Member> findActiveMemberByUserId(String userId);
}

View File

@@ -0,0 +1,38 @@
package com.bio.bio_backend.domain.base.member.repository;
import com.bio.bio_backend.domain.base.member.entity.Member;
import com.bio.bio_backend.domain.base.member.entity.QMember;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;
import java.util.Optional;
/**
* QueryDSL을 활용하여 MemberRepositoryCustom 인터페이스를 구현하는 클래스
* 복잡한 쿼리나 동적 쿼리를 QueryDSL로 작성하여 성능과 가독성을 향상시킵니다.
*/
@Repository
@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom {
private final JPAQueryFactory queryFactory;
/**
* QMember 인스턴스를 생성하여 쿼리에서 사용합니다.
* QueryDSL의 Q클래스를 통해 타입 안전한 쿼리 작성이 가능합니다.
*/
private final QMember member = QMember.member;
@Override
public Optional<Member> findActiveMemberByUserId(String userId) {
// 활성화된 사용자 중에서 사용자 ID로 검색
Member foundMember = queryFactory
.selectFrom(member)
.where(member.userId.eq(userId)
.and(member.useFlag.eq(true)))
.fetchOne();
return Optional.ofNullable(foundMember);
}
}

View File

@@ -0,0 +1,24 @@
package com.bio.bio_backend.domain.base.member.service;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import com.bio.bio_backend.domain.base.member.dto.MemberDto;
import java.util.List;
import java.util.Map;
public interface MemberService extends UserDetailsService {
UserDetails loadUserByUsername(String id);
MemberDto createMember(MemberDto memberDTO);
String getRefreshToken(String id);
void deleteRefreshToken(String id);
void updateMember(MemberDto member);
List<MemberDto> selectMemberList(Map<String, String> params);
}

View File

@@ -0,0 +1,98 @@
package com.bio.bio_backend.domain.base.member.service;
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.mapper.MemberMapper;
import com.bio.bio_backend.domain.base.member.repository.MemberRepository;
import com.bio.bio_backend.global.exception.ApiException;
import com.bio.bio_backend.global.constants.ApiResponseCode;
import com.bio.bio_backend.global.constants.AppConstants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
@Slf4j
@Transactional(readOnly = true)
public class MemberServiceImpl implements MemberService {
private final MemberMapper memberMapper; // MapStruct Mapper 사용
private final MemberRepository memberRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {
Member member = memberRepository.findActiveMemberByUserId(id)
.orElseThrow(() -> new UsernameNotFoundException("사용자를 찾을 수 없습니다: " + id));
return memberMapper.toMemberDto(member);
}
@Override
@Transactional
public MemberDto createMember(MemberDto memberDto) {
// userId 중복 체크
if (memberRepository.existsByUserId(memberDto.getUserId())) {
throw new ApiException(ApiResponseCode.USER_ID_DUPLICATE);
}
Member member = Member.builder()
.userId(memberDto.getUserId())
.password(bCryptPasswordEncoder.encode(memberDto.getPassword()))
.name(memberDto.getName())
.email(memberDto.getEmail())
.build();
member.setCreatedOid(AppConstants.ADMIN_OID);
member.setCreatedId(AppConstants.ADMIN_USER_ID);
member.setUpdatedOid(AppConstants.ADMIN_OID);
member.setUpdatedId(AppConstants.ADMIN_USER_ID);
Member savedMember = memberRepository.save(member);
return memberMapper.toMemberDto(savedMember);
}
@Override
@Transactional
public void updateMember(MemberDto memberDto) {
Member member = memberRepository.findActiveMemberByUserId(memberDto.getUserId())
.orElseThrow(() -> new ApiException(ApiResponseCode.USER_NOT_FOUND));
memberMapper.updateMemberFromDto(memberDto, member);
memberRepository.save(member);
}
@Override
public String getRefreshToken(String id) {
Member member = memberRepository.findActiveMemberByUserId(id)
.orElseThrow(() -> new ApiException(ApiResponseCode.USER_NOT_FOUND));
return member.getRefreshToken();
}
@Override
@Transactional
public void deleteRefreshToken(String id) {
Member member = memberRepository.findActiveMemberByUserId(id)
.orElseThrow(() -> new ApiException(ApiResponseCode.USER_NOT_FOUND));
member.setRefreshToken(null);
memberRepository.save(member);
}
@Override
public List<MemberDto> selectMemberList(Map<String, String> params) {
List<Member> members = memberRepository.findByUseFlagTrue();
return memberMapper.toMemberDtoList(members);
}
}

View File

@@ -1,129 +0,0 @@
package com.bio.bio_backend.domain.user.member.controller;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.modelmapper.ModelMapper;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid;
import com.bio.bio_backend.domain.user.member.dto.MemberDTO;
import com.bio.bio_backend.domain.user.member.dto.CreateMemberRequestDTO;
import com.bio.bio_backend.domain.user.member.dto.CreateMemberResponseDto;
import com.bio.bio_backend.domain.user.member.service.MemberService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
@RestController
@RequiredArgsConstructor
@Slf4j
public class MemberController {
private final MemberService memberService;
private final ModelMapper mapper;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
@GetMapping("/join")
public ResponseEntity<String> createMember1() {
return ResponseEntity.status(HttpStatus.CREATED).body("test");
}
@PostMapping("/join")
public ResponseEntity<CreateMemberResponseDto> createMember(@RequestBody @Valid CreateMemberRequestDTO requestDto) {
// RequestMember를 MemberDTO로 변환
MemberDTO member = new MemberDTO();
member.setId(requestDto.getUserId());
member.setPw(requestDto.getPassword());
int oid = memberService.createMember(member);
// 생성된 회원 정보를 조회하여 응답
MemberDTO createdMember = memberService.selectMember(oid);
CreateMemberResponseDto responseDto = mapper.map(createdMember, CreateMemberResponseDto.class);
return ResponseEntity.status(HttpStatus.CREATED).body(responseDto);
}
// @PostMapping("/member/list")
// public ResponseEntity<List<ResponseMember>> getMemberList(@RequestBody(required = false) Map<String, String> params) {
// if(params == null){
// params = new HashMap<>();
// }
// Iterable<MemberDTO> memberList = memberService.selectMemberList(params);
// List<ResponseMember> result = new ArrayList<>();
// memberList.forEach(m -> {
// result.add(new ModelMapper().map(m, ResponseMember.class));
// });
// return ResponseEntity.status(HttpStatus.OK).body(result);
// }
// @GetMapping("/member/{seq}")
// public ResponseEntity<ResponseMember> selectMember(@PathVariable("seq") int seq) {
// MemberDTO member = memberService.selectMember(seq);
// ResponseMember responseMember = mapper.map(member, ResponseMember.class);
// return ResponseEntity.status(HttpStatus.OK).body(responseMember);
// }
// @PutMapping("/member")
// public CustomApiResponse<Void> updateMember(@RequestBody @Valid CreateMemberRequestDTO requestMember, @AuthenticationPrincipal MemberDTO registrant) {
// // 현재 JWT는 사용자 id 값을 통하여 생성, 회원정보 변경 시 JWT 재발급 여부 검토
// MemberDTO member = mapper.map(requestMember, MemberDTO.class);
// if (requestMember.getPassword() != null) {
// member.setPw(bCryptPasswordEncoder.encode(requestMember.getPassword()));
// }
// member.setRegSeq(registrant.getSeq());
// memberService.updateMember(member);
// return CustomApiResponse.success(ApiResponseCode.USER_INFO_CHANGE, null);
// }
// @DeleteMapping("/member")
// public CustomApiResponse<Void> deleteMember(@RequestBody @Valid CreateMemberRequestDTO requestMember){
// MemberDTO member = mapper.map(requestMember, MemberDTO.class);
// memberService.deleteMember(member);
// return CustomApiResponse.success(ApiResponseCode.USER_DELETE_SUCCESSFUL, null);
// }
// @PostMapping("/logout")
// public CustomApiResponse<Void> logout(@AuthenticationPrincipal MemberDTO member) {
// String id = member.getId();
// try {
// memberService.deleteRefreshToken(id);
// } catch (Exception e) {
// return CustomApiResponse.fail(ApiResponseCode.INTERNAL_SERVER_ERROR, null);
// }
// return CustomApiResponse.success(ApiResponseCode.LOGOUT_SUCCESSFUL, null);
// }
}

View File

@@ -1,17 +0,0 @@
package com.bio.bio_backend.domain.user.member.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import jakarta.validation.constraints.NotBlank;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CreateMemberRequestDTO {
@NotBlank(message = "사용자 ID는 필수입니다")
private String userId;
@NotBlank(message = "비밀번호는 필수입니다")
private String password;
}

View File

@@ -1,11 +0,0 @@
package com.bio.bio_backend.domain.user.member.dto;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;
@Data
@JsonInclude(JsonInclude.Include.NON_NULL)
public class CreateMemberResponseDto {
private String id;
private String pw;
}

View File

@@ -1,127 +0,0 @@
package com.bio.bio_backend.domain.user.member.dto;
import java.sql.Timestamp;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.bio.bio_backend.global.constants.MemberConstants;
import lombok.Data;
@Data
/**
* 회원
*/
public class MemberDTO implements UserDetails {
/**
* 시퀀스 (PK)
*/
private int seq;
/**
* ID
*/
private String id;
/**
* Password
*/
private String pw;
/**
* 권한
*/
private String role;
/**
* 회원 상태
*/
private String status;
/**
* 가입 일시
*/
private Timestamp regAt;
/**
* 등록자
*/
private int regSeq;
/**
* 수정 일시
*/
private Timestamp udtAt;
/**
* 수정자
*/
private int udtSeq;
/**
* 최근 로그인 일시
*/
private Timestamp lastLoginAt;
/**
* Refresh Token
*/
private String refreshToken;
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> roles = new HashSet<>();
String auth = "";
if(role.equals("SYSTEM_ADMIN")){
auth = MemberConstants.ROLE_SYSTEM_ADMIN + "," +
MemberConstants.ROLE_ADMIN + "," + MemberConstants.ROLE_MEMBER;
}else if(role.equals("ADMIN")){
auth = MemberConstants.ROLE_ADMIN + "," + MemberConstants.ROLE_MEMBER;
}else {
auth = MemberConstants.ROLE_MEMBER;
}
for (String x : auth.split(",")) {
roles.add(new SimpleGrantedAuthority(x));
}
return roles;
}
@Override
public String getPassword() {
return pw;
}
@Override
public String getUsername() {
return id;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

View File

@@ -1,46 +0,0 @@
package com.bio.bio_backend.domain.user.member.entity;
import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import com.bio.bio_backend.global.entity.BaseEntity;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Entity
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(
name = "member",
uniqueConstraints = {
@UniqueConstraint(name = "uk_member_user_id", columnNames = "user_id")
}
)
public class Member extends BaseEntity {
@Column(name = "user_id", nullable = false, length = 100)
private String userId;
@Column(name = "password", nullable = false, length = 100)
private String password;
@Column(name = "role", nullable = false, length = 40)
private String role;
@Column(name = "status", nullable = false, length = 1)
private String status;
@Column(name = "refresh_token", length = 200)
private String refreshToken;
@Column(name = "last_login_at")
private LocalDateTime lastLoginAt;
}

View File

@@ -1,27 +0,0 @@
package com.bio.bio_backend.domain.user.member.mapper;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.annotations.Mapper;
import com.bio.bio_backend.domain.user.member.dto.MemberDTO;
@Mapper
public interface MemberMapper {
int createMember(MemberDTO memberDTO);
MemberDTO loadUserByUsername(String id);
void updateRefreshToken(MemberDTO memberDTO);
String getRefreshToken(String id);
int deleteRefreshToken(String id);
List<MemberDTO> selectMemberList(Map<String, String> params);
MemberDTO selectMemberBySeq(int seq);
int updateMember(MemberDTO member);
}

View File

@@ -1,68 +0,0 @@
package com.bio.bio_backend.domain.user.member.repository;
import com.bio.bio_backend.domain.user.member.entity.Member;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import java.util.List;
import java.util.Optional;
/**
* QueryDSL을 활용한 커스텀 쿼리 메서드들을 정의하는 인터페이스
* 복잡한 쿼리나 동적 쿼리가 필요한 경우 이 인터페이스를 구현하여 사용합니다.
*/
public interface MemberRepositoryCustom {
/**
* 사용자 ID로 회원을 조회합니다.
* QueryDSL을 사용하여 더 유연한 쿼리 작성이 가능합니다.
*
* @param userId 사용자 ID
* @return Optional<Member> 회원 정보 (없으면 empty)
*/
Optional<Member> findByUserIdCustom(String userId);
/**
* 역할(Role)별로 회원 목록을 조회합니다.
*
* @param role 회원 역할
* @return List<Member> 해당 역할을 가진 회원 목록
*/
List<Member> findByRole(String role);
/**
* 상태(Status)별로 회원 목록을 조회합니다.
*
* @param status 회원 상태
* @return List<Member> 해당 상태를 가진 회원 목록
*/
List<Member> findByStatus(String status);
/**
* 사용자 ID와 상태로 회원을 조회합니다.
*
* @param userId 사용자 ID
* @param status 회원 상태
* @return Optional<Member> 회원 정보
*/
Optional<Member> findByUserIdAndStatus(String userId, String status);
/**
* 검색 조건에 따른 회원 목록을 페이징하여 조회합니다.
*
* @param userId 사용자 ID (부분 검색)
* @param role 회원 역할
* @param status 회원 상태
* @param pageable 페이징 정보
* @return Page<Member> 페이징된 회원 목록
*/
Page<Member> findMembersByCondition(String userId, String role, String status, Pageable pageable);
/**
* 마지막 로그인 시간이 특정 시간 이후인 회원들을 조회합니다.
*
* @param lastLoginAfter 마지막 로그인 기준 시간
* @return List<Member> 해당 조건을 만족하는 회원 목록
*/
List<Member> findActiveMembersByLastLogin(java.time.LocalDateTime lastLoginAfter);
}

View File

@@ -1,130 +0,0 @@
package com.bio.bio_backend.domain.user.member.repository;
import com.bio.bio_backend.domain.user.member.entity.Member;
import com.bio.bio_backend.domain.user.member.entity.QMember;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
/**
* QueryDSL을 활용하여 MemberRepositoryCustom 인터페이스를 구현하는 클래스
* 복잡한 쿼리나 동적 쿼리를 QueryDSL로 작성하여 성능과 가독성을 향상시킵니다.
*/
@Repository
@RequiredArgsConstructor
public class MemberRepositoryImpl implements MemberRepositoryCustom {
private final JPAQueryFactory queryFactory;
/**
* QMember 인스턴스를 생성하여 쿼리에서 사용합니다.
* QueryDSL의 Q클래스를 통해 타입 안전한 쿼리 작성이 가능합니다.
*/
private final QMember member = QMember.member;
@Override
public Optional<Member> findByUserIdCustom(String userId) {
// QueryDSL을 사용하여 사용자 ID로 회원을 조회합니다.
// eq() 메서드를 사용하여 정확한 일치 조건을 설정합니다.
Member foundMember = queryFactory
.selectFrom(member)
.where(member.userId.eq(userId))
.fetchOne();
return Optional.ofNullable(foundMember);
}
@Override
public List<Member> findByRole(String role) {
// 역할별로 회원을 조회합니다.
// eq() 메서드를 사용하여 정확한 일치 조건을 설정합니다.
return queryFactory
.selectFrom(member)
.where(member.role.eq(role))
.fetch();
}
@Override
public List<Member> findByStatus(String status) {
// 상태별로 회원을 조회합니다.
return queryFactory
.selectFrom(member)
.where(member.status.eq(status))
.fetch();
}
@Override
public Optional<Member> findByUserIdAndStatus(String userId, String status) {
// 사용자 ID와 상태를 모두 만족하는 회원을 조회합니다.
// and() 메서드를 사용하여 여러 조건을 결합합니다.
Member foundMember = queryFactory
.selectFrom(member)
.where(member.userId.eq(userId)
.and(member.status.eq(status)))
.fetchOne();
return Optional.ofNullable(foundMember);
}
@Override
public Page<Member> findMembersByCondition(String userId, String role, String status, Pageable pageable) {
// BooleanBuilder를 사용하여 동적 쿼리를 구성합니다.
// null이 아닌 조건만 쿼리에 포함시킵니다.
BooleanBuilder builder = new BooleanBuilder();
// 사용자 ID가 제공된 경우 부분 검색 조건을 추가합니다.
if (userId != null && !userId.trim().isEmpty()) {
builder.and(member.userId.containsIgnoreCase(userId));
}
// 역할이 제공된 경우 정확한 일치 조건을 추가합니다.
if (role != null && !role.trim().isEmpty()) {
builder.and(member.role.eq(role));
}
// 상태가 제공된 경우 정확한 일치 조건을 추가합니다.
if (status != null && !status.trim().isEmpty()) {
builder.and(member.status.eq(status));
}
// 전체 개수를 조회합니다.
long total = queryFactory
.selectFrom(member)
.where(builder)
.fetchCount();
// 페이징 조건을 적용하여 결과를 조회합니다.
List<Member> content = queryFactory
.selectFrom(member)
.where(builder)
.orderBy(member.createdAt.desc()) // 생성일 기준 내림차순 정렬
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
// Page 객체를 생성하여 반환합니다.
return new PageImpl<>(content, pageable, total);
}
@Override
public List<Member> findActiveMembersByLastLogin(LocalDateTime lastLoginAfter) {
// 마지막 로그인 시간이 특정 시간 이후인 활성 회원들을 조회합니다.
// 여러 조건을 조합하여 복잡한 쿼리를 작성합니다.
return queryFactory
.selectFrom(member)
.where(member.status.eq("A") // 활성 상태
.and(member.lastLoginAt.isNotNull()) // 마지막 로그인 시간이 존재
.and(member.lastLoginAt.after(lastLoginAfter))) // 특정 시간 이후
.orderBy(member.lastLoginAt.desc()) // 마지막 로그인 시간 기준 내림차순 정렬
.fetch();
}
}

View File

@@ -1,30 +0,0 @@
package com.bio.bio_backend.domain.user.member.service;
import java.util.List;
import java.util.Map;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import com.bio.bio_backend.domain.user.member.dto.MemberDTO;
public interface MemberService extends UserDetailsService {
UserDetails loadUserByUsername(String id);
int createMember(MemberDTO memberDTO);
void updateRefreshToken(MemberDTO memberDTO);
String getRefreshToken(String id);
int deleteRefreshToken(String id);
List<MemberDTO> selectMemberList(Map<String, String> params);
MemberDTO selectMember(int seq);
int updateMember(MemberDTO member);
int deleteMember(MemberDTO member);
}

View File

@@ -1,95 +0,0 @@
package com.bio.bio_backend.domain.user.member.service;
import com.bio.bio_backend.domain.user.member.dto.MemberDTO;
import com.bio.bio_backend.domain.user.member.entity.Member;
import com.bio.bio_backend.domain.user.member.mapper.MemberMapper;
import com.bio.bio_backend.domain.user.member.repository.MemberRepository;
import com.bio.bio_backend.global.constants.MemberConstants;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
@Service
@RequiredArgsConstructor
@Slf4j
public class MemberServiceImpl implements MemberService {
private final MemberMapper memberMapper;
private final MemberRepository memberRepository;
private final BCryptPasswordEncoder bCryptPasswordEncoder;
@Override
public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException {
MemberDTO member = memberMapper.loadUserByUsername(id);
if (member == null) {
throw new UsernameNotFoundException("User not found with id : " + id);
}
return member;
}
@Override
public int createMember(MemberDTO memberDTO) {
// JPA Entity를 사용하여 회원 생성
Member member = Member.builder()
.userId(memberDTO.getId())
.password(bCryptPasswordEncoder.encode(memberDTO.getPw()))
.role(MemberConstants.ROLE_MEMBER)
.status(MemberConstants.MEMBER_ACTIVE)
.build();
// JPA 레파지토리를 통해 저장
Member savedMember = memberRepository.save(member);
// 저장된 회원의 oid를 반환
return savedMember.getOid().intValue();
}
@Override
public void updateRefreshToken(MemberDTO memberDTO) {
memberMapper.updateRefreshToken(memberDTO);
}
@Override
public String getRefreshToken(String id) {
return memberMapper.getRefreshToken(id);
}
@Override
public int deleteRefreshToken(String id) {
return memberMapper.deleteRefreshToken(id);
}
@Override
public List<MemberDTO> selectMemberList(Map<String, String> params) {
return memberMapper.selectMemberList(params);
}
@Override
public MemberDTO selectMember(int seq) {
return memberMapper.selectMemberBySeq(seq);
}
@Override
public int updateMember(MemberDTO member) {
return memberMapper.updateMember(member);
}
@Override
public int deleteMember(MemberDTO member) {
member.setStatus(MemberConstants.MEMBER_INACTIVE);
log.info(member.toString());
return memberMapper.updateMember(member);
}
}

View File

@@ -0,0 +1,24 @@
package com.bio.bio_backend.global.annotation;
import org.mapstruct.Mapping;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* BaseEntity의 감사 필드들을 ignore 처리하는 MapStruct 커스텀 어노테이션
* 여러 매퍼에서 공통으로 사용할 수 있습니다.
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)
@Mapping(target = "oid", ignore = true)
@Mapping(target = "createdAt", ignore = true)
@Mapping(target = "updatedAt", ignore = true)
@Mapping(target = "createdOid", ignore = true)
@Mapping(target = "updatedOid", ignore = true)
@Mapping(target = "createdId", ignore = true)
@Mapping(target = "updatedId", ignore = true)
public @interface IgnoreBaseEntityMapping {
}

View File

@@ -0,0 +1,15 @@
package com.bio.bio_backend.global.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 메서드 실행 로깅 어노테이션
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface LogExecution {
String value() default "";
}

View File

@@ -0,0 +1,55 @@
package com.bio.bio_backend.global.aop;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
@Aspect
@Component
@Slf4j
public class MethodExecutionLoggingAspect {
@Around("@annotation(logExecution)")
public Object logExecution(ProceedingJoinPoint pjp, com.bio.bio_backend.global.annotation.LogExecution logExecution) throws Throwable {
String message = logExecution.value().isEmpty() ?
pjp.getSignature().getName() : logExecution.value();
String className = pjp.getTarget().getClass().getSimpleName();
String methodName = pjp.getSignature().getName();
String userInfo = getCurrentUser();
long startTime = System.currentTimeMillis();
log.info("[START] {} | 호출경로: {}.{} | 사용자: {}",
message, className, methodName, userInfo);
try {
Object result = pjp.proceed();
long duration = System.currentTimeMillis() - startTime;
log.info("[SUCCESS] {} | 호출경로: {}.{} | 사용자: {} | 시간: {}ms",
message, className, methodName, userInfo, duration);
return result;
} catch (Exception e) {
long duration = System.currentTimeMillis() - startTime;
log.error("[FAILED] {} | 호출경로: {}.{} | 사용자: {} | 시간: {}ms | 오류: {}",
message, className, methodName, userInfo, duration, e.getMessage());
throw e;
}
}
private String getCurrentUser() {
try {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
if (auth != null && auth.isAuthenticated() && !"anonymousUser".equals(auth.getName())) {
return auth.getName();
}
return "인증되지 않은 사용자";
} catch (Exception e) {
return "알 수 없는 사용자";
}
}
}

View File

@@ -6,21 +6,13 @@ import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
/** @Aspect
* Repository 계층의 메서드 호출을 로깅하는 AOP(Aspect-Oriented Programming) 클래스 @Component
* 모든 Repository 인터페이스의 메서드 호출 시점과 실행 시간을 로그로 기록합니다. @Slf4j
*/
@Aspect // AOP 기능을 활성화하는 어노테이션
@Component // Spring Bean으로 등록하는 어노테이션
@Slf4j // Lombok의 로깅 기능을 제공하는 어노테이션
public class RepositoryLoggingAspect { public class RepositoryLoggingAspect {
/** /**
* Repository 계층의 모든 메서드 호출을 가로채서 로깅하는 Around 어드바이스 * Repository 계층의 모든 메서드 호출을 가로채서 로깅하는 Around 어드바이스
*
* @param pjp ProceedingJoinPoint - 실행될 메서드의 정보를 담고 있는 객체
* @return Object - 원본 메서드의 실행 결과
* @throws Throwable - 원본 메서드에서 발생할 수 있는 예외
*/ */
@Around("execution(* org.springframework.data.repository.Repository+.*(..))") @Around("execution(* org.springframework.data.repository.Repository+.*(..))")
public Object logQueryCall(ProceedingJoinPoint pjp) throws Throwable { public Object logQueryCall(ProceedingJoinPoint pjp) throws Throwable {
@@ -47,10 +39,7 @@ public class RepositoryLoggingAspect {
// 원본 메서드의 결과를 반환 // 원본 메서드의 결과를 반환
return result; return result;
} catch (Throwable ex) { } catch (Throwable ex) {
// 메서드 실행 중 예외 발생 시 로그로 기록
log.warn("[QUERY FAIL] {}.{}() -> {}", type, method, ex.toString()); log.warn("[QUERY FAIL] {}.{}() -> {}", type, method, ex.toString());
// 예외를 다시 던져서 원래의 예외 처리 흐름을 유지
throw ex; throw ex;
} }
} }

View File

@@ -1,15 +1,15 @@
package com.bio.bio_backend.global.config; package com.bio.bio_backend.global.config;
import org.modelmapper.ModelMapper; import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.context.annotation.Bean; import java.time.LocalDateTime;
import org.springframework.web.cors.CorsConfiguration; import java.time.format.DateTimeFormatter;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
@Configuration @Configuration
public class AppConfig { public class AppConfig {
@@ -18,9 +18,21 @@ public class AppConfig {
public BCryptPasswordEncoder bCryptPasswordEncoder() { public BCryptPasswordEncoder bCryptPasswordEncoder() {
return new BCryptPasswordEncoder(); return new BCryptPasswordEncoder();
} }
@Bean @Bean
public ModelMapper modelMapper() { public ObjectMapper objectMapper() {
return new ModelMapper(); ObjectMapper mapper = new ObjectMapper();
// JavaTimeModule 등록
JavaTimeModule javaTimeModule = new JavaTimeModule();
// LocalDateTime 직렬화/역직렬화 설정
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
javaTimeModule.addSerializer(LocalDateTime.class, new LocalDateTimeSerializer(formatter));
javaTimeModule.addDeserializer(LocalDateTime.class, new LocalDateTimeDeserializer(formatter));
mapper.registerModule(javaTimeModule);
return mapper;
} }
} }

View File

@@ -0,0 +1,24 @@
package com.bio.bio_backend.global.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
config.setAllowCredentials(true);
source.registerCorsConfiguration("/**", config);
return new CorsFilter(source);
}
}

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 {
}

Some files were not shown because too many files have changed in this diff Show More