Merge pull request #5 from TouchInstinct/common-spring-web

Common spring web
This commit is contained in:
Alexander Buntakov 2021-06-07 16:34:25 +03:00 committed by GitHub
commit 44d1411e83
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 203 additions and 0 deletions

View File

@ -38,3 +38,9 @@
* `models.*` - базовые `Entity`
* `repositories` - утилиты и доп. интерфейсы для репозиториев
* `EnableJpaAuditingExtra` - подключение `JpaAuditing` с поддержкой типа `ZoneDateTime`
## common-spring-web
* `request.Utils` - различные `extensions` для работы с `HttpServletRequest`
* `errors.*` - исключения и типы данных для `web`
* `webclient.*` - классы для расширения webclient, включая логирование

View File

@ -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")
}

View File

@ -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
}
}

View File

@ -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
)
}
}

View File

@ -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
}
}

View File

@ -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")
}
}

View File

@ -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
}
}
}

View File

@ -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,
)

View File

@ -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)

View File

@ -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)
}

View File

@ -22,3 +22,4 @@ pluginManagement {
include("common")
include("common-spring")
include("common-spring-jpa")
include("common-spring-web")