Skip to content

Commit

Permalink
✨feature : api 기본세팅 작업
Browse files Browse the repository at this point in the history
 - webflux 설정
 - openfeign 설정
  • Loading branch information
ParkYunHo committed Jun 22, 2023
1 parent 2153384 commit 689c220
Show file tree
Hide file tree
Showing 13 changed files with 345 additions and 2 deletions.
6 changes: 5 additions & 1 deletion api/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,10 @@ plugins {
dependencies {
implementation(project(":core"))

implementation("org.springframework.boot:spring-boot-starter-web")
// implementation("org.springframework.boot:spring-boot-starter-web")

implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.springframework.boot:spring-boot-starter-aop")

implementation("org.springframework.cloud:spring-cloud-starter-openfeign:4.0.3")
}
51 changes: 51 additions & 0 deletions api/src/main/kotlin/com/john/lotto/common/dto/BaseResponse.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package com.john.lotto.common.dto

import org.springframework.http.HttpStatus
import org.springframework.web.reactive.function.BodyInserters
import org.springframework.web.reactive.function.server.ServerResponse
import java.io.Serializable

/**
* @author yoonho
* @since 2023.06.22
*/
data class BaseResponse (
val message: String?,
val status: HttpStatus,
val data: Any?
): Serializable {
constructor(): this(message = "Success", status = HttpStatus.OK, null)
constructor(message: String?, status: HttpStatus): this(message = message, status = status, null)

fun error(status: HttpStatus, message: String) =
ServerResponse.status(status)
.bodyValue(
BaseResponse(
message = message,
status = status,
null
)
)

fun success(data: Any?) =
ServerResponse.ok().body(
BodyInserters.fromValue(
BaseResponse(
message = "Success",
status = HttpStatus.OK,
data
)
)
)

fun successNoContent() =
ServerResponse.ok().body(
BodyInserters.fromValue(
BaseResponse(
message = "Success",
status = HttpStatus.OK,
null
)
)
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.john.lotto.common.exception

/**
* @author yoonho
* @since 2023.06.22
*/
class BadRequestException: RuntimeException {
constructor(msg: String?): super(msg)
constructor(): super()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.john.lotto.common.exception

/**
* @author yoonho
* @since 2023.06.22
*/
class InternalServerException: RuntimeException {
constructor(msg: String?): super(msg)
constructor(): super()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package com.john.lotto.common.handler

import com.john.lotto.common.exception.BadRequestException
import com.john.lotto.common.exception.InternalServerException
import feign.Response
import feign.codec.ErrorDecoder
import org.slf4j.LoggerFactory
import org.springframework.http.HttpStatus
import java.io.BufferedReader
import java.io.InputStream
import java.lang.Exception

/**
* @author yoonho
* @since 2023.06.22
*/
class FeignErrorDecoder: ErrorDecoder {
private val log = LoggerFactory.getLogger(this::class.java)

override fun decode(methodKey: String?, response: Response): Exception {
val status = HttpStatus.resolve(response.status()) ?: HttpStatus.BAD_REQUEST
val body = this.getInputStreamText(input = response.body().asInputStream())

if(status.is4xxClientError) {
log.warn(" >>> [decode] 4xx Client Error - methodKey: $methodKey, status: ${response.status()}, body: $body")
throw BadRequestException()
}else if(status.is5xxServerError) {
log.warn(" >>> [decode] 5xx Server Error - methodKey: $methodKey, status: ${response.status()}, body: $body")
throw InternalServerException()
}

return ErrorDecoder.Default().decode(methodKey, response)
}

private fun getInputStreamText(input: InputStream): String {
val reader = BufferedReader(input.reader())
lateinit var content: String
reader.use {
content = it.readText()
}
return content
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.john.lotto.common.handler

import com.fasterxml.jackson.databind.ObjectMapper
import com.john.lotto.common.dto.BaseResponse
import org.slf4j.LoggerFactory
import org.springframework.boot.web.reactive.error.ErrorWebExceptionHandler
import org.springframework.core.annotation.Order
import org.springframework.http.HttpStatus
import org.springframework.http.MediaType
import org.springframework.stereotype.Component
import org.springframework.web.server.ServerWebExchange
import reactor.core.publisher.Mono

/**
* @author yoonho
* @since 2023.06.22
*/
@Component
@Order(-2)
class WebFluxExceptionHandler: ErrorWebExceptionHandler {

companion object {
private val log = LoggerFactory.getLogger(this::class.java)
private val objectMapper = ObjectMapper()
}

override fun handle(exchange: ServerWebExchange, ex: Throwable): Mono<Void> {
when(ex) {
is IllegalArgumentException -> {
log.warn(" >>> [handle] IllegalArgumentException occurs - message: {}", ex.message)
return exchange.response.writeWith(Mono.fromSupplier {
val bufferFactory = exchange.response.bufferFactory()
exchange.response.statusCode = HttpStatus.BAD_REQUEST
exchange.response.headers.contentType = MediaType.APPLICATION_JSON
return@fromSupplier bufferFactory.wrap(
objectMapper.writeValueAsBytes(BaseResponse(ex.message, HttpStatus.BAD_REQUEST, null))
)
})
}
else -> {
return exchange.response.writeWith(Mono.fromSupplier {
val bufferFactory = exchange.response.bufferFactory()
exchange.response.statusCode = HttpStatus.BAD_REQUEST
exchange.response.headers.contentType = MediaType.APPLICATION_JSON
return@fromSupplier bufferFactory.wrap(
objectMapper.writeValueAsBytes(BaseResponse(ex.message, HttpStatus.BAD_REQUEST, null))
)
})
}
}
}
}
63 changes: 63 additions & 0 deletions api/src/main/kotlin/com/john/lotto/config/OpenFeignConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package com.john.lotto.config

import com.john.lotto.common.handler.FeignErrorDecoder
import feign.RequestInterceptor
import feign.codec.ErrorDecoder
import org.slf4j.LoggerFactory
import org.springframework.cloud.openfeign.EnableFeignClients
import org.springframework.cloud.openfeign.FeignFormatterRegistrar
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.format.datetime.standard.DateTimeFormatterRegistrar
import org.springframework.http.MediaType

/**
* @author yoonho
* @since 2023.06.22
*/
@Configuration
@EnableFeignClients(basePackages = ["com.john.lotto"])
class OpenFeignConfig {
private val log = LoggerFactory.getLogger(this::class.java)

/**
* LocalDateTime Format 설정
*
* @see <a href="https://techblog.woowahan.com/2630/">Feign</a>
* @return [FeignFormatterRegistrar]
* @author yoonho
* @since 2023.06.22
*/
@Bean
fun localDateFeignFormatterRegister(): FeignFormatterRegistrar =
FeignFormatterRegistrar { registry ->
val registrar = DateTimeFormatterRegistrar()
registrar.setUseIsoFormat(true)
registrar.registerFormatters(registry)
}

/**
* Request Interceptor
*
* @return [RequestInterceptor]
* @author yoonho
* @since 2023.06.22
*/
@Bean
fun requestInterceptor(): RequestInterceptor =
RequestInterceptor { interceptor ->
log.info(" >>> [requestInterceptor] path: ${interceptor.path()}, method: ${interceptor.method()}, body: ${interceptor.body()}, queries: ${interceptor.queries()}")
interceptor.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
}

/**
* Error 핸들러 등록
*
* @return [ErrorDecoder]
* @author yoonho
* @since 2023.06.22
*/
@Bean
fun errorDecoder(): ErrorDecoder =
FeignErrorDecoder()
}
21 changes: 21 additions & 0 deletions api/src/main/kotlin/com/john/lotto/config/WebFluxConfig.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.john.lotto.config

import org.springframework.context.annotation.Configuration
import org.springframework.web.reactive.config.CorsRegistry
import org.springframework.web.reactive.config.EnableWebFlux
import org.springframework.web.reactive.config.WebFluxConfigurer

/**
* @author yoonho
* @since 2023.06.22
*/
@Configuration
@EnableWebFlux
class WebFluxConfig: WebFluxConfigurer {

override fun addCorsMappings(registry: CorsRegistry) {
registry.addMapping("/webjars/**")
.allowedOrigins("http://localhost:8080")
.allowedMethods("GET", "POST")
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package com.john.lotto.number.adapter.`in`.web

import com.john.lotto.common.dto.BaseResponse
import com.john.lotto.common.exception.BadRequestException
import com.john.lotto.number.application.port.`in`.FindLottoNumberUseCase
import feign.FeignException.BadRequest
import org.slf4j.LoggerFactory
import org.springframework.stereotype.Component
import org.springframework.web.reactive.function.server.ServerRequest
import org.springframework.web.reactive.function.server.ServerResponse
import reactor.core.publisher.Mono


/**
* @author yoonho
* @since 2023.06.22
*/
@Component
class NumberHandler(
private val lottoNumberUseCase: FindLottoNumberUseCase
) {
private val log = LoggerFactory.getLogger(this::class.java)

fun findLottoNumber(request: ServerRequest): Mono<ServerResponse> =
lottoNumberUseCase.findLottoNumber(
request.queryParam("drwtNo").orElseThrow { BadRequestException("필수 입력값 누락") }
.toLong()
)
.flatMap { BaseResponse().success(it) }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package com.john.lotto.number.adapter.`in`.web

import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.http.MediaType
import org.springframework.web.reactive.function.server.RouterFunction
import org.springframework.web.reactive.function.server.ServerResponse
import org.springframework.web.reactive.function.server.router

/**
* @author yoonho
* @since 2023.06.22
*/
@Configuration
class NumberRouter(
private val numberHandler: NumberHandler
) {

@Bean
fun numberRouterFunction(): RouterFunction<ServerResponse> = router {
accept(MediaType.APPLICATION_JSON).nest {
GET("/api/number", numberHandler::findLottoNumber)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.john.lotto.number.application

import com.john.lotto.common.exception.BadRequestException
import com.john.lotto.number.NumberRepository
import com.john.lotto.number.application.port.`in`.FindLottoNumberUseCase
import com.john.lotto.number.dto.LottoNumberDto
import org.springframework.stereotype.Service
import reactor.core.publisher.Mono

/**
* @author yoonho
* @since 2023.06.22
*/
@Service
class NumberService(
private val numberRepository: NumberRepository
): FindLottoNumberUseCase {

override fun findLottoNumber(drwtNo: Long): Mono<LottoNumberDto> =
Mono.just(numberRepository.findLottoNumber(drwtNo = drwtNo) ?: throw BadRequestException("로또번호가 존재하지 않습니다."))
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.john.lotto.number.application.port.`in`

import com.john.lotto.number.dto.LottoNumberDto
import reactor.core.publisher.Mono

/**
* @author yoonho
* @since 2023.06.22
*/
interface FindLottoNumberUseCase {
fun findLottoNumber(drwtNo: Long): Mono<LottoNumberDto>
}
2 changes: 1 addition & 1 deletion batch/src/main/resources/application-batch.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ spring:
batch:
job:
enabled: true
name: ${job.name:lottoNumberJob}
name: ${job.name}
config:
activate:
on-profile: default

0 comments on commit 689c220

Please sign in to comment.