Docs: README 및 minio 추가
README.md 파일 작성. minio 사용을 위한 의존성 추가, 환경변수 수정. 기타 주석 정리.
This commit is contained in:
40
README.md
Normal file
40
README.md
Normal file
@@ -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)
|
||||||
@@ -1,9 +1,10 @@
|
|||||||
plugins {
|
plugins {
|
||||||
kotlin("jvm") version "1.9.25"
|
kotlin("jvm") version "1.9.25"
|
||||||
kotlin("plugin.spring") 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("org.springframework.boot") version "3.5.9"
|
||||||
id("io.spring.dependency-management") version "1.1.7"
|
id("io.spring.dependency-management") version "1.1.7"
|
||||||
kotlin("plugin.jpa") version "1.9.25"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "me.wypark"
|
group = "me.wypark"
|
||||||
@@ -12,7 +13,7 @@ description = "blog-backend"
|
|||||||
|
|
||||||
java {
|
java {
|
||||||
toolchain {
|
toolchain {
|
||||||
languageVersion = JavaLanguageVersion.of(21)
|
languageVersion = JavaLanguageVersion.of(21) // Java 21 굿!
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,22 +21,43 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val springCloudAwsVersion = "3.2.1"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// 1. Standard Spring Boot Starters
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-security")
|
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-data-redis")
|
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("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||||
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
implementation("org.jetbrains.kotlin:kotlin-reflect")
|
||||||
developmentOnly("org.springframework.boot:spring-boot-devtools")
|
|
||||||
|
// 3. Database Drivers
|
||||||
runtimeOnly("org.postgresql:postgresql")
|
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.springframework.boot:spring-boot-starter-test")
|
||||||
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
|
testImplementation("org.jetbrains.kotlin:kotlin-test-junit5")
|
||||||
testImplementation("org.springframework.security:spring-security-test")
|
testImplementation("org.springframework.security:spring-security-test")
|
||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
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 {
|
kotlin {
|
||||||
|
|||||||
@@ -1,15 +1,24 @@
|
|||||||
services:
|
services:
|
||||||
# 백엔드 애플리케이션
|
# 1. 백엔드 애플리케이션
|
||||||
blog-api:
|
blog-api:
|
||||||
build: .
|
build: .
|
||||||
container_name: blog-api
|
container_name: blog-api
|
||||||
restart: always
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
environment:
|
environment:
|
||||||
|
# Database
|
||||||
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/blog_db
|
- SPRING_DATASOURCE_URL=jdbc:postgresql://db:5432/blog_db
|
||||||
- SPRING_DATASOURCE_USERNAME=wypark
|
- SPRING_DATASOURCE_USERNAME=wypark
|
||||||
- SPRING_DATASOURCE_PASSWORD=your_password
|
- SPRING_DATASOURCE_PASSWORD=your_password
|
||||||
|
# Redis
|
||||||
- SPRING_DATA_REDIS_HOST=redis
|
- SPRING_DATA_REDIS_HOST=redis
|
||||||
- SPRING_DATA_REDIS_PORT=6379
|
- 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:
|
depends_on:
|
||||||
db:
|
db:
|
||||||
condition: service_healthy
|
condition: service_healthy
|
||||||
@@ -18,11 +27,13 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- blog-net
|
- blog-net
|
||||||
|
|
||||||
# 데이터베이스 (PostgreSQL 17 추천)
|
# 2. 데이터베이스 (PostgreSQL)
|
||||||
db:
|
db:
|
||||||
image: postgres:17-alpine
|
image: postgres:17-alpine
|
||||||
container_name: blog-db
|
container_name: blog-db
|
||||||
restart: always
|
restart: always
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
environment:
|
environment:
|
||||||
POSTGRES_USER: wypark
|
POSTGRES_USER: wypark
|
||||||
POSTGRES_PASSWORD: your_password
|
POSTGRES_PASSWORD: your_password
|
||||||
@@ -36,7 +47,7 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- blog-net
|
- blog-net
|
||||||
|
|
||||||
# 캐시 서버 (Redis 7)
|
# 3. 캐시 서버 (Redis)
|
||||||
redis:
|
redis:
|
||||||
image: redis:7-alpine
|
image: redis:7-alpine
|
||||||
container_name: blog-redis
|
container_name: blog-redis
|
||||||
@@ -48,6 +59,22 @@ services:
|
|||||||
networks:
|
networks:
|
||||||
- blog-net
|
- 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:
|
networks:
|
||||||
blog-net:
|
blog-net:
|
||||||
driver: bridge
|
driver: bridge
|
||||||
@@ -11,7 +11,6 @@ class RefreshTokenRepository(
|
|||||||
@Value("\${jwt.refresh-token-validity}") private val refreshTokenValidity: Long
|
@Value("\${jwt.refresh-token-validity}") private val refreshTokenValidity: Long
|
||||||
) {
|
) {
|
||||||
// 저장 (Key: Email, Value: RefreshToken)
|
// 저장 (Key: Email, Value: RefreshToken)
|
||||||
// RTR 핵심: 사용자가 로그인을 새로 하거나 토큰을 재발급 받을 때마다 덮어씌움
|
|
||||||
fun save(email: String, refreshToken: String) {
|
fun save(email: String, refreshToken: String) {
|
||||||
redisTemplate.opsForValue().set(
|
redisTemplate.opsForValue().set(
|
||||||
"RT:$email",
|
"RT:$email",
|
||||||
|
|||||||
@@ -1,19 +1,49 @@
|
|||||||
spring:
|
spring:
|
||||||
|
application:
|
||||||
|
name: blog-api
|
||||||
|
|
||||||
|
# 1. 데이터베이스 설정
|
||||||
datasource:
|
datasource:
|
||||||
|
# Docker 내부 통신용
|
||||||
url: jdbc:postgresql://db:5432/blog_db
|
url: jdbc:postgresql://db:5432/blog_db
|
||||||
username: wypark
|
username: ${SPRING_DATASOURCE_USERNAME:wypark}
|
||||||
password: your_password
|
password: ${SPRING_DATASOURCE_PASSWORD:your_password}
|
||||||
|
driver-class-name: org.postgresql.Driver
|
||||||
|
|
||||||
|
# 2. JPA 설정
|
||||||
jpa:
|
jpa:
|
||||||
hibernate:
|
hibernate:
|
||||||
ddl-auto: update
|
ddl-auto: update
|
||||||
properties:
|
properties:
|
||||||
hibernate:
|
hibernate:
|
||||||
format_sql: true
|
format_sql: true # 쿼리 줄바꿈
|
||||||
|
show_sql: true # 쿼리 출력
|
||||||
|
highlight_sql: true # 쿼리 색상 강조 (가독성 UP)
|
||||||
|
open-in-view: false # OSIV 종료 (DB 커넥션 최적화)
|
||||||
|
|
||||||
|
# 3. Redis 설정
|
||||||
data:
|
data:
|
||||||
redis:
|
redis:
|
||||||
host: redis
|
host: redis # Docker 서비스명
|
||||||
port: 6379
|
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:
|
jwt:
|
||||||
secret: "v3ryS3cr3tK3yF0rMyB10gPr0j3ctM4k3ItL0ng3rTh4n32Byt3s!!" # 32바이트 이상 필수
|
# 보안 경고: 실제 배포 시에는 절대 코드에 비밀키를 남기지 마세요. 환경변수로 주입받으세요.
|
||||||
access-token-validity: 1800000 # 30분 (ms)
|
secret: ${JWT_SECRET:v3ryS3cr3tK3yF0rMyB10gPr0j3ctM4k3ItL0ng3rTh4n32Byt3s!!}
|
||||||
refresh-token-validity: 604800000 # 7일 (ms)
|
access-token-validity: 1800000 # 30분
|
||||||
|
refresh-token-validity: 604800000 # 7일
|
||||||
Reference in New Issue
Block a user