Skip to content

Commit

Permalink
chore: Global Exception Handling (#3)
Browse files Browse the repository at this point in the history
* chore: 패키지 구조 변경

* chore: GIT_TOKEN 쓰기 권한 부족으로 인한 오토리뷰어 코드 제거

* chore: PR Template에 서브모듈 관련 Noti 추가

* feat: Global Exception Handling

* feat: member -> user 엔티티 이름 변경

* chore: CI pipeline에 빌드 테스트 누락사항 수정

* feat: 축약형 변수명 수정
  • Loading branch information
h-beeen authored Mar 15, 2024
1 parent a5a88b1 commit 6655587
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 41 deletions.
13 changes: 8 additions & 5 deletions .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
## <i>PULL REQUEST</i>

### 🎋 작업 중인 브랜치
### 🎋 Issue Ticket
-

### 🔑 주요 작업사항
-

### 🏞 (Optional) 참고 자료
- 스크린샷을 첨부해주세요.

### 관련 이슈
-

### (중요) 서브모듈이 수정되었나요?
- 기존 커밋 :
- 변경 커밋 :
- 변경 사항 :
- [] 해당 서브모듈 변경사항이 PR에 잘 반영되었나요?

### 꼭 확인해 주세요!!
-
-
33 changes: 13 additions & 20 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
@@ -1,42 +1,35 @@
name: 빌드 테스트 / 리뷰어 할당
name: 테스트 코드 및 빌드

on:
pull_request:
types: [opened, synchronize, closed]
types: [ opened, synchronize, closed ]
branches:
- 'develop'
- 'master'

jobs:
build_and_review_assign:
name: "[CI] Check Build/Testcases and Assign Reviewer"
test:
name: "[CI] Check Tests"
runs-on: ubuntu-latest

steps:
- name: (Set Up) checkout
- name: Checkout code
uses: actions/checkout@v3
with:
token: ${{ secrets.GIT_TOKEN }}
submodules: true

- name: (Set Up) Set up JDK 17
- name: Setup Java 17
uses: actions/setup-java@v3
with:
java-version: '17'
distribution: 'temurin'
distribution: 'adopt'

- name: (Set Up) Grant Execute permission for gradlew
run: chmod 777 gradlew
- name: Update Git submodules
run: git submodule update --remote --recursive

- name: (Build) Build with Gradle
id: build
run: ./gradlew test -i
- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: (Assign Reviewer)
if: steps.build.outcome == 'success'
uses: hkusu/review-assign-action@v1
with:
assignees: ${{ github.actor }}
reviewers: HyungJu, h-beeen
ready-comment: '코드 리뷰 요청합니다 🙆 <reviewers>'
merged-comment: '성공적으로 Merge 되었습니다. Shout out to <reviewers> :wink:'
- name: Test with Gradle
run: ./gradlew test
21 changes: 21 additions & 0 deletions src/main/kotlin/com/vacgom/backend/domain/member/Member.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.vacgom.backend.domain.member

import com.vacgom.backend.global.auditing.BaseEntity
import jakarta.persistence.*

@Entity
@Table(name = "t_member")
class Member(nickname: String) : BaseEntity() {

@Id
@Column(name = "member_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null

@Column(name = "nickname")
val nickname: String

init {
this.nickname = nickname
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package com.vacgom.backend.global.exception

import com.vacgom.backend.global.exception.error.BusinessException
import com.vacgom.backend.global.exception.error.ErrorCode
import com.vacgom.backend.global.exception.error.ErrorResponse
import com.vacgom.backend.global.exception.error.GlobalError
import com.vacgom.backend.global.logger.CommonLogger
import org.springframework.http.HttpStatus
import org.springframework.http.ResponseEntity
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.MissingServletRequestParameterException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.ResponseStatus
import org.springframework.web.bind.annotation.RestControllerAdvice

@RestControllerAdvice
class ApiExceptionHandler {
companion object : CommonLogger();

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MethodArgumentNotValidException::class)
fun handleMethodArgumentNotValidException(ex: MethodArgumentNotValidException): ErrorResponse? {
return ErrorResponse(ex.fieldErrors)
}

@ResponseStatus(HttpStatus.BAD_REQUEST)
@ExceptionHandler(MissingServletRequestParameterException::class)
fun missingServletRequestParameterException(): ErrorResponse? {
return ErrorResponse(GlobalError.INVALID_REQUEST_PARAM)
}

@ExceptionHandler(BusinessException::class)
fun handleBusinessException(
exception: BusinessException
): ResponseEntity<ErrorResponse?> {
logBusinessException(exception)
return convert(exception.errorCode)
}

private fun convert(
errorCode: ErrorCode
): ResponseEntity<ErrorResponse?> {
return ResponseEntity
.status(errorCode.status)
.body(ErrorResponse(errorCode))
}

private fun logBusinessException(exception: BusinessException) {
if (exception.errorCode.status.is5xxServerError) {
log.error("", exception)
} else {
log.error("Error Message = {}", exception.message)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.vacgom.backend.global.exception

import com.fasterxml.jackson.databind.ObjectMapper
import com.vacgom.backend.global.exception.error.BusinessException
import com.vacgom.backend.global.exception.error.ErrorResponse
import jakarta.servlet.FilterChain
import jakarta.servlet.ServletException
import jakarta.servlet.http.HttpServletRequest
import jakarta.servlet.http.HttpServletResponse
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.filter.OncePerRequestFilter
import java.io.IOException


@Component
class ApiExceptionHandlingFilter(
private val om: ObjectMapper
) : OncePerRequestFilter() {

@Throws(ServletException::class, IOException::class)
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
chain: FilterChain
) {
try {
chain.doFilter(request, response)
} catch (exception: BusinessException) {
setErrorResponse(response, exception)
}
}

private fun setErrorResponse(
response: HttpServletResponse,
exception: BusinessException
) = try {
response.contentType = MediaType.APPLICATION_JSON_VALUE
response.status = HttpStatus.UNAUTHORIZED.value()
om.writeValue(response.outputStream, ErrorResponse(exception.errorCode))
} catch (exception: IOException) {
throw RuntimeException(exception)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.vacgom.backend.global.exception.error


class BusinessException(
val errorCode: ErrorCode
) : RuntimeException(errorCode.message)
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.vacgom.backend.global.exception.error

import org.springframework.http.HttpStatus

interface ErrorCode {
val message: String
val status: HttpStatus
val code: String
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package com.vacgom.backend.global.exception.error

import org.springframework.validation.FieldError
import java.time.LocalDateTime

data class ErrorResponse(
val timeStamp: String = LocalDateTime.now().toString(),
val errorCode: String,
val errorMessage: String,
val details: Any? = null
) {
constructor(
errorCode: ErrorCode
) : this(
errorCode = errorCode.code,
errorMessage = errorCode.message
)

constructor(
errorCode: ErrorCode,
details: Any?
) : this(
errorCode = errorCode.code,
errorMessage = errorCode.message,
details = details
)

constructor(
fieldError: FieldError?
) : this(
errorCode = fieldError?.code ?: "",
errorMessage = fieldError?.defaultMessage ?: ""
)

constructor(fieldErrors: List<FieldError>) : this(
GlobalError.INVALID_REQUEST_PARAM,
fieldErrors.associate {
it.field to (it.defaultMessage ?: "null")
}
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.vacgom.backend.global.exception.error

import org.springframework.http.HttpStatus

enum class GlobalError(
override val message: String,
override val status: HttpStatus,
override val code: String
) : ErrorCode {
GLOBAL_NOT_FOUND("리소스가 존재하지 않습니다.", HttpStatus.NOT_FOUND, "G_001"),
INVALID_REQUEST_PARAM("요청 파라미터가 유효하지 않습니다.", HttpStatus.BAD_REQUEST, "G_002");
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.vacgom.backend.global.logger

import org.slf4j.LoggerFactory

abstract class CommonLogger {
val log = LoggerFactory.getLogger(this.javaClass)!!
}
16 changes: 0 additions & 16 deletions src/main/kotlin/com/vacgom/backend/member/domain/Member.kt

This file was deleted.

0 comments on commit 6655587

Please sign in to comment.