Skip to content
ย 
ย 

Latest commit

ย 

History

214 Commits

Folders and files

NameName
Last commit message
Last commit date
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

Highlight Backend

Java Spring Boot Spring Security WebSocket MySQL AWS Docker

์‹ค์‹œ๊ฐ„ ๊ฒฝ๋งค ํ”Œ๋žซํผ Highlight์˜ ๋ฐฑ์—”๋“œ API ์„œ๋ฒ„. ๋‹ค์ˆ˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ๊ฒฝํ•ฉํ•˜๋Š” ์ž…์ฐฐ ํ™˜๊ฒฝ์—์„œ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ยท ์‹ค์‹œ๊ฐ„์„ฑ ยท ๊ฒฝ๋งค ์ƒ๋ช…์ฃผ๊ธฐ ์ž๋™ํ™”๋ฅผ ํ•ต์‹ฌ ๊ณผ์ œ๋กœ ์‚ผ์•„ ์„ค๊ณ„ํ–ˆ์Šต๋‹ˆ๋‹ค.

2025 ๋ธ”๋ ˆ์ด๋ฒ„์Šค MVP ๊ฐœ๋ฐœ ํ•ด์ปคํ†ค ์‹œ์ฆŒ2 ์ˆ˜์ƒ์ž‘ ยท ๊ฐœ๋ฐœ ๊ธฐ๊ฐ„ 2025.08 (์•ฝ 1๊ฐœ์›”)


๊ธฐ์ˆ ์  ํ•˜์ด๋ผ์ดํŠธ

๊ฒฝ๋งค๋Š” "๊ฐ™์€ ์ˆœ๊ฐ„, ๊ฐ™์€ ๋ฌผ๊ฑด, ์—ฌ๋Ÿฌ ์‚ฌ๋žŒ"์ด ๋ถ€๋”ชํžˆ๋Š” ๋„๋ฉ”์ธ์ž…๋‹ˆ๋‹ค. ์ด ์„œ๋ฒ„๋Š” ๊ทธ ๊ฒฝํ•ฉ ์ง€์ ์„ ์–ด๋–ป๊ฒŒ ์•ˆ์ „ํ•˜๊ณ  ๋น ๋ฅด๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š”๊ฐ€์— ์ง‘์ค‘ํ–ˆ์Šต๋‹ˆ๋‹ค.

1. ๋™์‹œ ์ž…์ฐฐ ์ •ํ•ฉ์„ฑ โ€” Pessimistic Lock

์—ฌ๋Ÿฌ ์‚ฌ์šฉ์ž๊ฐ€ ๋™์‹œ์— ๊ฐ™์€ ๊ฒฝ๋งค์— ์ž…์ฐฐํ•˜๋ฉด race condition์œผ๋กœ ์ตœ๊ณ ๊ฐ€๊ฐ€ ๋ฎ์–ด์จ์ง€๊ฑฐ๋‚˜ ์ค‘๋ณต ๋‚™์ฐฐ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ž…์ฐฐ ํŠธ๋žœ์žญ์…˜ ์ง„์ž… ์‹œ์ ์— ํ•ด๋‹น Auction ํ–‰์— ๋ฐฐํƒ€ ๋ฝ(PESSIMISTIC_WRITE) ์„ ๊ฑธ์–ด, ๊ฒ€์ฆ โ†’ ์ด์ „ ์ตœ๊ณ ๊ฐ€ OUTBID ์ „ํ™˜ โ†’ ์‹ ๊ทœ ์ž…์ฐฐ ์ €์žฅ โ†’ ์ตœ๊ณ ๊ฐ€ ๊ฐฑ์‹ ์„ ํ•˜๋‚˜์˜ ์›์ž์  ํ๋ฆ„์œผ๋กœ ์ง๋ ฌํ™”ํ•ฉ๋‹ˆ๋‹ค.

// AuctionRepository
@Lock(LockModeType.PESSIMISTIC_WRITE)
@Query("SELECT a FROM Auction a WHERE a.id = :auctionId")
Optional<Auction> findByIdWithLock(@Param("auctionId") Long auctionId);
  • ์ตœ์†Œ ์ธ์ƒํญ(minimumBid)ยท์ž…์ฐฐ ๋‹จ์œ„(bidUnit) ๊ฒ€์ฆ, ๋™์ผ๊ฐ€ ์ž…์ฐฐ์€ ์„ ์ฐฉ์ˆœ ์šฐ์„ 
  • ์ž๋™ ์ž…์ฐฐ(auto-bid) ์ƒํ•œ ๊ฒ€์ฆ
  • ๋ฐ€๋ ค๋‚œ ์ด์ „ ์ตœ๊ณ  ์ž…์ฐฐ์ž๋Š” ์ฆ‰์‹œ OUTBID ์ƒํƒœ๋กœ ์ „ํ™˜ํ•˜๊ณ  ๊ฐœ์ธ ์•Œ๋ฆผ ๋ฐœ์†ก

2. ์‹ค์‹œ๊ฐ„ ๊ฒฝ๋งค โ€” WebSocket over STOMP

STOMP ๋ฉ”์‹œ์ง€ ๋ธŒ๋กœ์ปค + SockJS fallback(/ws, /ws/auctions, /ws/direct)์œผ๋กœ ์‹ค์‹œ๊ฐ„ ์ฑ„๋„์„ ๊ตฌ์„ฑํ•˜๊ณ , ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ์™€ ๊ฐœ์ธ ์•Œ๋ฆผ์„ ์ฑ„๋„ ๋‹จ์œ„๋กœ ๋ถ„๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค.

์ฑ„๋„ ์šฉ๋„
/topic/auction/{id} 1:N โ€” ๊ฒฝ๋งค๋ณ„ ์‹ค์‹œ๊ฐ„ ์ •๋ณด(์ƒˆ ์ž…์ฐฐยทํ˜„์žฌ๊ฐ€ยท์ƒํƒœยท์นด์šดํŠธ๋‹ค์šด)
/topic/auctions 1:N โ€” ์ „์ฒด ๊ฒฝ๋งค ๋ชฉ๋ก ๋ณ€๊ฒฝ ์ŠคํŠธ๋ฆผ
/queue/user/{id}/notifications 1:1 โ€” ๊ฐœ์ธ outbid ์•Œ๋ฆผ

์ž…์ฐฐ ํ•œ ๊ฑด์ด ์„ฑ์‚ฌ๋˜๋ฉด ๊ตฌ๋…์ž ์ „์›์—๊ฒŒ ์ตœ๊ณ ๊ฐ€๋ฅผ push ํ•˜๋Š” ๋™์‹œ์—, ๋ฐ€๋ ค๋‚œ ์ง์ „ ์ตœ๊ณ  ์ž…์ฐฐ์ž์—๊ฒŒ๋งŒ ๊ฐœ์ธ ํ๋กœ ์•Œ๋ฆผ์„ ๋ณด๋ƒ…๋‹ˆ๋‹ค.

3. ๊ฒฝ๋งค ์ƒ๋ช…์ฃผ๊ธฐ ์ž๋™ํ™” โ€” Dynamic Scheduling + Self-Healing

// ๊ฒฝ๋งค๋ณ„ ์‹œ์ž‘ ์‹œ๊ฐ์— ๋งž์ถฐ ๊ฐœ๋ณ„ ์˜ˆ์•ฝ, ScheduledFuture๋กœ ์ทจ์†Œยท์žฌ์˜ˆ์•ฝ ๊ด€๋ฆฌ
ScheduledFuture<?> task = taskScheduler.schedule(() -> startAuction(id), startTime);
scheduledTasks.put(auctionId, task);   // ConcurrentHashMap<Long, ScheduledFuture<?>>
  • ๋™์  ์˜ˆ์•ฝ: ๊ฒฝ๋งค๋งˆ๋‹ค TaskScheduler๋กœ ์‹œ์ž‘ ์‹œ๊ฐ์„ ์˜ˆ์•ฝํ•˜๊ณ  ConcurrentHashMap์œผ๋กœ ๊ฐœ๋ณ„ ์ทจ์†Œยท์žฌ์˜ˆ์•ฝ
  • ์•ˆ์ „๋ง(self-healing): ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ยท์˜ˆ์•ฝ ๋ˆ„๋ฝ์— ๋Œ€๋น„ํ•ด 1๋ถ„ ์ฃผ๊ธฐ ์Šค์œ•์œผ๋กœ ๋†“์นœ ์‹œ์ž‘/๋งŒ๋ฃŒ ์ข…๋ฃŒ๋ฅผ ์ž๋™ ๋ณต๊ตฌํ•˜๊ณ  ์ƒํ’ˆ ์ƒํƒœ๋ฅผ ๋™๊ธฐํ™”
  • 1์ดˆ ์นด์šดํŠธ๋‹ค์šด: ์ง„ํ–‰ ์ค‘ ๊ฒฝ๋งค์˜ ๋‚จ์€ ์‹œ๊ฐ„์„ ์ดˆ ๋‹จ์œ„๋กœ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ

4. ์—ฐ๊ด€ ์ƒํ’ˆ ์ถ”์ฒœ โ€” ์กฐํšŒ ์ด๋ ฅ ๊ธฐ๋ฐ˜ ๋ฐฐ์น˜

์‚ฌ์šฉ์ž ์กฐํšŒ ์ด๋ ฅ(UserProductView)์„ ๋ถ„์„ํ•ด ํ•จ๊ป˜ ๋ณธ ์ƒํ’ˆ(co-view) ์—ฐ๊ด€๋„๋ฅผ ๊ณ„์‚ฐํ•˜๊ณ , ์ƒ์„ธ ํŽ˜์ด์ง€ ์ถ”์ฒœ์— ํ™œ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • @Scheduled(cron = "0 0 3 * * *") โ€” ๋งค์ผ ์ƒˆ๋ฒฝ 3์‹œ @Async ๋ฐฐ์น˜๋กœ ์ตœ๊ทผ 30์ผ ์ด๋ ฅ ์ง‘๊ณ„ โ†’ ProductAssociation ๊ฐฑ์‹ 
  • @Scheduled(cron = "0 0 4 * * 0") โ€” ๋งค์ฃผ ์ผ์š”์ผ ์ƒˆ๋ฒฝ 4์‹œ ์˜ค๋ž˜๋œ ์—ฐ๊ด€ ๋ฐ์ดํ„ฐ ์ •๋ฆฌ

5. ์ธ์ฆ ยท ์˜ˆ์™ธ ์„ค๊ณ„

  • JWT Access/Refresh ์ด์ค‘ ํ† ํฐ, JwtAuthenticationFilter๋ฅผ ์‹œํ๋ฆฌํ‹ฐ ํ•„ํ„ฐ์ฒด์ธ ์„ ๋‘์— ๋ฐฐ์น˜, ์„ธ์…˜์€ STATELESS
  • CoolSMS ํœด๋Œ€ํฐ ๋ณธ์ธ์ธ์ฆ ๊ธฐ๋ฐ˜ ํšŒ์›๊ฐ€์ž…/๋กœ๊ทธ์ธ
  • ๋„๋ฉ”์ธ๋ณ„ ErrorCode enum + BusinessException + @RestControllerAdvice GlobalExceptionHandler๋กœ ์ผ๊ด€๋œ ์—๋Ÿฌ ์‘๋‹ต ๊ทœ๊ฒฉํ™”

์•„ํ‚คํ…์ฒ˜

flowchart LR
    subgraph Client["Client โ€” Next.js Web"]
        FE["React 19<br/>STOMP.js / SockJS"]
    end

    FE -- "REST ยท JWT" --> CTRL["Controller Layer<br/>(19 controllers)"]
    FE -- "WebSocket ยท STOMP" --> BROKER["STOMP Broker<br/>/topic ยท /queue"]

    CTRL --> SVC["Service Layer<br/>(18 services)"]
    BROKER --> SVC

    SVC --> REPO["Repository โ€” Spring Data JPA"]
    REPO --> DB[("MySQL 8.0<br/>AWS RDS")]
    SVC --> S3[("AWS S3<br/>์ƒํ’ˆ ์ด๋ฏธ์ง€")]

    SCH["Scheduler<br/>TaskScheduler ยท @Scheduled"] --> SVC
    BAT["Batch<br/>์—ฐ๊ด€๋„ @Async cron"] --> REPO
Loading

Layered Architecture โ€” Controller โ†’ Service โ†’ Repository โ†’ Domain, ํšก๋‹จ ๊ด€์‹ฌ์‚ฌ๋Š” security ยท exception ยท util ยท validation์œผ๋กœ ๋ถ„๋ฆฌ.


์ž…์ฐฐ ์ฒ˜๋ฆฌ ํ๋ฆ„

sequenceDiagram
    participant C as Client
    participant Ctrl as BidController
    participant Svc as BidService
    participant DB as MySQL
    participant WS as STOMP Broker

    C->>Ctrl: POST /api/bids (JWT)
    Ctrl->>Svc: createBid(request, userId)
    Svc->>DB: findByIdWithLock(auctionId) [PESSIMISTIC_WRITE]
    Svc->>Svc: ์ž…์ฐฐ ๊ฒ€์ฆ (์ตœ์†Œ ์ธ์ƒํญ ยท ์ž…์ฐฐ ๋‹จ์œ„ ยท ์ž๋™์ž…์ฐฐ ์ƒํ•œ)
    Svc->>DB: ์ด์ „ ์ตœ๊ณ ๊ฐ€ OUTBID, ์‹ ๊ทœ ์ž…์ฐฐ ์ €์žฅ, ์ตœ๊ณ ๊ฐ€ ๊ฐฑ์‹ 
    Svc-->>WS: /topic/auction/{id} ์ƒˆ ์ž…์ฐฐ ๋ธŒ๋กœ๋“œ์บ์ŠคํŠธ
    Svc-->>WS: /queue/user/{prev} ๊ฐœ์ธ outbid ์•Œ๋ฆผ
    WS-->>C: ์‹ค์‹œ๊ฐ„ ๊ฐฑ์‹  push
    Svc-->>Ctrl: BidResponseDto
    Ctrl-->>C: 200 OK
Loading

๋ฐ์ดํ„ฐ ๋ชจ๋ธ (ERD)

erDiagram
    USER    ||--o{ BID : places
    AUCTION ||--o{ BID : receives
    PRODUCT ||--|| AUCTION : "auctioned as"
    SELLER  ||--o{ PRODUCT : registers
    PRODUCT ||--o{ PRODUCT_IMAGE : has
    USER    ||--o{ USER_PRODUCT_VIEW : views
    PRODUCT ||--o{ PRODUCT_ASSOCIATION : "related to"
    USER    ||--o{ PRODUCT_WISHLIST : wishes

    AUCTION {
        Long id PK
        AuctionStatus status
        BigDecimal startPrice
        BigDecimal currentHighestBid
        BigDecimal buyItNowPrice
        BigDecimal bidUnit
        datetime scheduledStartTime
        datetime scheduledEndTime
    }
    BID {
        Long id PK
        BigDecimal bidAmount
        BidStatus status
        boolean isAutoBid
        datetime createdAt
    }
    PRODUCT {
        Long id PK
        String productName
        ProductStatus status
        Category category
    }
    USER {
        Long id PK
        String nickname
        String phoneNumber
        Rank rank
    }
Loading

AuctionStatus: SCHEDULED โ†’ IN_PROGRESS โ†’ COMPLETED / FAILED / CANCELLED ยท BidStatus: WINNING / OUTBID / WON


๊ธฐ์ˆ  ์Šคํƒ

๊ตฌ๋ถ„ ๊ธฐ์ˆ 
Framework Spring Boot 3.4.5 (Java 17), Spring Data JPA, Spring Security, Spring WebSocket(STOMP)
Database MySQL 8.0 (AWS RDS) ยท H2 (ํ…Œ์ŠคํŠธ)
Realtime STOMP over WebSocket ยท SockJS fallback
Auth JWT (Access/Refresh) ยท CoolSMS ํœด๋Œ€ํฐ ์ธ์ฆ
Storage AWS S3 (์ƒํ’ˆ ์ด๋ฏธ์ง€)
Infra AWS EC2 ยท Docker ยท Docker Compose
CI/CD GitHub Actions โ†’ Docker Hub โ†’ EC2
Docs Swagger / OpenAPI 3.0 ยท Spring Actuator

ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

src/main/java/com/highlight/highlight_backend/
โ”œโ”€โ”€ config/         # Security ยท WebSocket(STOMP) ยท Scheduling ยท CORS ยท Swagger
โ”œโ”€โ”€ controller/     # REST API (19) + WebSocket ์ปจํŠธ๋กค๋Ÿฌ
โ”œโ”€โ”€ service/        # ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง (18) โ€” ์ž…์ฐฐ ยท ๊ฒฝ๋งค ์Šค์ผ€์ค„๋Ÿฌ ยท ์ถ”์ฒœ ๋ฐฐ์น˜ ๋“ฑ
โ”œโ”€โ”€ domain/         # JPA ์—”ํ‹ฐํ‹ฐ (12)
โ”œโ”€โ”€ repository/     # Spring Data JPA + Specification (๋™์  ๊ฒ€์ƒ‰)
โ”œโ”€โ”€ dto/            # ์š”์ฒญ/์‘๋‹ต ยท WebSocket ๋ฉ”์‹œ์ง€ DTO
โ”œโ”€โ”€ security/       # JWT ์ธ์ฆ ํ•„ํ„ฐ
โ”œโ”€โ”€ exception/      # ๋„๋ฉ”์ธ๋ณ„ ErrorCode + ์ „์—ญ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ
โ”œโ”€โ”€ validation/     # ์ปค์Šคํ…€ ๊ฒ€์ฆ (@ValidPassword)
โ””โ”€โ”€ util/           # JWT ยท ์ธ์ฆ ยท ์‘๋‹ต ์œ ํ‹ธ

๋กœ์ปฌ ์‹คํ–‰

์š”๊ตฌ์‚ฌํ•ญ: Java 17+ ยท Docker & Docker Compose

# 1) ํด๋ก 
git clone https://github.com/blaybus-highlight/backend.git
cd backend

# 2) .env ์ž‘์„ฑ (DB / JWT / AWS S3 / CoolSMS ๋“ฑ)
#    docker-compose.yml ์ฐธ๊ณ 

# 3) ์ „์ฒด ์Šคํƒ ์‹คํ–‰ (MySQL + Backend)
docker-compose up -d
docker-compose logs -f highlight-backend

# โ€” ๋˜๋Š” ๋กœ์ปฌ ๊ฐœ๋ฐœ ์‹คํ–‰ โ€”
docker-compose up -d highlight-db   # DB๋งŒ ์ปจํ…Œ์ด๋„ˆ๋กœ
./gradlew bootRun

# ํ…Œ์ŠคํŠธ
./gradlew test

API ๋ฌธ์„œ

  • Swagger UI: http://<host>:8085/swagger-ui/index.html
  • OpenAPI JSON: http://<host>:8085/api-docs

๋Œ€ํ‘œ ์—”๋“œํฌ์ธํŠธ

Method Path ์„ค๋ช…
POST /api/auth/phone/send ยท /verify SMS ์ธ์ฆ์ฝ”๋“œ ๋ฐœ์†ก/๊ฒ€์ฆ
POST /api/auth/signup ยท /login ํšŒ์›๊ฐ€์ž… / ๋กœ๊ทธ์ธ(JWT ๋ฐœ๊ธ‰)
GET /api/products/{id} ์ƒํ’ˆ ์ƒ์„ธ (์—ฐ๊ด€ ์ƒํ’ˆ ํฌํ•จ)
GET /api/auctions ๊ฒฝ๋งค ๋ชฉ๋ก ์กฐํšŒ
GET /api/auctions/{id}/status ์‹ค์‹œ๊ฐ„ ๊ฒฝ๋งค ์ƒํƒœ
POST /api/bids ์ž…์ฐฐํ•˜๊ธฐ (๋ฝ ๊ธฐ๋ฐ˜ ์ฒ˜๋ฆฌ)
POST /api/buy-now ์ฆ‰์‹œ๊ตฌ๋งค

CI/CD

main ๋ธŒ๋žœ์น˜ push โ†’ GitHub Actions๊ฐ€ Gradle ๋นŒ๋“œ โ†’ Docker ์ด๋ฏธ์ง€ ๋นŒ๋“œ & Docker Hub push โ†’ SSH๋กœ EC2 ๋ฐฐํฌ๊นŒ์ง€ ์ž๋™ํ™”.

push(main) โ†’ Gradle build โ†’ Docker build&push โ†’ Docker Hub โ†’ EC2 pull&run

About

Blaybus MVP Hackathon Season 2 | Excellence Award Winner ๐Ÿ†

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages