Skip to content

Latest commit

ย 

History

125 Commits

Folders and files

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

Repository files navigation

MTM Backend

MCM ์ œํ’ˆ ๊ฐ€์ƒ ์ฐฉ์šฉ ์„œ๋น„์Šค โ€” ๋ฐฑ์—”๋“œ

ํšŒ์›์ด ์˜ฌ๋ฆฐ ์ „์‹  ์‚ฌ์ง„์„ ๊ธฐ์ค€ ์ด๋ฏธ์ง€(์ •์ž์„ธยทํฐ ๋ฐฐ๊ฒฝ)๋กœ ํ•œ ๋ฒˆ ๋ณ€ํ™˜ํ•ด ๋‘๊ณ , MCM ์ œํ’ˆ์„ ๊ณ ๋ฅผ ๋•Œ๋งˆ๋‹ค ๊ทธ ์œ„์— ์ œํ’ˆ์ด ์˜ฌ๋ผ๊ฐ„ ์ฐฉ์šฉ ์ด๋ฏธ์ง€๋ฅผ ์ƒ์„ฑํ•œ๋‹ค.

์›๋ณธ ์‚ฌ์ง„  โ”€โ”€(AI 1ํšŒ)โ”€โ”€โ–ถ  ๊ธฐ์ค€ ์ด๋ฏธ์ง€  โ”€โ”€(AI, ์ œํ’ˆ๋งˆ๋‹ค)โ”€โ”€โ–ถ  ์ฐฉ์šฉ ์ด๋ฏธ์ง€
ํšŒ์› ์—…๋กœ๋“œ              ์ •์ž์„ธยทํฐ ๋ฐฐ๊ฒฝ                  (์‚ฌ์ง„, SKU)๋ณ„ ์ €์žฅยท์žฌ์‚ฌ์šฉ

๊ธฐ์ˆ  ์Šคํƒ

๊ตฌ๋ถ„ ๊ธฐ์ˆ  ๋ฒ„์ „
Language Java 21
Framework Spring Boot 4.1.0
Build Gradle (Groovy) -
ORM Spring Data JPA Boot ๊ด€๋ฆฌ
DB MySQL (AWS RDS) 8.x
์ธ์ฆ Spring Security + JWT (jjwt) 0.13.0
์ด๋ฏธ์ง€ ์ €์žฅ์†Œ AWS SDK for Java v2 (S3) BOM 2.51.2
์ด๋ฏธ์ง€ ์ƒ์„ฑ Google Gen AI Java SDK (Gemini) 1.65.0
์ด๋ฏธ์ง€ ์ƒ์„ฑ(ํด๋ฐฑ) OpenAI Java SDK 4.50.0
API ๋ฌธ์„œ springdoc-openapi 3.1.0
๋ฐฐํฌ Docker ยท AWS EC2 -
ํ…Œ์ŠคํŠธ JUnit 5, Spring Boot Test, H2 Boot ๊ด€๋ฆฌ

๊ตฌํ˜„ ํ˜„ํ™ฉ

์Šฌ๋ผ์ด์Šค ์ด์Šˆ ๋‚ด์šฉ ์ƒํƒœ
1 #3 ์ œํ’ˆ ๋ชฉ๋ก์ด ํ™”๋ฉด์— ๋œฌ๋‹ค โœ… (#24๋กœ ๊ตฌํ˜„)
2 #4 ๊ฐ€์ž…ํ•˜๊ณ  ๋กœ๊ทธ์ธํ•  ์ˆ˜ ์žˆ๋‹ค โœ…
3 #5 ์‹ ์ฒด ์ •๋ณด๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์ €์žฅํ•œ๋‹ค โœ…
4 #6 ์‚ฌ์ง„์„ ์˜ฌ๋ฆฌ๋ฉด ์‚ฌ์ง„์ฒฉ์— ์Œ“์ธ๋‹ค โœ…
5 #7 ์˜ฌ๋ฆฐ ์‚ฌ์ง„์ด ๊ธฐ์ค€ ์ด๋ฏธ์ง€๋กœ ๋ฐ”๋€๋‹ค โœ…
6 #8 ์ œํ’ˆ์„ ๊ณ ๋ฅด๋ฉด ์ฐฉ์šฉ ์ด๋ฏธ์ง€๊ฐ€ ๋งŒ๋“ค์–ด์ง„๋‹ค โœ…
7 #9 ์ด๋ฏธ ๋ณธ ์ œํ’ˆ์€ ์ฆ‰์‹œ ๋‹ค์‹œ ๋œฌ๋‹ค โœ…
8 #10 ์ฐฉ์šฉ ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์‹œ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค โœ…
9 #11 ๊ธฐ์ค€ ์ด๋ฏธ์ง€๋ฅผ ๋‹ค์‹œ ๋งŒ๋“ค๋ฉด ์ฐฉ์šฉ ์ด๋ฏธ์ง€๊ฐ€ ์ •๋ฆฌ๋œ๋‹ค โœ…
10 #12 OpenAI๋กœ ๊ฐˆ์•„๋ผ์›Œ ๋น„๊ตํ•œ๋‹ค โœ… Gemini ์ตœ์ข… ์ฑ„ํƒ

์Šฌ๋ผ์ด์Šค 10๊ฐœ ์ „๋ถ€ ์™„๋ฃŒ. MCM ์ œํ’ˆ 30 SKU์™€ ์ œํ’ˆ ์ปท 231์žฅ์ด ์ ์žฌ๋˜์–ด ์žˆ๋‹ค.

์ด๋ฏธ์ง€ ์ƒ์„ฑ์€ ์ฒ˜์Œ์— OpenAI๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค. ํ…Œ์ŠคํŠธ ๊ณผ์ •์—์„œ ์‘๋‹ต ์‹œ๊ฐ„๊ณผ ๊ฒฐ๊ณผ ํ’ˆ์งˆ์— ๋ฌธ์ œ๊ฐ€ ์žˆ์–ด Gemini์™€ ๋น„๊ต ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ๊ณ , ํŒ€ ํšŒ์˜๋ฅผ ๊ฑฐ์ณ Gemini๋ฅผ ์ตœ์ข… ์ฑ„ํƒํ–ˆ๋‹ค. ์ž์„ธํ•œ ๊ฒฝ์œ„๋Š” ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…์— ์ •๋ฆฌํ–ˆ๋‹ค.

API ๋ชฉ๋ก

๋ฉ”์„œ๋“œ ๊ฒฝ๋กœ ์„ค๋ช…
POST /api/v1/auth/signup ํšŒ์›๊ฐ€์ž…
POST /api/v1/auth/login ๋กœ๊ทธ์ธ (์•ก์„ธ์Šคยท๋ฆฌํ”„๋ ˆ์‹œ ํ† ํฐ ๋ฐœ๊ธ‰)
POST /api/v1/auth/reissue ์•ก์„ธ์Šค ํ† ํฐ ์žฌ๋ฐœ๊ธ‰
GET /api/v1/members/me ๋‚ด ์ •๋ณด ์กฐํšŒ
POST /api/v1/members/me/body-info ์‹ ์ฒด ์ •๋ณด ์ €์žฅ
PATCH /api/v1/members/me/body-info ์‹ ์ฒด ์ •๋ณด ๋ถ€๋ถ„ ์ˆ˜์ •
GET /api/v1/products ์ œํ’ˆ ๋ชฉ๋ก ์กฐํšŒ (ํŽ˜์ด์ง•)
GET /api/v1/products/{productId} ์ œํ’ˆ ์ƒ์„ธ ์กฐํšŒ
POST /api/v1/photos ์›๋ณธ ์‚ฌ์ง„ ์—…๋กœ๋“œ
GET /api/v1/photos ์‚ฌ์ง„์ฒฉ ์กฐํšŒ
DELETE /api/v1/photos/{photoId} ์‚ฌ์ง„ ์‚ญ์ œ (๊ธฐ์ค€ยท์ฐฉ์šฉ ์ด๋ฏธ์ง€ ์—ฐ์‡„ ์‚ญ์ œ)
POST /api/v1/photos/{photoId}/base-image ๊ธฐ์ค€ ์ด๋ฏธ์ง€ ์ƒ์„ฑ
POST /api/v1/photos/{photoId}/base-image/regenerate ๊ธฐ์ค€ ์ด๋ฏธ์ง€ ์žฌ์ƒ์„ฑ
GET /api/v1/base-images ๋‚ด ๊ธฐ์ค€ ์ด๋ฏธ์ง€ ๋ชฉ๋ก ์กฐํšŒ
POST /api/v1/base-images/{baseImageId}/worn-images ์ฐฉ์šฉ ์ด๋ฏธ์ง€ ์ƒ์„ฑ (๊ฐ™์€ ์กฐํ•ฉ์€ ์žฌ์‚ฌ์šฉ)
POST /api/v1/base-images/{baseImageId}/worn-images/regenerate ์ฐฉ์šฉ ์ด๋ฏธ์ง€ ์žฌ์ƒ์„ฑ
GET /api/v1/base-images/{baseImageId}/worn-images ๊ธฐ์ค€ ์ด๋ฏธ์ง€๋ณ„ ์ฐฉ์šฉ ์ด๋ฏธ์ง€ ๋ชฉ๋ก ์กฐํšŒ

ํŠธ๋Ÿฌ๋ธ”์ŠˆํŒ…

1. ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๊ณต๊ธ‰์ž๋ฅผ OpenAI์—์„œ Gemini๋กœ ๊ต์ฒด

์ด๋ฏธ์ง€ ์ƒ์„ฑ์€ ์ฒ˜์Œ์— OpenAI(gpt-image-2)๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค. ๋™์ž‘์€ ํ–ˆ์ง€๋งŒ ํ…Œ์ŠคํŠธ์—์„œ ๋‘ ๊ฐ€์ง€๊ฐ€ ๊ฑธ๋ ธ๋‹ค.

  • ์‘๋‹ต ์‹œ๊ฐ„ โ€” ๊ธธ๊ฒŒ๋Š” 30~40์ดˆ. ๋กœ๋”ฉ๋ฐ”๋กœ ๋ฒ„ํ‹ฐ๊ธฐ ์–ด๋ ต๊ณ , ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ๋ฅผ ๋ถ™์ด๋ฉด ๊ธฐ๋ณธ ํƒ€์ž„์•„์›ƒ(nginx 60์ดˆ)์—๋„ ๊ฑธ๋ฆด ์ˆ˜ ์žˆ๋Š” ๊ฐ’์ด๋‹ค.
  • ๊ฒฐ๊ณผ ํ’ˆ์งˆ โ€” ์ œํ’ˆ์ด ์›๋ณธ๊ณผ ๋‹ค๋ฅด๊ฒŒ ๊ทธ๋ ค์ง€๋Š” ๋นˆ๋„๊ฐ€ ๋†’์•˜๋‹ค.

๊ทธ๋ž˜์„œ Gemini์™€ ๋น„๊ต ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ–ˆ๊ณ , ์‘๋‹ต ์‹œ๊ฐ„์ด 10์ดˆ ์ด๋‚ด๋กœ ์งง๊ณ  ๊ฒฐ๊ณผ๋„ ๋” ์•ˆ์ •์ ์ด์–ด์„œ ํŒ€ ํšŒ์˜๋ฅผ ๊ฑฐ์ณ Gemini๋ฅผ ์ตœ์ข… ์ฑ„ํƒํ–ˆ๋‹ค.

๊ต์ฒด ๋น„์šฉ์ด ๋‚ฎ์•˜๋˜ ๊ฑด ImageGenerationGateway ์ธํ„ฐํŽ˜์ด์Šค ๋’ค์— ๊ตฌํ˜„์ฒด๋ฅผ ๋‘” ๊ตฌ์กฐ ๋•๋ถ„์ด๋‹ค. ๋„๋ฉ”์ธ ์ฝ”๋“œ๋Š” ํ•œ ์ค„๋„ ๋ฐ”๋€Œ์ง€ ์•Š์•˜๊ณ , ํ™˜๊ฒฝ ๋ณ€์ˆ˜ ํ•˜๋‚˜๋กœ ์ „ํ™˜๋œ๋‹ค.

IMAGE_GENERATION_PROVIDER=GEMINI

Gemini ์•ˆ์—์„œ ์–ด๋–ค ๋ชจ๋ธ์„ ์“ธ์ง€๋„ ์ธก์ •์œผ๋กœ ์ •ํ–ˆ๋‹ค. GeminiImageModelBenchmark๊ฐ€ ํ›„๋ณด 3์ข…(gemini-3.1-flash-lite-image / gemini-3.1-flash-image / gemini-3-pro-image)์„ ์‹ค์ œ ํ˜ธ์ถœํ•ด ๋ชจ๋ธ๋ณ„ ์ง€์—ฐ์‹œ๊ฐ„๊ณผ ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€๋ฅผ ๋น„๊ตํ•œ๋‹ค. ๊ณผ๊ธˆ์ด ๋ฐœ์ƒํ•˜๋ฏ€๋กœ CI์—์„œ๋Š” ์ œ์™ธํ•˜๊ณ  ํ•„์š”ํ•  ๋•Œ๋งŒ ์ˆ˜๋™์œผ๋กœ ์‹คํ–‰ํ•œ๋‹ค.

OpenAI ๊ตฌํ˜„์ฒด๋Š” ์ง€์šฐ์ง€ ์•Š๊ณ  ํด๋ฐฑ์œผ๋กœ ๋‚จ๊ฒจ ๋‘์—ˆ๋‹ค. ์„ค์ •๋งŒ ๋ฐ”๊พธ๋ฉด ์–ธ์ œ๋“  ๋˜๋Œ๋ฆด ์ˆ˜ ์žˆ๋‹ค.

2. ๊ฐ™์€ ์ œํ’ˆ์„ ๋‹ค์‹œ ๊ณ ๋ฅผ ๋•Œ๋งˆ๋‹ค AI๋ฅผ ๋‹ค์‹œ ํ˜ธ์ถœ

์ƒ์„ฑ ๊ฒฐ๊ณผ๊ฐ€ ๋งค๋ฒˆ ๋‹ฌ๋ผ์ง€๋Š” ํŠน์„ฑ(ADR 0001) ๋•Œ๋ฌธ์— (๊ธฐ์ค€ ์ด๋ฏธ์ง€, ์ œํ’ˆ) ์กฐํ•ฉ์œผ๋กœ ์ €์žฅํ•ด ๋‘๊ณ  ์žฌ์‚ฌ์šฉํ•œ๋‹ค. ๊ฐ™์€ ์กฐํ•ฉ์„ ๋‹ค์‹œ ์š”์ฒญํ•˜๋ฉด AI๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ  ์ €์žฅ๋œ ๊ฒฐ๊ณผ๋ฅผ ๋ฐ”๋กœ ๋Œ๋ ค์ค€๋‹ค.

์ˆ˜์‹ญ ์ดˆ์—์„œ 1์ดˆ ๋ฏธ๋งŒ์œผ๋กœ ์ค„์—ˆ๊ณ , AI ํ˜ธ์ถœ ๋น„์šฉ๋„ ๊ทธ๋งŒํผ ์•„๊ผˆ๋‹ค.

3. ํฌ๋กค๋ง ๋ฐ์ดํ„ฐ์˜ ํ†ตํ™”๊ฐ€ ์Šคํ‚ค๋งˆ์™€ ๋งž์ง€ ์•Š์Œ

product.currency๊ฐ€ ENUM('KRW')์ธ๋ฐ ํฌ๋กค๋งํ•œ 30๊ฑด์ด ์ „๋ถ€ USD์˜€๋‹ค. ddl-auto: validate๋ผ ์ ์žฌ๊ฐ€ ์•„์˜ˆ ๋˜์ง€ ์•Š๋Š” ์ƒํ™ฉ์ด์—ˆ๋‹ค.

ํ™˜์œจ๋กœ ํ™˜์‚ฐํ•˜๋ฉด ์›๋ณธ ๊ฐ€๊ฒฉ ์ •๋ณด๊ฐ€ ์‚ฌ๋ผ์ง€๋ฏ€๋กœ ENUM('KRW','USD')๋กœ ํ™•์žฅํ•˜๊ณ  ์›๋ณธ ํ†ตํ™”๋ฅผ ๊ทธ๋Œ€๋กœ ์ €์žฅํ•˜๋Š” ์ชฝ์„ ํƒํ–ˆ๋‹ค. ํ‘œ๊ธฐ ๋ฐฉ์‹์€ ํด๋ผ์ด์–ธํŠธ๊ฐ€ ์ •ํ•œ๋‹ค.

4. ์‚ฌ์ง„์„ ์‚ญ์ œํ•ด๋„ ์ €์žฅ์†Œ์— ํŒŒ์ผ์ด ๋‚จ์Œ

์™ธ๋ž˜ ํ‚ค๊ฐ€ ON DELETE CASCADE๋ผ ์‚ฌ์ง„๋งŒ ์ง€์›Œ๋„ DB๋Š” ์ •๋ฆฌ๋˜์ง€๋งŒ, ํ–‰์ด ์‚ฌ๋ผ์ง€๋Š” ์ˆœ๊ฐ„ storage_key๋ฅผ ์žƒ์–ด S3 ๊ฐ์ฒด๊ฐ€ ์˜๊ตฌํžˆ ๋‚จ๋Š”๋‹ค.

์ง€์šฐ๊ธฐ ์ „์— ์ €์žฅ์†Œ ํ‚ค๋ฅผ ๋ชจ์œผ๊ณ  ์ž์‹(์ฐฉ์šฉ ์ด๋ฏธ์ง€ โ†’ ๊ธฐ์ค€ ์ด๋ฏธ์ง€ โ†’ ์›๋ณธ ์‚ฌ์ง„)๋ถ€ํ„ฐ ๋ช…์‹œ์ ์œผ๋กœ ์‚ญ์ œํ•œ๋‹ค. ์ €์žฅ์†Œ ์‚ญ์ œ๋Š” DB ์‚ญ์ œ๊ฐ€ ๋๋‚œ ๋’ค ๋งˆ์ง€๋ง‰์— ์ˆ˜ํ–‰ํ•œ๋‹ค โ€” ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ์ง€์› ๋‹ค๊ฐ€ ๋กค๋ฐฑ๋˜๋ฉด DB๋Š” ๋˜๋Œ์•„๊ฐ€๋„ ํŒŒ์ผ์€ ๋ณต๊ตฌํ•  ์ˆ˜ ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

5. ๋ชฉ๋ก ์กฐํšŒ์˜ N+1

์ œํ’ˆ ๋ชฉ๋ก์˜ ๋Œ€ํ‘œ ์ด๋ฏธ์ง€, ์‚ฌ์ง„์ฒฉ์˜ ๊ธฐ์ค€ ์ด๋ฏธ์ง€๋ฅผ ํ•ญ๋ชฉ๋งˆ๋‹ค ์กฐํšŒํ•˜๋ฉด N+1์ด ๋ฐœ์ƒํ•œ๋‹ค. ๋‘ ๊ณณ ๋ชจ๋‘ ํ˜„์žฌ ํŽ˜์ด์ง€ ๋ถ„๋Ÿ‰์„ ํ•œ ๋ฒˆ์— ์กฐํšŒํ•ด Map์œผ๋กœ ๋ฌถ์–ด ๋งค์นญํ•˜๋„๋ก ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

๋‚จ์•„ ์žˆ๋Š” ๊ณผ์ œ

  • ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์‹คํŒจ๊ฐ€ ๋กœ๊ทธ์— ๋‚จ์ง€ ์•Š๋Š” ๊ฒฝ๋กœ๊ฐ€ ์žˆ๋‹ค. SDK ์˜ˆ์™ธ๋Š” ๋กœ๊ทธ๊ฐ€ ๋‚จ์ง€๋งŒ, ์‘๋‹ต์— ์ด๋ฏธ์ง€๊ฐ€ ์—†์–ด parseGeneratedImage์—์„œ ๋˜์ง€๋Š” ๊ฒฝ์šฐ๋Š” ๋กœ๊ทธ ์—†์ด 502๋งŒ ๋‚˜๊ฐ„๋‹ค. ์›์ธ ์ถ”์ ์ด ์–ด๋ ค์›Œ ๊ฐœ์„ ์ด ํ•„์š”ํ•˜๋‹ค.
  • product_cut.is_worn_slot์ด ์ „๋ถ€ false๋‹ค. ํฌ๋กค๋ง ๋ฐ์ดํ„ฐ์— ๋ชจ๋ธ ์ฐฉ์šฉ ์ปท์„ ํŒ์ •ํ•  ๊ทผ๊ฑฐ๊ฐ€ ์—†์–ด ์ ์žฌ ์‹œ ์ง€์ •ํ•˜์ง€ ๋ชปํ–ˆ๋‹ค.

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

com.likelion.mtm
โ”œโ”€โ”€ MtmApplication.java
โ”‚
โ”œโ”€โ”€ global                          # ๋„๋ฉ”์ธ ๊ณตํ†ต
โ”‚   โ”œโ”€โ”€ config                      # SecurityConfig, SwaggerConfig, JpaAuditingConfig,
โ”‚   โ”‚                               # S3Config, GeminiConfig, OpenAiConfig, ImageGenerationConfig
โ”‚   โ”œโ”€โ”€ common                      # BaseTimeEntity, PageResponseDTO<T>
โ”‚   โ”œโ”€โ”€ rsdata                      # RsData<T>
โ”‚   โ”œโ”€โ”€ exception                   # CustomException, ErrorCode, GlobalExceptionHandler
โ”‚   โ””โ”€โ”€ security                    # JwtProvider, JwtAuthenticationFilter,
โ”‚                                   # JwtAuthenticationEntryPoint, JwtAccessDeniedHandler
โ”‚
โ”œโ”€โ”€ domain
โ”‚   โ”œโ”€โ”€ member                      # ํšŒ์› ยท ์ธ์ฆ ยท ์‹ ์ฒด ์ •๋ณด               [์Šฌ๋ผ์ด์Šค 2, 3]
โ”‚   โ”œโ”€โ”€ product                     # ์ œํ’ˆ ยท ์‹ค์ธก ์น˜์ˆ˜ ํŒŒ์„œ ยท ์ฐฉ์šฉ ๋ฐฉ์‹ ๋ถ„๋ฅ˜๊ธฐ ยท ํฌ๋กค๋ง ์ ์žฌ๊ธฐ [์Šฌ๋ผ์ด์Šค 1]
โ”‚   โ”œโ”€โ”€ photo                       # ์›๋ณธ ์‚ฌ์ง„(์‚ฌ์ง„์ฒฉ) ยท ๊ธฐ์ค€ ์ด๋ฏธ์ง€        [์Šฌ๋ผ์ด์Šค 4, 5, 9]
โ”‚   โ””โ”€โ”€ worn                        # ์ฐฉ์šฉ ์ด๋ฏธ์ง€ ยท ์žฌ์‚ฌ์šฉ ยท ์žฌ์ƒ์„ฑ          [์Šฌ๋ผ์ด์Šค 6, 7, 8]
โ”‚
โ””โ”€โ”€ infra                           # ๊ต์ฒด ๊ฐ€๋Šฅํ•œ ์™ธ๋ถ€ ์—ฐ๋™ โ€” PRD๊ฐ€ ์š”๊ตฌํ•˜๋Š” ๊ฒฝ๊ณ„
    โ”œโ”€โ”€ storage
    โ”‚   โ”œโ”€โ”€ ImageStorage            # ์ธํ„ฐํŽ˜์ด์Šค
    โ”‚   โ”œโ”€โ”€ S3ImageStorage          # ๊ตฌํ˜„์ฒด
    โ”‚   โ””โ”€โ”€ ImageData               # ์ด๋ฏธ์ง€ ๋ฐ”์ด๋„ˆ๋ฆฌ + MIME ํƒ€์ž…
    โ””โ”€โ”€ imagegen
        โ”œโ”€โ”€ ImageGenerationGateway  # ์ธํ„ฐํŽ˜์ด์Šค
        โ”œโ”€โ”€ GeminiImageGateway      # ์ฃผ๋ ฅ
        โ”œโ”€โ”€ OpenAiImageGateway      # ํด๋ฐฑ
        โ”œโ”€โ”€ BaseImagePromptAssembler   # ๊ธฐ์ค€ ์ด๋ฏธ์ง€ ํ”„๋กฌํ”„ํŠธ ์กฐ๋ฆฝ๊ธฐ
        โ”œโ”€โ”€ WornImagePromptAssembler   # ์ฐฉ์šฉ ์ด๋ฏธ์ง€ ํ”„๋กฌํ”„ํŠธ ์กฐ๋ฆฝ๊ธฐ
        โ””โ”€โ”€ ImageGenerationRequest / GeneratedImage / ImageInput / ImageGenerationProvider

๊ฐ domain ํŒจํ‚ค์ง€ ๋‚ด๋ถ€๋Š” controller / service / repository / entity / dto๋กœ ๋‚˜๋ˆˆ๋‹ค. ํ…Œ์ŠคํŠธ์šฉ FakeImageGateway๋Š” src/test/java/com/likelion/mtm/infra/imagegen์— ์žˆ๋‹ค.


์‹คํ–‰ ๋ฐฉ๋ฒ•

1. ํ™˜๊ฒฝ ๋ณ€์ˆ˜

๋ ˆํฌ ๋ฃจํŠธ์— .env๋ฅผ ๋งŒ๋“ ๋‹ค. .env๋Š” ์ปค๋ฐ‹ํ•˜์ง€ ์•Š๋Š”๋‹ค (.gitignore ํ™•์ธ).

DOCKER_IMAGE={๋ณธ์ธ๊ณ„์ •}/mtm-backend:latest
DATABASE_URL=jdbc:mysql://{RDS์—”๋“œํฌ์ธํŠธ}:3306/first_db?serverTimezone=Asia/Seoul&characterEncoding=UTF-8
DATABASE_USERNAME=
DATABASE_PASSWORD=
JWT_SECRET={32์ž ์ด์ƒ ๋žœ๋ค ๋ฌธ์ž์—ด}
JWT_ACCESS_EXP_MIN=60
JWT_REFRESH_EXP_DAY=30
IMAGE_GENERATION_PROVIDER=NONE
GEMINI_API_KEY=
OPENAI_API_KEY=
  • DATABASE_URL์€ JDBC URL ์ „์ฒด๋ฅผ ๋„ฃ๋Š”๋‹ค. ํ˜ธ์ŠคํŠธ๋งŒ ๋„ฃ์œผ๋ฉด ๊ธฐ๋™์— ์‹คํŒจํ•œ๋‹ค.
  • IMAGE_GENERATION_PROVIDER๋Š” GEMINI / OPENAI / NONE ์ค‘ ํ•˜๋‚˜๋‹ค. NONE์ด๋ฉด ์ด๋ฏธ์ง€ ์ƒ์„ฑ ๊ธฐ๋Šฅ๋งŒ ๋น ์ง€๊ณ  ํšŒ์›ยท์‚ฌ์ง„ยท์ œํ’ˆ ๊ธฐ๋Šฅ์€ ์ •์ƒ ๊ธฐ๋™ํ•œ๋‹ค. AI ํ‚ค ์—†์ด ๊ฐœ๋ฐœํ•  ๋•Œ ์“ด๋‹ค.
  • AWS ์ž๊ฒฉ์ฆ๋ช…์€ .env์— ๋„ฃ์ง€ ์•Š๋Š”๋‹ค. ๋กœ์ปฌ์€ ~/.aws/credentials, ๋ฐฐํฌ ์„œ๋ฒ„๋Š” EC2 IAM ์—ญํ• ๋กœ ์ฃผ์ž…๋œ๋‹ค.

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

./gradlew bootRun

Swagger โ€” http://localhost:8080/swagger-ui/index.html

3. ๋ฐฐํฌ

๋กœ์ปฌ์—์„œ ์ด๋ฏธ์ง€๋ฅผ ๋งŒ๋“ค์–ด ์˜ฌ๋ฆฐ๋‹ค

docker build -t {๋ณธ์ธ๊ณ„์ •}/mtm-backend:latest .
docker login
docker push {๋ณธ์ธ๊ณ„์ •}/mtm-backend:latest

์„œ๋ฒ„์—์„œ ๋ฐ›์•„ ์‹คํ–‰ํ•œ๋‹ค

์„œ๋ฒ„ ์ž‘์—… ๋””๋ ‰ํ„ฐ๋ฆฌ์— docker-compose.yml๊ณผ .env๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค. .env๋Š” ๊นƒ์— ์—†์œผ๋ฏ€๋กœ ์„œ๋ฒ„์—์„œ ์ง์ ‘ ๋งŒ๋“ ๋‹ค.

docker compose pull
docker compose up -d
docker compose logs -f
  • restart: unless-stopped๋ผ ์„œ๋ฒ„๊ฐ€ ์žฌ๋ถ€ํŒ…๋ผ๋„ ์ปจํ…Œ์ด๋„ˆ๊ฐ€ ์ž๋™์œผ๋กœ ์˜ฌ๋ผ์˜จ๋‹ค.
  • .env์— DOCKER_IMAGE๊ฐ€ ์—†์œผ๋ฉด compose๊ฐ€ ๊ธฐ๋™์„ ๊ฑฐ๋ถ€ํ•œ๋‹ค.

HTTPS

ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ HTTPS๋กœ ๋ฐฐํฌ๋˜์–ด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ˜ผํ•ฉ ์ฝ˜ํ…์ธ ๋ฅผ ์ฐจ๋‹จํ•˜๋ฏ€๋กœ, ๋ฐฑ์—”๋“œ๋„ HTTPS๋กœ ์„œ๋น„์Šคํ•œ๋‹ค.

  • ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ๊ฐ€ 443์—์„œ TLS๋ฅผ ์ข…๋ฃŒํ•˜๊ณ  ์ปจํ…Œ์ด๋„ˆ์˜ 8080์œผ๋กœ ์ „๋‹ฌํ•œ๋‹ค. 80์œผ๋กœ ๋“ค์–ด์˜จ ์š”์ฒญ์€ HTTPS๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ๋œ๋‹ค.
  • ์ธ์ฆ์„œ๋Š” Let's Encrypt๋ฅผ ์“ฐ๋ฉฐ, ๋„๋ฉ”์ธ ์—†์ด IP ์ฃผ์†Œ(52.79.118.19)๋กœ ๋ฐœ๊ธ‰ํ–ˆ๋‹ค.
  • ํ”„๋ก์‹œ๊ฐ€ TLS๋ฅผ ๋Š๊ณ  ๋ฐฑ์—”๋“œ์—๋Š” HTTP๋กœ ๋„˜๊ธฐ๊ธฐ ๋•Œ๋ฌธ์—, ์Šคํ”„๋ง์ด ์›๋ž˜ ํ”„๋กœํ† ์ฝœ์„ ์ธ์‹ํ•˜๋„๋ก ์•„๋ž˜ ์„ค์ •์ด ํ•„์š”ํ•˜๋‹ค. ์—†์œผ๋ฉด Secure ์ฟ ํ‚ค๋‚˜ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์ฃผ์†Œ๊ฐ€ http://๋กœ ๋‚˜๊ฐ„๋‹ค.
server:
  forward-headers-strategy: framework
  • ์™ธ๋ถ€ ์ ‘์†์ด ๋˜๋ ค๋ฉด EC2 ๋ณด์•ˆ ๊ทธ๋ฃน๊ณผ ์„œ๋ฒ„ ๋ฐฉํ™”๋ฒฝ(ufw)์— 80ยท443์ด ์—ด๋ ค ์žˆ์–ด์•ผ ํ•œ๋‹ค.

ํŒ€ ์ปจ๋ฒค์…˜

๋ธŒ๋žœ์น˜ ์ „๋žต

๋ธŒ๋žœ์น˜ ์šฉ๋„
main ๋ฐฐํฌ
develop ํ†ตํ•ฉ. PR์€ ์—ฌ๊ธฐ๋กœ ๋ณด๋‚ธ๋‹ค
{๋ผ๋ฒจ}/#{์ด์Šˆ๋ฒˆํ˜ธ} ์ž‘์—… ๋ธŒ๋žœ์น˜. ์˜ˆ) Feat/#3
  • ์ด์Šˆ ํ•˜๋‚˜๋‹น ๋ธŒ๋žœ์น˜ ํ•˜๋‚˜.
  • ์ž‘์—… ๋ธŒ๋žœ์น˜๋Š” develop์—์„œ ๋”ฐ๊ณ , PR๋„ develop์œผ๋กœ ๋ณด๋‚ธ๋‹ค.
  • ์ด์Šˆ์™€ ๋ธŒ๋žœ์น˜๋Š” ๋‹ด๋‹น์ž๊ฐ€ ์ง์ ‘ ๋งŒ๋“ ๋‹ค.

์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€

[{๋ผ๋ฒจ}/#{์ด์Šˆ๋ฒˆํ˜ธ}] {๋ผ๋ฒจ}: {ํ•œ๊ธ€ ์„ค๋ช…}

์˜ˆ์‹œ:

[Feat/#3] Feat: ์‹ค์ธก ์น˜์ˆ˜ ํŒŒ์„œ ๊ตฌํ˜„
[Test/#3] Test: ์ฐฉ์šฉ ๋ฐฉ์‹ ๋ถ„๋ฅ˜๊ธฐ ํ…Œ์ŠคํŠธ ์ถ”๊ฐ€
[Fix/#7] Fix: ๊ธฐ์ค€ ์ด๋ฏธ์ง€ ์ €์žฅ ์‹œ storage key ์ค‘๋ณต ๋ฌธ์ œ ์ˆ˜์ •
๋ผ๋ฒจ ์˜๋ฏธ
Feat ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ
Fix ๋ฒ„๊ทธ ์ˆ˜์ •
Refactor ๊ธฐ๋Šฅ ๋ณ€ํ™” ์—†๋Š” ๊ตฌ์กฐ ๊ฐœ์„ 
Design UI/์‘๋‹ต ํ˜•ํƒœ ๋ณ€๊ฒฝ
Chore ๋นŒ๋“œยท์˜์กด์„ฑ
Setting ํ™˜๊ฒฝ ์„ค์ •
Comment ์ฃผ์„ยท๋กœ๊ทธ
Docs ๋ฌธ์„œ
Test ํ…Œ์ŠคํŠธ
  • ํ•˜๋‚˜์˜ ๊ธฐ๋Šฅ์ด ๋๋‚  ๋•Œ๋งˆ๋‹ค ์ปค๋ฐ‹ํ•œ๋‹ค. ์˜๋ฏธ ๋‹จ์œ„๋กœ ์ž˜๊ฒŒ ์ชผ๊ฐœ๋˜, ๊ฐ ์ปค๋ฐ‹์€ ๋นŒ๋“œ๊ฐ€ ๋˜์–ด์•ผ ํ•œ๋‹ค.

์ด์Šˆ ์–‘์‹

์ œ๋ชฉ: [{๋ผ๋ฒจ}] {๋ฒˆํ˜ธ} {๋ชฉํ‘œ}

## ๋ชฉ์ 

## ๋ฒ”์œ„
### ํฌํ•จ:
### ์ œ์™ธ:

## ์ž‘์—… ๋‚ด์šฉ
- [ ]

## ์™„๋ฃŒ ์กฐ๊ฑด
- [ ]

์Šฌ๋ผ์ด์Šค ์ด์Šˆ(#3~#12)๋Š” PM์ด ์ด๋ฏธ ์ž‘์„ฑํ•ด ๋‘์—ˆ๋‹ค. ์ƒˆ๋กœ ๋งŒ๋“œ๋Š” ๊ฒƒ์€ ์ด ์–‘์‹์„ ๋”ฐ๋ฅธ๋‹ค.

PR ์–‘์‹

์ œ๋ชฉ: [{๋ผ๋ฒจ}/{Issue ๋ฒˆํ˜ธ}] ์ž‘์—… ์š”์•ฝ

## ์—ฐ๊ด€ Issue
- closes #{Issue ๋ฒˆํ˜ธ}

## ์š”์•ฝ
์ด PR์—์„œ ๋ณ€๊ฒฝํ•œ ๋‚ด์šฉ์„ ๊ฐ„๋‹จํžˆ ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

## ๋ณ€๊ฒฝ ์‚ฌํ•ญ
- ๋ณ€๊ฒฝ ์‚ฌํ•ญ 1
- ๋ณ€๊ฒฝ ์‚ฌํ•ญ 2

## ๋ฒ”์œ„
### ํฌํ•จ:
- ์˜ˆ์‹œ 1
### ์ œ์™ธ:
- ์˜ˆ์‹œ 1

## ์Šคํฌ๋ฆฐ์ƒท / ๋ฏธ๋ฆฌ ๋ณด๊ธฐ
- before/after ์ด๋ฏธ์ง€ ๋˜๋Š” ์บก์ฒ˜ ์ฒจ๋ถ€
  • ์ž„์˜ ์„น์…˜(API ๋ช…์„ธ, ๋ฆฌ๋ทฐ ํฌ์ธํŠธ ๋“ฑ)์„ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๋Š”๋‹ค. ํ•„์š”ํ•˜๋ฉด ์š”์•ฝยท๋ณ€๊ฒฝ ์‚ฌํ•ญ ์•ˆ์— ๋…น์ธ๋‹ค.
  • ๋จธ์ง€ ์ „ ํŒ€์› 1๋ช… ์ด์ƒ ๋ฆฌ๋ทฐ๋ฅผ ๋ฐ›๋Š”๋‹ค.

์ฝ”๋“œ ์ปจ๋ฒค์…˜

๊ณ„์ธต

Controller  ์š”์ฒญ/์‘๋‹ต ๋ณ€ํ™˜๋งŒ. ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง ๊ธˆ์ง€
Service     ๋กœ์ง ยท ํŠธ๋žœ์žญ์…˜
Repository  DB ์ ‘๊ทผ

์‘๋‹ต๊ณผ ์˜ˆ์™ธ

  • ๋ชจ๋“  ์‘๋‹ต์€ RsData<T>๋กœ ๊ฐ์‹ผ๋‹ค.
  • ์˜ˆ์™ธ๋Š” CustomException + ErrorCode๋ฅผ ๋˜์ง€๊ณ  GlobalExceptionHandler๊ฐ€ ์ฒ˜๋ฆฌํ•œ๋‹ค.
  • ๋‚ด๋ถ€ ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋ฅผ ๊ทธ๋Œ€๋กœ ์‘๋‹ต์— ๋…ธ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค.
  • ํŽ˜์ด์ง• ์‘๋‹ต์€ PageResponseDTO<T>๋กœ ๊ฐ์‹ผ๋‹ค. Spring์˜ Page๋ฅผ ์ง์ ‘ ๋…ธ์ถœํ•˜์ง€ ์•Š๋Š”๋‹ค.

์—”ํ‹ฐํ‹ฐ์™€ DTO

  • ์—”ํ‹ฐํ‹ฐ โ†’ DTO ๋ณ€ํ™˜์€ DTO์˜ ์ •์  ๋ฉ”์„œ๋“œ from()
  • DTO โ†’ ์—”ํ‹ฐํ‹ฐ ๋ณ€ํ™˜์€ toEntity()
  • ์—”ํ‹ฐํ‹ฐ์— @Setter๋ฅผ ๋ถ™์ด์ง€ ์•Š๋Š”๋‹ค. ์˜๋ฏธ ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋กœ ์ƒํƒœ๋ฅผ ๋ฐ”๊พผ๋‹ค (updateBodyInfo(), replaceStorageKey() ๋“ฑ)
  • ๊ณตํ†ต ์‹œ๊ฐ„ ์ปฌ๋Ÿผ์€ BaseTimeEntity ์ƒ์†

ํŠธ๋žœ์žญ์…˜

  • ์กฐํšŒ ์ „์šฉ ์„œ๋น„์Šค ๋ฉ”์„œ๋“œ๋Š” @Transactional(readOnly = true)
  • LAZY ์—ฐ๊ด€์„ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๊ฑด๋“œ๋ฆฌ๋ฉด 500์ด ๋‚œ๋‹ค. DTO ๋ณ€ํ™˜์€ ์„œ๋น„์Šค ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ๋๋‚ธ๋‹ค.

์ฃผ์„๊ณผ ๋ฌธ์„œ

  • ๋ชจ๋“  ์ฝ”๋“œ์— ํ•œ๊ธ€ ์ฃผ์„์„ ๋‹จ๋‹ค. ๊ฐ ํด๋ž˜์Šคยท๋ฉ”์„œ๋“œ๊ฐ€ ๋ฌด์Šจ ์—ญํ• ์ธ์ง€ ์ฝ๊ณ  ๋ฐ”๋กœ ์•Œ ์ˆ˜ ์žˆ๊ฒŒ.
  • ์ƒˆ ์—”๋“œํฌ์ธํŠธ์—๋Š” Swagger ์–ด๋…ธํ…Œ์ด์…˜์„ ์ „๋ถ€ ๋ถ™์ธ๋‹ค (@Tag, @Operation, @ApiResponses). ๋‚˜์ค‘์— ๋ชฐ์•„์„œ ํ•˜๋ฉด ์•ˆ ๋ถ™๋Š”๋‹ค.

ํŒŒ๋ผ๋ฏธํ„ฐ

  • @PathVariable, @RequestParam์— ์ด๋ฆ„์„ ๋ฐ˜๋“œ์‹œ ๋ช…์‹œํ•œ๋‹ค.
    // O
    @PathVariable("id") Long id
    // X โ€” IntelliJ ์‹คํ–‰ ์‹œ 500
    @PathVariable Long id

์šฉ์–ด

CONTEXT.md์˜ ์šฉ์–ด๋ฅผ ๋”ฐ๋ฅธ๋‹ค. ๋ณ€์ˆ˜๋ช…ยทํด๋ž˜์Šค๋ช…ยท์ฃผ์„ยท์ปค๋ฐ‹ ๋ฉ”์‹œ์ง€ ์ „๋ถ€ ํ•ด๋‹น๋œ๋‹ค.

์“ธ ๊ฒƒ ์“ฐ์ง€ ๋ง ๊ฒƒ
์›๋ณธ ์‚ฌ์ง„ Photo ์—…๋กœ๋“œ ์ด๋ฏธ์ง€, ์œ ์ € ์ด๋ฏธ์ง€
๊ธฐ์ค€ ์ด๋ฏธ์ง€ BaseImage ์ •์ž์„ธ ์ด๋ฏธ์ง€, ๋ฒ ์ด์Šค ์ด๋ฏธ์ง€, ๋ˆ„๋ผ ์ด๋ฏธ์ง€
์ฐฉ์šฉ ์ด๋ฏธ์ง€ WornImage ๊ฒฐ๊ณผ ์ด๋ฏธ์ง€, ํ•ฉ์„ฑ ์ด๋ฏธ์ง€, ๋ฃฉ
์ œํ’ˆ ์ปท ProductCut ์ƒํ’ˆ ์ด๋ฏธ์ง€
์‹ค์ธก ์น˜์ˆ˜ Dimensions ์‚ฌ์ด์ฆˆ
์ฐฉ์šฉ ๋ฐฉ์‹ WearType ๊ฐ€๋ฐฉ ํƒ€์ž…, ์นดํ…Œ๊ณ ๋ฆฌ
์‚ฌ์ง„์ฒฉ ๊ฐค๋Ÿฌ๋ฆฌ, ์•จ๋ฒ”

ํŠนํžˆ ์ƒ์„ฑ ํ•ฉ์„ฑ๊ณผ ์˜ค๋ ค๋ถ™์ด๊ธฐ๋ฅผ "ํ•ฉ์„ฑ"์œผ๋กœ ๋ญ‰๋šฑ๊ทธ๋ฆฌ์ง€ ์•Š๋Š”๋‹ค. ์–ด๋А ์ชฝ์ธ์ง€ ์•Œ ์ˆ˜ ์—†์–ด์ง„๋‹ค.


์ž‘์—… ๋ถ„๋ฐฐ

ํŠธ๋ž™ ๋‹ด๋‹น ๋‹ด๋‹นํ•œ ์ž‘์—…
A. ํšŒ์› ยท ์ œํ’ˆ ยท ๋ฐฐํฌ ๊น€๋™ํ™˜ ์Šฌ๋ผ์ด์Šค 2ยท3(#4ยท#5), ์Šฌ๋ผ์ด์Šค 1(#3โ†’#24), ์ œํ’ˆ ์ƒ์„ธยท๋ชฉ๋ก ํŽ˜์ด์ง•(#34), ์ฐฉ์šฉ ๋ฐฉ์‹ ๋…ธ์ถœ(#37), ์‚ฌ์ง„ ์‚ญ์ œ(#38), Docker ๋ฐฐํฌ ํ™˜๊ฒฝ(#31)
B. ์ด๋ฏธ์ง€ ์ƒ์„ฑ ์ธํ”„๋ผ ์ตœ์ •ํ›ˆ ์Šฌ๋ผ์ด์Šค 4ยท5ยท6(#6ยท#7ยท#8), ImageStorageยทImageGenerationGateway ์ถ”์ƒํ™”, GeminiยทOpenAI ๊ฒŒ์ดํŠธ์›จ์ด, AWS ์ธํ”„๋ผ
C. ์ด๋ฏธ์ง€ ํŒŒ์ดํ”„๋ผ์ธ ์œ ์†Œ์˜ ERDยท์—”ํ‹ฐํ‹ฐ(#13), ํฌ๋กค๋ง ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘, ์Šฌ๋ผ์ด์Šค 7ยท8ยท9(#9ยท#10ยท#11), ๊ธฐ์ค€ ์ด๋ฏธ์ง€ยท์ฐฉ์šฉ ์ด๋ฏธ์ง€ ์กฐํšŒ API(#41ยท#43ยท#45)
  • A/B/C๊ฐ€ ๋งˆ์ฃผ์น˜๋Š” ์ ‘์ ์€ ImageGenerationGateway / ImageStorage ์ธํ„ฐํŽ˜์ด์Šค ์‹œ๊ทธ๋‹ˆ์ฒ˜๋ฟ์ด๋‹ค. Phase 0์—์„œ ํ™•์ •ํ•œ ๋’ค ๋ฐ”๊พธ์ง€ ์•Š๋Š”๋‹ค.
  • #10, #11์€ ๊ฐ™์€ ํŒŒ์ผ(domain/worn)์„ ๊ฑด๋“œ๋ฆฌ๋ฏ€๋กœ ๋™์‹œ์— ์ง„ํ–‰ํ•˜์ง€ ์•Š๊ณ  ์ˆœ์„œ๋Œ€๋กœ ์ฒ˜๋ฆฌํ–ˆ๋‹ค.

About

No description, website, or topics provided.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages