diff --git a/README.md b/README.md index 6fd89ce..dd1bdd3 100644 --- a/README.md +++ b/README.md @@ -1,153 +1,400 @@ -# alist API +# alist API -Spring Boot 기반의 RESTful API 서버 +Spring Boot 3 기반의 `alist` 백엔드 API 서버입니다. -## 프로젝트 정보 +## 프로젝트 개요 -- **그룹**: com.alist -- **포트**: 8106 -- **빌드 결과물**: `api.jar` -- **GitLab**: https://gitlab.pjt.kr/alist/api +- 그룹: `com.alist` +- Java: `21` +- Spring Boot: `3.5.10` +- 포트: `8106` +- 빌드 결과물: `build/libs/api.jar` ## 기술 스택 -- **Java**: 21 -- **Framework**: Spring Boot 3.5.10 -- **빌드 도구**: Gradle -- **데이터베이스**: MariaDB -- **ORM**: MyBatis -- **인증**: JWT (jjwt 0.11.5) + Spring Security -- **API 문서**: Swagger (springdoc-openapi 2.6.0) -- **기타**: Lombok, Validation, Actuator, log4jdbc +- Spring Web +- Spring Security +- JWT (`jjwt 0.11.5`) +- Spring Session + Redis +- MyBatis (`mapper/**/*.xml`) +- MariaDB +- Swagger / OpenAPI (`springdoc-openapi 2.8.0`) +- Validation +- Actuator +- Lombok +- log4jdbc ## 주요 기능 -- JWT 기반 인증/인가 -- MyBatis XML Mapper를 통한 데이터 액세스 -- 표준화된 API 응답 (`ApiResponse`) -- Swagger API 문서 (HTTP Basic 인증) -- 파일 업로드/다운로드 -- Actuator 헬스 체크 +- JWT 발급 및 검증 +- Redis 기반 세션 관리와 쿠키 설정 +- Swagger UI Basic 인증 보호 +- DB 기반 허용 Origin 캐시를 사용하는 동적 CORS +- TUS 업로드 초기화, 권한 검증, 상태 조회, 완료 처리, 취소 처리 +- 파일 조회/다운로드 API +- 공통 응답 래퍼 `ApiResponse` 및 `ApiResponseCode` 사용 -## 빌드 및 실행 +## 디렉터리 구조 + +```text +src/main/java/com/alist/api +├── common +│ ├── modules/file # 공통 파일 업로드/다운로드 모듈 +│ ├── response # ApiResponse, ApiResponseCode +│ └── utils # 공통 유틸리티 +├── config +│ ├── cache # CORS 허용 Origin 캐시 +│ ├── exception # 전역 예외 처리 +│ ├── filter # DynamicCorsFilter +│ ├── jwt # JWT 인증 관련 구성 +│ └── properties # 설정 프로퍼티 +└── modules + ├── auth # 인증/세션 관련 API + ├── main # 루트 리다이렉트 + └── testUser # 테스트 사용자 API +``` + +리소스 파일은 아래 위치를 사용합니다. + +- 설정: `src/main/resources/application*.yaml` +- Mapper XML: `src/main/resources/mapper/**/*.xml` +- 로그 설정: `src/main/resources/logback-*.xml` + +## 실행 방법 ### 빌드 + ```bash ./gradlew bootJar ``` ### 로컬 실행 + ```bash ./gradlew bootRun --args='--spring.profiles.active=local' ``` ### JAR 실행 + ```bash java -jar build/libs/api.jar --spring.profiles.active=local ``` -## 환경 설정 - -### 프로파일 +## 프로파일 | 프로파일 | 설명 | 설정 파일 | -|---------|------|----------| -| `local` | 로컬 개발 환경 | `application-local.yml` | -| `pjt` | 프로젝트(개발) 환경 | `application-pjt.yml` | +| --- | --- | --- | +| `local` | 로컬 개발 환경 | `src/main/resources/application-local.yaml` | +| `pjt` | 프로젝트 개발 서버 환경 | `src/main/resources/application-pjt.yaml` | -### 환경별 접속 정보 +공통 설정은 `src/main/resources/application.yaml`에 있습니다. -#### 로컬 (local) -- **API**: http://localhost:8106 -- **Swagger**: http://localhost:8106/swagger-ui.html +## 필수 설정 항목 -#### 개발 서버 (pjt) -- **API**: https://api-alist.pjt.kr -- **파일**: https://file-alist.pjt.kr -- **Swagger**: https://api-alist.pjt.kr/swagger-ui/index.html +실행 전 아래 설정들이 환경에 맞게 준비되어 있어야 합니다. -## API 문서 +### 데이터 저장소 -Swagger UI를 통해 API 문서를 확인할 수 있습니다. +- `spring.datasource.*` +- `spring.data.redis.*` +- `spring.session.*` -- **접속**: `/swagger-ui.html` -- **인증**: HTTP Basic (환경별 yaml에 설정된 ID/PW 사용) +### 인증/보안 -## 패키지 구조 +- `jwt.secret` +- `jwt.access-token-validity-seconds` +- `jwt.refresh-token-validity-seconds` +- `cookie.secure` +- `cookie.domain` +- `cookie.name` +- `cookie.same-site` +- `swagger.login.id` +- `swagger.login.password` -``` -com.alist.api -├── common -│ ├── response/ # ApiResponse, ApiResponseCode -│ └── utils/ # 공통 유틸리티 -├── config -│ ├── jwt/ # JWT 필터, 핸들러, Provider -│ ├── exception/ # GlobalExceptionHandler -│ ├── properties/ # JwtProperties 등 -│ ├── SecurityConfig.java -│ └── OpenApiConfig.java -└── modules - └── {도메인}/ # Controller, Service, Mapper, DTO -``` +### 파일 업로드 -## 보안 +- `file.upload.tus-endpoint` +- `file.upload.public-base-url` +- `file.upload.tmp-root` +- `file.upload.final-root` +- `file.upload.interrupt-seconds` +- `file.upload.auth-cache.ttl-seconds` -### 인증 방식 -- **Swagger**: HTTP Basic 인증 (InMemory) -- **API**: JWT Bearer 토큰 인증 (Stateless) +`pjt` 프로파일은 DB/Redis/JWT/Swagger 값을 환경변수로 주입받도록 작성되어 있습니다. + +## 보안 구조 + +### Swagger + +- 보호 경로: `/v3/api-docs/**`, `/swagger-ui/**`, `/swagger-ui.html` +- 인증 방식: HTTP Basic +- 계정 정보: `swagger.login.id`, `swagger.login.password` + +### API + +- 기본 인증 방식: JWT Bearer +- 세션 저장소: Redis +- 세션 쿠키: `RedisSessionConfig`에서 도메인, Secure, SameSite 제어 ### 공개 경로 -- `/` - 루트 -- `/actuator/health` - 헬스 체크 -- `/auth/**` - 인증 관련 API -- `/api/user/signup` - 회원가입 -## 응답 코드 +- `/` +- `/actuator/health` +- `/auth/**` +- `/test/**` +- `/files/tusHook` -API 응답은 `ApiResponseCode` enum으로 표준화되어 있습니다. +루트 `/` 요청은 `/swagger-ui/index.html`로 리다이렉트됩니다. -### 주요 응답 코드 +## CORS -| 코드 | 메시지 | HTTP Status | 용도 | -|------|--------|-------------|------| -| `CODE_200` | 성공 | 200 OK | 일반 성공 | -| `CODE_2001` | {0} 정보 조회에 성공하였습니다. | 200 OK | 단건 조회 성공 | -| `CODE_2002` | {0} 등록 되었습니다. | 201 Created | 등록 성공 | -| `CODE_2003` | 조회된 정보가 없습니다. | 200 OK | 조회 결과 없음 | -| `CODE_2004` | 중복된 {0} 정보 입니다. | 409 Conflict | 중복 데이터 | -| `CODE_4001` | 입력값을 확인해주세요. | 400 Bad Request | Validation 오류 | -| `CODE_4003` | 필수 요청 파라미터가 누락되었습니다. | 400 Bad Request | 필수 파라미터 누락 | +`DynamicCorsFilter`가 최우선 필터로 동작하며, 허용 Origin 목록은 `CorsAllowedOriginsCache`에서 조회합니다. -## 배포 +- 허용된 Origin에만 `Access-Control-Allow-Origin` 설정 +- Credential 허용 +- `OPTIONS` preflight 요청은 `200 OK`로 즉시 응답 +- DB를 직접 매 요청마다 조회하지 않고 캐시된 목록을 사용 -### Docker -```bash -# 이미지 빌드 -docker build -t registry.pjt.kr/alist/api . +## 파일 업로드/다운로드 -# 컨테이너 실행 -docker run -d -p 8106:8106 --name alist-api registry.pjt.kr/alist/api +이 프로젝트는 대용량 업로드를 위해 TUS 서버(`tusd`)와 연동합니다. API는 업로드 메타데이터 관리, 업로드 권한 검증, 상태 저장, 완료/취소 처리를 담당합니다. + +### 주요 업로드 엔드포인트 + +- `POST /files/uploadInit` +- `GET /files/uploadAuth` +- `POST /files/uploadStatus` +- `POST /files/tusHook` +- `POST /files/uploadCancel` + +### 파일 조회 엔드포인트 + +- `GET /files/list/{fileMasterIdx}` +- `GET /files/view/{fileUuid}` +- `GET /files/download/{fileUuid}` + +### 운영 메모 + +- TUS 업로드 엔드포인트: `https://file-alist.pjt.kr/tus/files/` +- 업로드 임시 경로: `/srv/project/alist/uploads/tmp` +- 업로드 최종 경로: `/srv/project/alist/uploads` +- 정적 파일 도메인: `https://file-alist.pjt.kr` + +## 주요 인증 엔드포인트 + +- `POST /auth/token` +- `POST /auth/refresh` +- `GET /auth/loginChecked` +- `POST /auth/logout` +- `POST /test/testSignup` + +## API 예시 요청/응답 + +아래 예시는 실제 컨트롤러의 요청 필드와 `ApiResponse` 응답 구조를 기준으로 정리했습니다. + +### 1. 토큰 발급 + +요청: + +```http +POST /auth/token +Content-Type: application/json + +{ + "id": "test" +} ``` -### CI/CD -- **Jenkins**: `Jenkinsfile.pjt` -- **Docker Registry**: registry.pjt.kr -- **배포 스크립트**: `deploy/` 디렉토리 +응답 예시: -## 개발 가이드 +```json +{ + "data": { + "id": null, + "accessToken": "eyJhbGciOiJI..." + }, + "code": "CODE_2001", + "message": "임시 토큰 정보 조회에 성공하였습니다." +} +``` -상세한 개발 가이드는 [CLAUDE.md](./CLAUDE.md)를 참고하세요. +### 2. 테스트 회원가입 -### 코드 작성 규칙 -- 응답은 `ApiResponse` 래퍼 사용 -- 응답 코드는 `ApiResponseCode` enum 사용 -- MyBatis Mapper XML은 `src/main/resources/mapper/` 하위에 작성 -- 새 모듈 추가 시: `modules/{moduleName}/` 구조로 생성 +요청: -### MyBatis 규칙 -- Mapper XML 위치: `src/main/resources/mapper/**/*.xml` -- 카멜케이스 자동 변환 활성화 (`map-underscore-to-camel-case: true`) -- Mapper 인터페이스와 XML의 namespace, id 일치 필수 +```http +POST /test/testSignup +Content-Type: application/json + +{ + "id": "testuser01", + "password": "pass1234" +} +``` + +응답 예시: + +```json +{ + "data": { + "userIdx": 101, + "id": "testuser01" + }, + "code": "CODE_2002", + "message": "아이디 등록 되었습니다." +} +``` + +중복일 경우 예시: + +```json +{ + "data": { + "userIdx": null, + "id": null + }, + "code": "CODE_2004", + "message": "중복된 아이디 정보 입니다." +} +``` + +### 3. 파일 업로드 초기화 + +요청: + +```http +POST /files/uploadInit +Content-Type: application/json +Cookie: ALIST_SESSION=... + +{ + "fileCategory": "notice", + "folderPath": "/2026/03", + "itemList": [ + { + "originName": "guide.pdf", + "sizeBytes": 102400, + "contentType": "application/pdf" + } + ] +} +``` + +응답 예시: + +```json +{ + "data": { + "fileMasterIdx": 55, + "tusEndpoint": "https://file-alist.pjt.kr/tus/files/", + "itemList": [ + { + "fileSeq": 1, + "fileUuid": "2f5f3ef1-8a4e-4b2d-84da-1c1111111111", + "uploadToken": "upload-token-sample", + "originName": "guide.pdf", + "sizeBytes": 102400, + "contentType": "application/pdf" + } + ] + }, + "code": "CODE_200", + "message": "성공" +} +``` + +### 4. 업로드 상태 조회 + +요청: + +```http +POST /files/uploadStatus +Content-Type: application/json + +{ + "fileUuid": "2f5f3ef1-8a4e-4b2d-84da-1c1111111111" +} +``` + +응답 예시: + +```json +{ + "data": { + "fileUuid": "2f5f3ef1-8a4e-4b2d-84da-1c1111111111", + "status": "UPLOADING", + "uploadedBytes": 51200, + "totalBytes": 102400, + "percent": 50, + "updatedAt": "2026-03-11T09:30:00" + }, + "code": "CODE_200", + "message": "성공" +} +``` + +조회 결과가 없을 경우 예시: + +```json +{ + "data": { + "fileUuid": null, + "status": null, + "uploadedBytes": null, + "totalBytes": null, + "percent": null, + "updatedAt": null + }, + "code": "CODE_2003", + "message": "조회된 정보가 없습니다." +} +``` + +## Actuator + +외부 노출 대상은 아래와 같습니다. + +- `health` +- `info` +- `metrics` + +헬스체크 기본 경로: + +- `GET /actuator/health` + +## 응답 규칙 + +모든 API 응답은 `ApiResponse` 래퍼를 사용하며, 상태/메시지는 `ApiResponseCode` enum으로 관리합니다. + +자주 사용하는 코드 예시는 아래와 같습니다. + +| 코드 | HTTP Status | 의미 | +| --- | --- | --- | +| `CODE_200` | `200 OK` | 일반 성공 | +| `CODE_204` | `204 No Contnet` | 일반 성공 | +| `CODE_2001` | `200 OK` | 단건 조회 성공 | +| `CODE_2002` | `201 Created` | 등록 성공 | +| `CODE_2003` | `200 OK` | 조회 결과 없음 | +| `CODE_2004` | `409 Conflict` | 중복 데이터 | +| `CODE_4001` | `400 Bad Request` | 입력값/바인딩 오류 | +| `CODE_4003` | `400 Bad Request` | 필수 파라미터 누락 | +| `CODE_401` | `401 Unauthorized` | 인증 필요 | +| `CODE_403` | `403 Forbidden` | 권한 없음 | +| `CODE_500` | `500 Internal Server Error` | 서버 오류 | + +## 배포 관련 + +- Dockerfile: `Dockerfile` +- Jenkins 파이프라인: `Jenkinsfile.pjt` +- 배포 스크립트: `deploy/` +- 개발 서버 API 도메인: `https://api-alist.pjt.kr` +- 파일 도메인: `https://file-alist.pjt.kr` +- Swagger: `https://api-alist.pjt.kr/swagger-ui/index.html` + +## 참고 + +- Swagger 로컬 접속: [http://localhost:8106/swagger-ui.html](http://localhost:8106/swagger-ui.html) +- 운영 환경에서는 `application-pjt.yaml`의 환경변수 주입 방식 사용을 권장합니다. ## 라이선스