[Spring] Spring Boot로 ToDoApp만들기(투두 앱) (2)

728x90

TodoApiController

  • 투두 앱의 CRUD 처리를 담당하는 컨트롤러 작성(설계 단계)
  • Reqeust 에는 필요한 Bodyt 설계 -> Valid 작업들어가기 때문에 핸들러도 작성해준다.
@RestController
@RequestMapping("/api/todo")
class TodoApiController {

    // R
    @GetMapping(path = [""])
    fun read(@RequestParam(required = false) index: Int?) { // 없으면 전체조회, 있으면 단건 조회, required -> 필수값 아닌지 정하기, Optional이므로 Valid할 필요 없다.

    }

    // C
    @PostMapping(path = [""])
    fun create(@Valid @RequestBody todoDto: TodoDto) {  // Body설계 -> Model , 들어오는 동시에 검증(Valid)

    }

    // U
    @PutMapping(path = [""])
    fun update(@Valid @RequestBody todoDto: TodoDto) {

    }

    // D
    @DeleteMapping(path = ["/{index}"])
    fun delete(@PathVariable(name = "index") _index: Int) {

    }

}

Model 설계(Body)

TodoDTO

// CRUD 하는데 필요한 Body 설계
data class TodoDto(

    var index: Int? = null,

    @field:NotBlank
    var title: String? = null,

    var description: String? = null,

    @field:NotBlank
    // yyyy-MM-dd HH:mm:ss
    var schedule: String? = null,

    var createdAt: LocalDateTime? = null,

    var updatedAt: LocalDateTime? = null

) {
    // TODO 이전 학습한 custom annotation으로 변경 -> Test필요
    @AssertTrue(message = "yyyy-MM-dd HH:mm:ss 포맷이 맞지 않습니다.")
    fun validSchedule(): Boolean {
        return try {
            LocalDateTime.parse(
                schedule,
                DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")
            )   // 파싱해서 성공 -> true, 실패 -> false
            true
        } catch (e: Exception) {
            false
        }
    }
}

ErrorResponse

/*
{
    "result_code" : "OK",

    "http_status" : "400",
    "http_method" : "",
    "message" : "요청이 잘못 되었습니다.",
    "path" : "/api/exception/hello",
    "timestamp" : "2020-10-02T13:00:00",
    "error" : [
        {
            "field" : "_name",
            "message" : "5글자 이상이여야 합니다.",
            "value" : ""
        }
    ]
}
*/
data class ErrorResponse(

    @JsonProperty("reseult_code")   // -> snake_case 로 변경
    var resultCode: String? = null,

    @JsonProperty("http_status")
    var httpStatus: String? = null,

    @JsonProperty("http_method")
    var httpMethod: String? = null,

    var message: String? = null,

    var path: String? = null,

    var timestamp: LocalDateTime? = null,

    var error: MutableList<Error>? = null

)

data class Error(
    var field: String? = null,
    var message: String? = null,
    var value: Any? = null
)

Model Validation Check -> TodoDtoTest(T-D-D)

class TodoDtoTest {

    val validator = Validation.buildDefaultValidatorFactory().validator

    @Test
    fun todoDtoTest() {

        val todoDto = TodoDto().apply {
            this.title = "테스트"
            this.description = ""
            this.schedule = "2020-10-20 13:00:00"
        }

        val result = validator.validate(todoDto)

//        # 출력해서 확인
//        result.forEach {
//            println(it.propertyPath.last().name)
//            println(it.message)
//            println(it.invalidValue)
//        }

        // 에러 없는 경우 True, 있는경우 False
        Assertions.assertEquals(true, result.isEmpty())
    }
}

ErrorHandler(TodoApiController)

TodoApiControllerAdvice

// TodoAPiController에서만 작동하는 Handler
@ControllerAdvice(basePackageClasses = [TodoApiController::class])
class TodoApiControllerAdvice {

    // 옵셔널 제외하고 @RequestBody만 핸들링 하면 된다
    @ExceptionHandler(MethodArgumentNotValidException::class)
    fun methodArgumentNotValidException(
        e: MethodArgumentNotValidException,
        request: HttpServletRequest
    ): ResponseEntity<ErrorResponse> {

        // 어떤 에러가 있는지 받기
        val errors = mutableListOf<Error>()

        e.bindingResult.allErrors.forEach { errorObject ->

            Error().apply {
                // 형변환
                this.field = (errorObject as FieldError).field
                this.message = errorObject.defaultMessage
                this.value = errorObject.rejectedValue
            }.apply {
                errors.add(this)
            } // for each end
        }

        val errorResponse = ErrorResponse().apply {
            this.resultCode = "FAIL"
            this.httpStatus = HttpStatus.BAD_REQUEST.value().toString()
            this.httpMethod = request.method
            this.message = ""
            this.path = request.requestURI
            this.timestamp = LocalDateTime.now()
            this.error = errors
        }

        return ResponseEntity.badRequest().body(errorResponse)
    }
}

 

 

728x90

댓글

Designed by JB FACTORY