Merge pull request #5 from TouchInstinct/common-spring-web
Common spring web
This commit is contained in:
commit
44d1411e83
|
|
@ -38,3 +38,9 @@
|
|||
* `models.*` - базовые `Entity`
|
||||
* `repositories` - утилиты и доп. интерфейсы для репозиториев
|
||||
* `EnableJpaAuditingExtra` - подключение `JpaAuditing` с поддержкой типа `ZoneDateTime`
|
||||
|
||||
## common-spring-web
|
||||
|
||||
* `request.Utils` - различные `extensions` для работы с `HttpServletRequest`
|
||||
* `errors.*` - исключения и типы данных для `web`
|
||||
* `webclient.*` - классы для расширения webclient, включая логирование
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
plugins {
|
||||
id("kotlin")
|
||||
id("kotlin-spring")
|
||||
id("maven-publish")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api("com.fasterxml.jackson.module:jackson-module-kotlin")
|
||||
|
||||
implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
|
||||
|
||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||
implementation("org.springframework.boot:spring-boot-starter-webflux")
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package ru.touchin.common.spring.web.dto
|
||||
|
||||
interface ApiError {
|
||||
|
||||
val errorCode: Int
|
||||
val errorMessage: String?
|
||||
|
||||
companion object {
|
||||
const val SUCCESS_CODE = 0
|
||||
const val FAILURE_CODE = -1
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package ru.touchin.common.spring.web.dto
|
||||
|
||||
class DefaultApiError(
|
||||
override val errorCode: Int,
|
||||
override val errorMessage: String? = null
|
||||
): ApiError {
|
||||
|
||||
companion object {
|
||||
fun createFailure(errorMessage: String? = null) = DefaultApiError(
|
||||
errorCode = ApiError.FAILURE_CODE,
|
||||
errorMessage = errorMessage
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
@file:Suppress("unused")
|
||||
package ru.touchin.common.spring.web.request
|
||||
|
||||
import javax.servlet.http.HttpServletRequest
|
||||
|
||||
object RequestUtils {
|
||||
|
||||
fun HttpServletRequest.doesPrefixMatch(prefixes: List<String>): Boolean {
|
||||
return prefixes.any { pathPrefix ->
|
||||
this.requestURI.startsWith(pathPrefix, ignoreCase = true)
|
||||
}
|
||||
}
|
||||
|
||||
val HttpServletRequest.publicIp: String get() {
|
||||
return getHeader("X-Real-Ip")
|
||||
?: this.remoteAddr
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
@file:Suppress("unused")
|
||||
package ru.touchin.common.spring.web.webclient
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonInclude
|
||||
import com.fasterxml.jackson.databind.DeserializationFeature
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule
|
||||
import com.fasterxml.jackson.module.kotlin.KotlinModule
|
||||
import org.springframework.http.HttpStatus
|
||||
import org.springframework.http.client.reactive.ClientHttpConnector
|
||||
import org.springframework.web.reactive.function.client.ClientResponse
|
||||
import org.springframework.web.reactive.function.client.ExchangeStrategies
|
||||
import org.springframework.web.reactive.function.client.WebClient
|
||||
import org.springframework.web.reactive.function.client.bodyToMono
|
||||
import reactor.core.publisher.Mono
|
||||
import ru.touchin.common.spring.web.webclient.errors.WebClientStatusException
|
||||
import ru.touchin.common.spring.web.webclient.logger.WebClientLogger
|
||||
|
||||
abstract class BaseLogWebClient(
|
||||
private val webClientLogger: WebClientLogger,
|
||||
private val builder: WebClient.Builder,
|
||||
) : LogWebClient {
|
||||
|
||||
protected open var strategies: ExchangeStrategies? = null
|
||||
|
||||
protected open var clientConnector: ClientHttpConnector? = null
|
||||
|
||||
override fun getLogger(): WebClientLogger {
|
||||
return webClientLogger
|
||||
}
|
||||
|
||||
override fun getObjectMapper(): ObjectMapper {
|
||||
return defaultObjectMapper
|
||||
}
|
||||
|
||||
protected inline fun <reified T : Any> checkServiceAvailable(clientResponse: ClientResponse): Mono<T> {
|
||||
return when (val status = clientResponse.statusCode()) {
|
||||
HttpStatus.OK -> clientResponse.bodyToMono()
|
||||
else -> throw WebClientStatusException(
|
||||
"Status code $status",
|
||||
status,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
protected fun getWebClientBuilder(url: String): WebClient.Builder {
|
||||
val webClient = builder.baseUrl(url)
|
||||
|
||||
this.clientConnector?.let {
|
||||
webClient.clientConnector(it)
|
||||
}
|
||||
|
||||
this.strategies?.let {
|
||||
webClient.exchangeStrategies(it)
|
||||
}
|
||||
|
||||
return webClient
|
||||
}
|
||||
|
||||
abstract fun getWebClient(): WebClient
|
||||
|
||||
companion object {
|
||||
val defaultObjectMapper = ObjectMapper()
|
||||
.registerModule(KotlinModule())
|
||||
.registerModule(JavaTimeModule())
|
||||
.setSerializationInclusion(JsonInclude.Include.NON_NULL)
|
||||
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
|
||||
?: throw IllegalStateException("unable to retrieve ObjectMapper")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,38 @@
|
|||
@file:Suppress("unused")
|
||||
package ru.touchin.common.spring.web.webclient
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper
|
||||
import org.springframework.web.reactive.function.client.WebClient
|
||||
import reactor.core.publisher.Mono
|
||||
import ru.touchin.common.spring.web.webclient.dto.RequestLogData
|
||||
import ru.touchin.common.spring.web.webclient.logger.WebClientLogger
|
||||
|
||||
interface LogWebClient {
|
||||
|
||||
fun getLogger(): WebClientLogger
|
||||
|
||||
fun getObjectMapper(): ObjectMapper
|
||||
|
||||
fun <T> WebClient.RequestHeadersSpec<*>.exchange(
|
||||
clazz: Class<T>,
|
||||
requestLogData: RequestLogData,
|
||||
): Mono<T> {
|
||||
return exchangeToMono { clientResponse ->
|
||||
clientResponse.bodyToMono(String::class.java)
|
||||
}.map { responseBody ->
|
||||
getLogger().log(requestLogData.copy(responseBody = responseBody))
|
||||
|
||||
parseValue(responseBody, clazz)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> parseValue(source: String?, clazz: Class<T>): T {
|
||||
return if (clazz.canonicalName != String::class.java.canonicalName) {
|
||||
getObjectMapper().readValue(source, clazz)
|
||||
} else {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
source as T // T is String
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package ru.touchin.common.spring.web.webclient.dto
|
||||
|
||||
import org.springframework.http.HttpMethod
|
||||
|
||||
data class RequestLogData(
|
||||
val uri: String,
|
||||
val logTags: List<String>,
|
||||
val method: HttpMethod,
|
||||
val requestBody: Any? = null,
|
||||
val responseBody: Any? = null,
|
||||
)
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
@file:Suppress("unused")
|
||||
package ru.touchin.common.spring.web.webclient.errors
|
||||
|
||||
import org.springframework.http.HttpStatus
|
||||
|
||||
class WebClientStatusException(message: String, val status: HttpStatus) : Exception(message)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package ru.touchin.common.spring.web.webclient.logger
|
||||
|
||||
import ru.touchin.common.spring.web.webclient.dto.RequestLogData
|
||||
|
||||
interface WebClientLogger {
|
||||
|
||||
fun log(requestLogData: RequestLogData)
|
||||
|
||||
}
|
||||
|
|
@ -22,3 +22,4 @@ pluginManagement {
|
|||
include("common")
|
||||
include("common-spring")
|
||||
include("common-spring-jpa")
|
||||
include("common-spring-web")
|
||||
|
|
|
|||
Loading…
Reference in New Issue