Skip to content

Recur-SM/Back-End

Folders and files

NameName
Last commit message
Last commit date

Latest commit

ย 

History

119 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

Repository files navigation

์„ค์Šคํ„ฐ๋”” (Study Coaching Platform) โ€” Backend

๋ฉ˜ํ† ๊ฐ€ ๋ฉ˜ํ‹ฐ์—๊ฒŒ ๊ณผ์ œ๋ฅผ ๋ถ€์—ฌํ•˜๊ณ , ํ”Œ๋ž˜๋„ˆ๋ฅผ ๊ฒ€ํ† ํ•˜๋ฉฐ ํ”ผ๋“œ๋ฐฑ์„ ์ œ๊ณตํ•˜๋Š” ํ•™์Šต ์ฝ”์นญ ํ”Œ๋žซํผ์˜ ๋ฐฑ์—”๋“œ์ž…๋‹ˆ๋‹ค.

  • Stack: Spring Boot 3.5, Java 21, MySQL 8.4, JPA(Hibernate), Spring Security
  • Infra: AWS S3(ํ˜ธํ™˜ ์Šคํ† ๋ฆฌ์ง€), Docker Compose, Railway ๋ฐฐํฌ

ํ˜„์žฌ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋Š” Railway์— ๋ฐฐํฌ๋˜์–ด ์žˆ์œผ๋ฉฐ, Swagger UI์—์„œ API ๋ช…์„ธ๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ญ๋ชฉ URL
Production Server https://back-end-production-4beb.up.railway.app
Swagger UI https://back-end-production-4beb.up.railway.app/swagger-ui/index.html
Railway

๋ชฉ์ฐจ


์•„ํ‚คํ…์ฒ˜ ๊ฐœ์š”

๋„๋ฉ”์ธ ์ฃผ๋„ ํŒจํ‚ค์ง€ ๊ตฌ์กฐ๋กœ, ๊ฐ ๋„๋ฉ”์ธ์ด controller / dto / entity / repository / service๋ฅผ ์ž์ฒด์ ์œผ๋กœ ๋ณด์œ ํ•ฉ๋‹ˆ๋‹ค.

com.seolstudy.backend
โ”œโ”€โ”€ domain
โ”‚   โ”œโ”€โ”€ auth        # JWT ์ธ์ฆ (ํšŒ์›๊ฐ€์ž…/๋กœ๊ทธ์ธ/ํ† ํฐ ์žฌ๋ฐœ๊ธ‰/๋กœ๊ทธ์•„์›ƒ)
โ”‚   โ”œโ”€โ”€ user        # ์‚ฌ์šฉ์ž + ์—ญํ• (MENTOR/MENTEE)
โ”‚   โ”œโ”€โ”€ mentoring   # ๋ฉ˜ํ† -๋ฉ˜ํ‹ฐ ๋งค์นญ ๊ด€๋ฆฌ
โ”‚   โ”œโ”€โ”€ task        # ๊ณผ์ œ ๋ถ€์—ฌ(๋ฉ˜ํ† ) / ์ œ์ถœ(๋ฉ˜ํ‹ฐ) + ์™„๋ฃŒ ์ธ์ฆ
โ”‚   โ”œโ”€โ”€ planner     # ์ผ์ž๋ณ„ ํ”Œ๋ž˜๋„ˆ + ๋ฉ˜ํ†  ์ฝ”๋ฉ˜ํŠธ
โ”‚   โ”œโ”€โ”€ feedback    # ์ผ์ž๋ณ„ ๋ฉ˜ํ†  ํ”ผ๋“œ๋ฐฑ
โ”‚   โ””โ”€โ”€ subject     # ๊ณผ๋ชฉ (์‹œ์ž‘ ์‹œ ์‹œ๋“œ)
โ””โ”€โ”€ global
    โ”œโ”€โ”€ config      # Security, S3, Swagger, Web, Cors
    โ”œโ”€โ”€ security    # JwtTokenProvider, JwtAuthenticationFilter, UserDetails
    โ”œโ”€โ”€ payload     # CommonResponse + Error/Success Status enum
    โ”œโ”€โ”€ exception   # GeneralException + GlobalExceptionHandler
    โ””โ”€โ”€ storage     # FileStorage ์ถ”์ƒํ™” (S3 / Local)

๊ธฐ์ˆ ์  ์˜์‚ฌ๊ฒฐ์ • (Highlights)

1. ํŒŒ์ผ ์ €์žฅ์†Œ ์ถ”์ƒํ™” โ€” ์ „๋žต + ์–ด๋Œ‘ํ„ฐ ํŒจํ„ด

์—…๋กœ๋“œ ๋กœ์ง์„ FileStorage ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ถ”์ƒํ™”ํ•˜๊ณ , Spring @Profile๋กœ ๊ตฌํ˜„์ฒด๋ฅผ ์ž๋™ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ๋น„์ฆˆ๋‹ˆ์Šค ์„œ๋น„์Šค๋Š” ๊ตฌํ˜„์ฒด(S3/๋กœ์ปฌ)๋ฅผ ์ „ํ˜€ ๋ชจ๋ฅธ ์ฑ„ ์ธํ„ฐํŽ˜์ด์Šค์—๋งŒ ์˜์กดํ•ฉ๋‹ˆ๋‹ค.

        [PlannerService / TaskService]   โ† ์ธํ„ฐํŽ˜์ด์Šค์—๋งŒ ์˜์กด
                     โ”‚
                     โ–ผ
            โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
            โ”‚   FileStorage    โ”‚  (interface)
            โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
                  โ–ฒ        โ–ฒ
        @Profile("!local") โ”‚ @Profile("local")
        โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”  โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
        โ”‚S3FileStorageโ”‚  โ”‚LocalFileStorageAdapterโ”‚โ”€โ”€์œ„์ž„โ”€โ”€โ–ถ LocalFileStorage
        โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜  โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜           (์‹ค์ œ ํŒŒ์ผ I/O)

ํ•ต์‹ฌ ์„ค๊ณ„

ํ•ญ๋ชฉ ๋‚ด์šฉ ๊ทผ๊ฑฐ
์ „๋žต ํŒจํ„ด + DIP ํ”„๋กœํ•„์ด ์ฃผ์ž… ๋นˆ์„ ๊ฒฐ์ • โ†’ ์„œ๋น„์Šค ์ฝ”๋“œ ๋ณ€๊ฒฝ 0์ค„๋กœ ์ €์žฅ์†Œ ๊ต์ฒด global/storage/FileStorage.java
์–ด๋Œ‘ํ„ฐ ํŒจํ„ด ์‹œ๊ทธ๋‹ˆ์ฒ˜๊ฐ€ ๋‹ค๋ฅธ ๊ธฐ์กด LocalFileStorage๋ฅผ ์ธํ„ฐํŽ˜์ด์Šค์— ๋งž์ถฐ ์žฌ์‚ฌ์šฉ LocalFileStorageAdapter.java
2๋‹จ๊ณ„ ์ €์žฅ/์กฐํšŒ ์—…๋กœ๋“œ ์‹œ key๋งŒ DB ์ €์žฅ โ†’ ์กฐํšŒ ์‹œ์ ์— ์ ‘๊ทผ URL ์ƒ์„ฑ createPresignedGetUrl()
Presigned URL ๋น„๊ณต๊ฐœ ๋ฒ„ํ‚ท + ๋งŒ๋ฃŒ์‹œ๊ฐ„ ์žˆ๋Š” ์ž„์‹œ URL๋งŒ ๋…ธ์ถœ S3FileStorage.java:129-147
๋กœ์ปฌ ์™ธ๋ถ€์˜์กด์„ฑ ์ œ๊ฑฐ local ํ”„๋กœํ•„์€ S3 ๋นˆ ์ž์ฒด๋ฅผ ๋„์šฐ์ง€ ์•Š์Œ S3Config.java @Profile("!local")
์•ˆ์ „ํ•œ ๋ถ€๊ฐ€์ž‘์—… ๊ฐ์ฒด ์‚ญ์ œ ์‹คํŒจ ์‹œ throw ๋Œ€์‹  log.warn โ†’ ๋ฉ”์ธ ํŠธ๋žœ์žญ์…˜ ๋ณดํ˜ธ S3FileStorage.java:124-126

์„ค๊ณ„ ์˜๋„: ์™ธ๋ถ€ ์Šคํ† ๋ฆฌ์ง€(S3)๋ฅผ ์ธํ„ฐํŽ˜์ด์Šค ๋’ค๋กœ ์ˆจ๊ฒจ, ๋กœ์ปฌ์—์„œ๋Š” ๋””์Šคํฌยท์šด์˜์—์„œ๋Š” S3๋ฅผ ๋™์ผํ•œ ์ฝ”๋“œ๋กœ ์‚ฌ์šฉํ•œ๋‹ค. S3๋Š” ๋ฒ„ํ‚ท์„ ๋น„๊ณต๊ฐœ๋กœ ๋‘๊ณ  Presigned URL๋กœ๋งŒ ์ ‘๊ทผ์„ ํ—ˆ์šฉํ•ด ๋…ธ์ถœ ๋ฒ”์œ„๋ฅผ ์ œํ•œํ–ˆ๋‹ค.


2. ํ”„๋กœํ•„ ๊ธฐ๋ฐ˜ ํ™˜๊ฒฝ ๋ถ„๋ฆฌ (local / railway)

"๊ณตํ†ต + ํ”„๋กœํ•„๋ณ„ ์˜ค๋ฒ„๋ผ์ด๋“œ" 3๋‹จ ๊ตฌ์„ฑ์œผ๋กœ, ๋กœ์ปฌ์€ ์ง„์ž…์žฅ๋ฒฝ์„ ๋‚ฎ์ถ”๊ณ  ์šด์˜์€ ์„ค์ • ๋ˆ„๋ฝ์„ ์ฐจ๋‹จํ•ฉ๋‹ˆ๋‹ค.

application.yml          # ๊ณตํ†ต๊ฐ’ + ๊ธฐ๋ณธ ํ”„๋กœํ•„(default: local)
application-local.yml    # ๋กœ์ปฌ: DB ํ•˜๋“œ์ฝ”๋”ฉ + JWT secret ๊ธฐ๋ณธ๊ฐ’ ์ œ๊ณต
application-railway.yml  # ์šด์˜: DB/secret ์ „๋ถ€ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ฃผ์ž…
ํ™˜๊ฒฝ DB ์„ค์ • JWT Secret
local localhost:3306 ํ•˜๋“œ์ฝ”๋”ฉ (docker-compose์™€ ์ง) ๊ธฐ๋ณธ๊ฐ’ ์ œ๊ณต โ†’ ๋ฌด์„ค์ • ์ฆ‰์‹œ ์‹คํ–‰
railway ${MYSQLHOST} ๋“ฑ ํ™˜๊ฒฝ๋ณ€์ˆ˜ ์ฃผ์ž… ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ฐ•์ œ โ†’ ๋ฏธ์„ค์ • ์‹œ ๋ถ€ํŒ… ์‹คํŒจ

์„ค๊ณ„ ์˜๋„: ๋กœ์ปฌ์€ ๊ธฐ๋ณธ๊ฐ’์œผ๋กœ ํด๋ก  ํ›„ ๋ฐ”๋กœ ์‹คํ–‰ ๊ฐ€๋Šฅํ•˜๊ฒŒ, ์šด์˜์€ ๋น„๋ฐ€ํ‚ค๋ฅผ ๊ฐ•์ œํ•ด ์„ค์ • ๋ˆ„๋ฝ ์‹œ ๋ถ€ํŒ…์ด ์‹คํŒจํ•˜๋„๋ก ๋ถ„๋ฆฌํ–ˆ๋‹ค. DBยทJWTยทS3 ์ž๊ฒฉ์ฆ๋ช…์€ ์ฝ”๋“œ์— ํ•˜๋“œ์ฝ”๋”ฉํ•˜์ง€ ์•Š๊ณ  ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ๋ถ„๋ฆฌํ•ด ์ฃผ์ž…ํ•œ๋‹ค (์šด์˜: Railway ํ™˜๊ฒฝ๋ณ€์ˆ˜ / ๋กœ์ปฌ: ์‹คํ–‰ ๊ตฌ์„ฑ์˜ ํ™˜๊ฒฝ๋ณ€์ˆ˜).


3. JWT ์ธ์ฆ + Refresh Token ํšŒ์ „

Access(1์‹œ๊ฐ„) + Refresh(7์ผ) ํ† ํฐ ํŽ˜์–ด ๊ตฌ์กฐ์ด๋ฉฐ, Refresh Token์„ DB์— ์ €์žฅํ•ด ์žฌ๋ฐœ๊ธ‰ ํšŒ์ „๊ณผ ๋กœ๊ทธ์•„์›ƒ ๋ฌดํšจํ™”๋ฅผ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค.

  • JwtTokenProvider โ€” HMAC-SHA256(Keys.hmacShaKeyFor, 256bit), userId/username/role์„ claim์œผ๋กœ ๋ฐœ๊ธ‰ยท์ถ”์ถœ
  • RefreshToken ์—”ํ‹ฐํ‹ฐ โ€” User์™€ 1:1, ์žฌ๋ฐœ๊ธ‰ ์‹œ updateToken()์œผ๋กœ ํšŒ์ „, ๋กœ๊ทธ์•„์›ƒ ์‹œ DB์—์„œ ์‚ญ์ œํ•ด ์žฌ์‚ฌ์šฉ ์ฐจ๋‹จ
  • JwtAuthenticationFilter(OncePerRequestFilter) โ€” Authorization ํ—ค๋”์˜ Bearer ํ† ํฐ ๊ฒ€์ฆ ํ›„ SecurityContext ์„ค์ •
  • SessionCreationPolicy.STATELESS โ€” ์™„์ „ ๋ฌด์ƒํƒœ
  • BCryptPasswordEncoder โ€” ๋น„๋ฐ€๋ฒˆํ˜ธ ๋‹จ๋ฐฉํ–ฅ ํ•ด์‹ฑ

4. ๋ฉ”์„œ๋“œ ๋‹จ์œ„ ์—ญํ•  ๊ธฐ๋ฐ˜ ์ ‘๊ทผ์ œ์–ด (RBAC)

@EnableMethodSecurity๋ฅผ ์ผœ๊ณ  ์ปจํŠธ๋กค๋Ÿฌ ๋ฉ”์„œ๋“œ์— ๊ถŒํ•œ์„ ์„ ์–ธํ•ด, URL ํŒจํ„ด์ด ์•„๋‹Œ ๋น„์ฆˆ๋‹ˆ์Šค ๋ฉ”์„œ๋“œ ๋‹จ์œ„๋กœ ์ธ๊ฐ€๋ฅผ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

@PreAuthorize("hasRole('MENTOR')")             // ๊ณผ์ œ ๋ถ€์—ฌ, ํ”ผ๋“œ๋ฐฑ ์ž‘์„ฑ
@PreAuthorize("hasRole('MENTEE')")             // ํ”Œ๋ž˜๋„ˆ/๊ณผ์ œ ์ œ์ถœ
@PreAuthorize("hasAnyRole('MENTOR','MENTEE')") // ๊ณตํ†ต ์กฐํšŒ

์—ญํ• ์€ UserRole enum(MENTOR/MENTEE)์œผ๋กœ ๋ชจ๋ธ๋งํ•˜๊ณ  EnumType.STRING์œผ๋กœ ์ €์žฅํ•ฉ๋‹ˆ๋‹ค.


5. ํ†ต์ผ๋œ API ์‘๋‹ต & ์ค‘์•™ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

๋ชจ๋“  ์‘๋‹ต์„ CommonResponse<T> ํ•œ ๊ฐ€์ง€ ํฌ๋งท์œผ๋กœ ํ†ต์ผํ•˜๊ณ , ์˜ˆ์™ธ๋ฅผ ๋‹จ์ผ ํ•ธ๋“ค๋Ÿฌ์—์„œ ์ฒ˜๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

{ "isSuccess": true, "code": "COMMON_200", "message": "์„ฑ๊ณต", "result": { } }
  • CommonResponse<T> โ€” ์ œ๋„ค๋ฆญ ๋ž˜ํผ, @JsonInclude(NON_NULL)๋กœ ์—๋Ÿฌ ์‹œ result ์ƒ๋žต
  • ErrorStatus enum โ€” ๋„๋ฉ”์ธ๋ณ„ 40์—ฌ ๊ฐœ ์—๋Ÿฌ ์ฝ”๋“œ (AUTH_4011, TASK_4001 โ€ฆ), HTTP ์ƒํƒœ/์ฝ”๋“œ/๋ฉ”์‹œ์ง€ ์ผ๊ด„ ์ •์˜
  • GlobalExceptionHandler(@RestControllerAdvice) โ€” ๋น„์ฆˆ๋‹ˆ์Šค ์˜ˆ์™ธ / ๊ฒ€์ฆ ์‹คํŒจ(ํ•„๋“œ๋ณ„ ์ƒ์„ธ) / ํŒŒ์ผ ์—…๋กœ๋“œ ์ดˆ๊ณผ / ์ธ์ฆยท์ธ๊ฐ€ ์˜ˆ์™ธ / ํด๋ฐฑ์„ ํ•œ ๊ณณ์—์„œ ์ฒ˜๋ฆฌ

๊ฐ€์น˜: ํ”„๋ก ํŠธ์—”๋“œ๊ฐ€ ํ•ญ์ƒ ๊ฐ™์€ ๊ตฌ์กฐ๋กœ ์‘๋‹ต์„ ํŒŒ์‹ฑํ•  ์ˆ˜ ์žˆ๊ณ , ์—๋Ÿฌ ์ผ€์ด์Šค๊ฐ€ ์ฝ”๋“œ๋กœ ํ‘œ์ค€ํ™”๋˜์–ด ํ˜‘์—…ยท๋””๋ฒ„๊น… ํšจ์œจ์ด ๋†’๋‹ค.


๊ธฐํƒ€ ์„ค๊ณ„ ํฌ์ธํŠธ

ํฌ์ธํŠธ ๊ทผ๊ฑฐ
N+1 ๋ฐฉ์ง€ ์›”๋ณ„ ๊ณผ์ œ ์กฐํšŒ์—์„œ JOIN FETCH t.subject (TaskRepository)
ํŠธ๋žœ์žญ์…˜ ์ „๋žต ๊ธฐ๋ณธ @Transactional(readOnly = true), ์“ฐ๊ธฐ ๋ฉ”์„œ๋“œ๋งŒ ์žฌ์ •์˜
์ง€์—ฐ ๋กœ๋”ฉ ๋ชจ๋“  ์—ฐ๊ด€๊ด€๊ณ„ FetchType.LAZY
์ƒ์„ฑ์ž ์ฃผ์ž… ์ผ๊ด€ @RequiredArgsConstructor + final ํ•„๋“œ ์ „์šฉ (ํ•„๋“œ ์ฃผ์ž… ๋ฏธ์‚ฌ์šฉ)
DTO ๋งคํ•‘ ์ปจ๋ฒค์…˜ ์ •์  ํŒฉํ† ๋ฆฌ from() / of()๋กœ ๋งคํ•‘ ๋กœ์ง ์ค‘์•™ํ™”
์ž…๋ ฅ ๊ฒ€์ฆ Jakarta Validation(@NotBlank, @Size) + ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ ์ปค์Šคํ…€ ๊ฒ€์ฆ
์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ ์‹œ๋”ฉ SubjectInitializer(ApplicationRunner)๊ฐ€ ์ค‘๋ณต ์ฒดํฌ ํ›„ ๊ณผ๋ชฉ ์‹œ๋“œ
์š”์ฒญ ๋กœ๊น… RequestLoggingFilter๋กœ method/URI/status/์†Œ์š”์‹œ๊ฐ„(ms) ๊ธฐ๋ก
ํ…Œ์ŠคํŠธ Mockito BDD(given/when/then), ํ•œ๊ธ€ @DisplayName, H2 ์ธ๋ฉ”๋ชจ๋ฆฌ
API ๋ฌธ์„œํ™” Swagger(OpenAPI 3.0) + JWT Bearer SecurityScheme

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

# 1) ๋กœ์ปฌ MySQL ๊ธฐ๋™ (port 3306, db=app / user=app / pw=app1234)
docker compose up -d

# 2) ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ (๊ธฐ๋ณธ ํ”„๋กœํ•„: local)
./gradlew bootRun

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

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages