All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.github.zhujk.resp.core.CommonAdvice.kt Maven / Gradle / Ivy

There is a newer version: 1.0.6
Show newest version
package com.github.zhujk.resp.core

import com.github.zhujk.resp.annotation.RespIgnore
import com.github.zhujk.resp.converter.RespConverter
import com.github.zhujk.resp.exception.RespException
import com.github.zhujk.resp.exception.rootCause
import com.github.zhujk.resp.props.RespProperties
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.InitializingBean
import org.springframework.boot.actuate.autoconfigure.web.server.ManagementServerProperties
import org.springframework.boot.autoconfigure.web.ServerProperties
import org.springframework.core.MethodParameter
import org.springframework.core.annotation.AnnotationUtils
import org.springframework.core.env.Environment
import org.springframework.http.MediaType
import org.springframework.http.converter.HttpMessageConverter
import org.springframework.http.converter.json.MappingJacksonValue
import org.springframework.http.server.ServerHttpRequest
import org.springframework.http.server.ServerHttpResponse
import org.springframework.http.server.ServletServerHttpRequest
import org.springframework.http.server.ServletServerHttpResponse
import org.springframework.util.AntPathMatcher
import org.springframework.util.Assert
import org.springframework.util.PathMatcher
import org.springframework.web.bind.MethodArgumentNotValidException
import org.springframework.web.bind.annotation.ExceptionHandler
import org.springframework.web.bind.annotation.RestControllerAdvice
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice

@RestControllerAdvice
class CommonAdvice(
    private val respProps: RespProperties,
    private val converter: RespConverter<*>,
    private val serverProps: ServerProperties,
    private val managementServerProps: ManagementServerProperties,
    private val environment: Environment
) : ResponseBodyAdvice, InitializingBean {
    private val logger = LoggerFactory.getLogger(javaClass)
    private val pathMatcher: PathMatcher = AntPathMatcher()

    override fun afterPropertiesSet() {
        val respClass = respProps.respClass
        val convertedClass = converter.convert(0, "")!!.javaClass
        Assert.isAssignable(respClass, convertedClass) {
            "invalid converter:${converter.javaClass} for resp class:$respClass, converted result class:$convertedClass"
        }
    }

    @ExceptionHandler
    fun handleException(t: Throwable): Any? {
        when (t) {
            is MethodArgumentNotValidException -> {
                val msg = t
                    .bindingResult
                    .allErrors
                    .map { it.defaultMessage }
                    .joinToString(";")
                logger.info("invalid args,msg:{}", msg)
                return converter.convert(400, msg, throwable = t)
            }
            is RespException -> {
                val code = t.code
                val message = t.msg
                logger.info("resp error,code:{},message:{}", code, message)
                return converter.convert(code, message, throwable = t)
            }
            else -> {
                logger.error("error", t)
                val rootCause = t.rootCause
                if (rootCause is RespException) {
                    return converter.convert(rootCause.code, rootCause.msg)
                }
                return converter.convert(500, INTERNAL_ERROR, throwable = t)
            }
        }
    }

    override fun supports(returnType: MethodParameter, converterType: Class>): Boolean {
        return AnnotationUtils.findAnnotation(returnType.method!!, RespIgnore::class.java) == null &&
                AnnotationUtils.findAnnotation(returnType.declaringClass, RespIgnore::class.java) == null &&
                !respProps.respClass.isAssignableFrom(returnType.method!!.returnType)
    }

    override fun beforeBodyWrite(
        body: Any?,
        returnType: MethodParameter,
        selectedContentType: MediaType,
        selectedConverterType: Class>,
        request: ServerHttpRequest,
        response: ServerHttpResponse
    ): Any? {
        val container = getOrCreateContainer(body)
        beforeBodyWriteInternal(container, selectedContentType, returnType, request, response)
        return container
    }

    protected fun getOrCreateContainer(body: Any?): MappingJacksonValue {
        return if (body is MappingJacksonValue) body else MappingJacksonValue(body!!)
    }

    protected fun beforeBodyWriteInternal(
        bodyContainer: MappingJacksonValue,
        contentType: MediaType,
        returnType: MethodParameter,
        request: ServerHttpRequest,
        response: ServerHttpResponse
    ) {
        val serverContextPath = serverProps.servlet.contextPath ?: ""
        val managementContextPath = managementServerProps.servlet.contextPath ?: ""
        val serverPort = environment.getRequiredProperty("local.server.port").toInt()
        val managementPort = environment.getRequiredProperty("local.management.port").toInt()
        val req = (request as ServletServerHttpRequest).servletRequest
        val localPort = req.localPort
        val requestURI = req.requestURI
        val path = requestURI.substringAfter(
            when (localPort) {
                serverPort -> serverContextPath
                managementPort -> managementContextPath
                else -> TODO("invalid: localPort:$localPort,serverPort:$serverPort,managementPort:$managementPort")
            }
        )
        if (isIgnoredPath(path)) {
            return
        }
        val data = bodyContainer.value
        if (isIgnoredInstance(data)) {
            return
        }
        val status = (response as ServletServerHttpResponse).servletResponse.status
        bodyContainer.value = converter.convert(status, SUCCESS, data)!!
    }

    private fun isIgnoredInstance(data: Any) =
        respProps.respClass.isInstance(data) || respProps.ignoredClasses.any { it.isInstance(data) }

    private fun isIgnoredPath(path: String) =
        respProps.defaultIgnorePathPatterns.any { pathMatcher.match(it, path) } ||
                respProps.extraIgnorePathPatterns.any { pathMatcher.match(it, path) }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy