From 47135829f78a79829b7df385b3d5630acf8cfd6f Mon Sep 17 00:00:00 2001 From: "pwy3282040@msecure.co" Date: Fri, 26 Dec 2025 10:59:38 +0900 Subject: [PATCH] =?UTF-8?q?Docs:=20README=20=EB=B0=8F=20minio=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README.md 파일 작성. minio 사용을 위한 의존성 추가, 환경변수 수정. 기타 주석 정리. --- README.md | 40 +++++++++++++++++ build.gradle.kts | 36 ++++++++++++--- docker-compose.yml | 33 ++++++++++++-- .../domain/auth/RefreshTokenRepository.kt | 1 - src/main/resources/application-prod.yml | 44 ++++++++++++++++--- 5 files changed, 136 insertions(+), 18 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..82b05f1 --- /dev/null +++ b/README.md @@ -0,0 +1,40 @@ +# 🛠️ Spring Boot Blog API Server + +## 🚀 Project Overview +기존 **Jekyll(GitHub Pages)** 로 운영하던 정적 블로그의 한계를 극복하고, 확장 가능한 블로그 시스템을 구축하기 위해 개발한 **Spring Boot 기반의 REST API 서버**입니다. + +정적 사이트에서는 구현하기 어려웠던 **동적 포스팅 관리, 관리자 대시보드(Admin), 댓글 시스템** 등의 기능을 직접 구현하여 블로그 운영의 효율성을 높였습니다. **RESTful 원칙**에 입각한 API 설계와 **Spring Security**를 활용한 보안/인증 로직을 적용하여 백엔드 아키텍처 역량을 강화하는 데 중점을 두었습니다. + +### 🎯 Key Objectives +* **Migration**: 정적 파일(Jekyll) 의존성을 제거하고 DB 기반의 동적 시스템으로 전환 +* **Feature Expansion**: 웹 에디터, 게시글 관리, 방문자 통계 등 관리자(Admin) 기능 추가 +* **Architecture**: 프론트엔드와 백엔드를 분리한 REST API 서버 구축 + +### 🛠️ Tech Stack +* **Java 21, Spring Boot 3.5.9** +* **Spring Security, JWT** (Auth) +* **JPA (Hibernate), QueryDSL** (ORM) +* **PostgreSQL, Redis** (Cache/Session) +* **Markdown Parser** (Content) + +--- + +## 🔌 API Reference + +**Base URL**: `http://localhost:8080` +**Auth**: `Authorization: Bearer {Access_Token}` + +### 1. 공통 응답 규격 (Common Response) +모든 API 응답은 아래 표준 포맷을 따릅니다. + +```json +{ + "code": "SUCCESS", // 결과 코드 (SUCCESS 또는 ERROR_CODE) + "message": "요청 성공", // 응답 메시지 + "data": { ... } // 실제 데이터 (없을 경우 null) +} +``` + +## 관련 문서 +- [**ENDPOINT**](https://affine.wypark.me/workspace/f85df0c4-a315-4166-94a8-6558cdafff1d/p13joh_beKNJ6R-I9KrFW) +- [**API 명세서**](https://affine.wypark.me/workspace/f85df0c4-a315-4166-94a8-6558cdafff1d/7fRNXK9utxG4mcekWivGK) \ No newline at end of file diff --git a/build.gradle.kts b/build.gradle.kts index b975943..e0b3b82 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,9 +1,10 @@ plugins { kotlin("jvm") version "1.9.25" kotlin("plugin.spring") version "1.9.25" + kotlin("plugin.jpa") version "1.9.25" + kotlin("kapt") version "1.9.25" id("org.springframework.boot") version "3.5.9" id("io.spring.dependency-management") version "1.1.7" - kotlin("plugin.jpa") version "1.9.25" } group = "me.wypark" @@ -12,7 +13,7 @@ description = "blog-backend" java { toolchain { - languageVersion = JavaLanguageVersion.of(21) + languageVersion = JavaLanguageVersion.of(21) // Java 21 굿! } } @@ -20,22 +21,43 @@ repositories { mavenCentral() } +val springCloudAwsVersion = "3.2.1" + dependencies { + // 1. Standard Spring Boot Starters implementation("org.springframework.boot:spring-boot-starter-data-jpa") implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-data-redis") + implementation("org.springframework.boot:spring-boot-starter-validation") + + // 2. Kotlin Modules implementation("com.fasterxml.jackson.module:jackson-module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") - developmentOnly("org.springframework.boot:spring-boot-devtools") + + // 3. Database Drivers runtimeOnly("org.postgresql:postgresql") + + // 4. AWS S3 / MinIO + implementation("io.awspring.cloud:spring-cloud-aws-starter-s3:$springCloudAwsVersion") + + // 5. QueryDSL + implementation("com.querydsl:querydsl-jpa:5.1.0:jakarta") + kapt("com.querydsl:querydsl-apt:5.1.0:jakarta") + kapt("jakarta.persistence:jakarta.persistence-api") + kapt("jakarta.annotation:jakarta.annotation-api") + + // 6. JWT (0.12.x) + implementation("io.jsonwebtoken:jjwt-api:0.12.3") + runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.3") + runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.3") + + // 7. Test & Dev + developmentOnly("org.springframework.boot:spring-boot-devtools") testImplementation("org.springframework.boot:spring-boot-starter-test") testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") testImplementation("org.springframework.security:spring-security-test") testRuntimeOnly("org.junit.platform:junit-platform-launcher") - implementation("io.jsonwebtoken:jjwt-api:0.12.3") - runtimeOnly("io.jsonwebtoken:jjwt-impl:0.12.3") - runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.12.3") } kotlin { @@ -52,4 +74,4 @@ allOpen { tasks.withType { useJUnitPlatform() -} +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 852b47c..a9d0b66 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,15 +1,24 @@ services: - # 백엔드 애플리케이션 + # 1. 백엔드 애플리케이션 blog-api: build: . container_name: blog-api restart: always + ports: + - "8080:8080" environment: + # Database - SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/blog_db - SPRING_DATASOURCE_USERNAME=wypark - SPRING_DATASOURCE_PASSWORD=your_password + # Redis - SPRING_DATA_REDIS_HOST=redis - SPRING_DATA_REDIS_PORT=6379 + # AWS S3 / MinIO (Docker 내부 통신용) + - CLOUD_AWS_CREDENTIALS_ACCESS_KEY=admin + - CLOUD_AWS_CREDENTIALS_SECRET_KEY=password + - CLOUD_AWS_REGION_STATIC=ap-northeast-2 + - CLOUD_AWS_S3_ENDPOINT=http://minio:9000 depends_on: db: condition: service_healthy @@ -18,11 +27,13 @@ services: networks: - blog-net - # 데이터베이스 (PostgreSQL 17 추천) + # 2. 데이터베이스 (PostgreSQL) db: image: postgres:17-alpine container_name: blog-db restart: always + ports: + - "5432:5432" environment: POSTGRES_USER: wypark POSTGRES_PASSWORD: your_password @@ -36,7 +47,7 @@ services: networks: - blog-net - # 캐시 서버 (Redis 7) + # 3. 캐시 서버 (Redis) redis: image: redis:7-alpine container_name: blog-redis @@ -48,6 +59,22 @@ services: networks: - blog-net + # 4. 오브젝트 스토리지 (MinIO) + minio: + image: minio/minio + container_name: minio + ports: + - "9000:9000" # API 통신 + - "9001:9001" # 웹 콘솔 + environment: + MINIO_ROOT_USER: "admin" + MINIO_ROOT_PASSWORD: "password" + command: server /data --console-address ":9001" + volumes: + - ./minio_data:/data + networks: + - blog-net + networks: blog-net: driver: bridge \ No newline at end of file diff --git a/src/main/kotlin/me/wypark/blogbackend/domain/auth/RefreshTokenRepository.kt b/src/main/kotlin/me/wypark/blogbackend/domain/auth/RefreshTokenRepository.kt index 6820f27..39b6b02 100644 --- a/src/main/kotlin/me/wypark/blogbackend/domain/auth/RefreshTokenRepository.kt +++ b/src/main/kotlin/me/wypark/blogbackend/domain/auth/RefreshTokenRepository.kt @@ -11,7 +11,6 @@ class RefreshTokenRepository( @Value("\${jwt.refresh-token-validity}") private val refreshTokenValidity: Long ) { // 저장 (Key: Email, Value: RefreshToken) - // RTR 핵심: 사용자가 로그인을 새로 하거나 토큰을 재발급 받을 때마다 덮어씌움 fun save(email: String, refreshToken: String) { redisTemplate.opsForValue().set( "RT:$email", diff --git a/src/main/resources/application-prod.yml b/src/main/resources/application-prod.yml index 4debd48..7e8f51e 100644 --- a/src/main/resources/application-prod.yml +++ b/src/main/resources/application-prod.yml @@ -1,19 +1,49 @@ spring: + application: + name: blog-api + + # 1. 데이터베이스 설정 datasource: + # Docker 내부 통신용 url: jdbc:postgresql://db:5432/blog_db - username: wypark - password: your_password + username: ${SPRING_DATASOURCE_USERNAME:wypark} + password: ${SPRING_DATASOURCE_PASSWORD:your_password} + driver-class-name: org.postgresql.Driver + + # 2. JPA 설정 jpa: hibernate: ddl-auto: update properties: hibernate: - format_sql: true + format_sql: true # 쿼리 줄바꿈 + show_sql: true # 쿼리 출력 + highlight_sql: true # 쿼리 색상 강조 (가독성 UP) + open-in-view: false # OSIV 종료 (DB 커넥션 최적화) + + # 3. Redis 설정 data: redis: - host: redis + host: redis # Docker 서비스명 port: 6379 + +# 4. AWS S3 / MinIO 설정 (아까 이야기한 부분) +cloud: + aws: + s3: + bucket: my-blog-bucket # MinIO 콘솔에서 미리 생성해야 함 + endpoint: http://minio:9000 # Docker 내부 통신용 + credentials: + access-key: admin + secret-key: password + region: + static: ap-northeast-2 + stack: + auto: false + +# 5. JWT 설정 jwt: - secret: "v3ryS3cr3tK3yF0rMyB10gPr0j3ctM4k3ItL0ng3rTh4n32Byt3s!!" # 32바이트 이상 필수 - access-token-validity: 1800000 # 30분 (ms) - refresh-token-validity: 604800000 # 7일 (ms) \ No newline at end of file + # 보안 경고: 실제 배포 시에는 절대 코드에 비밀키를 남기지 마세요. 환경변수로 주입받으세요. + secret: ${JWT_SECRET:v3ryS3cr3tK3yF0rMyB10gPr0j3ctM4k3ItL0ng3rTh4n32Byt3s!!} + access-token-validity: 1800000 # 30분 + refresh-token-validity: 604800000 # 7일 \ No newline at end of file