一次项目上的几个有意思的问题


1 背景

本人参与一次招标项目,参与项目的考试以及解决可能会突发的各种问题,真的是紧张又刺激,下面记录下一些典型的问题,方便后面回顾.

2 遇到的问题

2.1 支持所有get/post/put/delete/patch请求类型等去访问接口

难点: 基于java/kotlin开发,一般基于spring 框架,会用相关的各种注解,每种类型接口写一遍,针对post请求类型,还需要考虑请求体form-data/form-url/json格式以及参数放置在路径上.
解决方式: 路径有参数从路径拿,路径没有从body拿
具体如下:
2.1.1 拦截器拦截请求,获取参数值以map格式放置threadLocal
class ReqParamInterceptor : HandlerInterceptorAdapter() {

    @Throws(Exception::class)
    override fun preHandle(request: HttpServletRequest, response: HttpServletResponse, handler: Any): Boolean {
        ReqParamHolder.initLocal(request.getParams())
        return super.preHandle(request, response, handler)
    }

    @Throws(Exception::class)
    override fun afterCompletion(request: HttpServletRequest, response: HttpServletResponse, handler: Any, ex: Exception?) {
        ReqParamHolder.clearLocal()
        super.afterCompletion(request, response, handler, ex)
    }

    object ReqParamHolder {

        private val clientIpThreadLocal = ThreadLocal<Map<String, String>>()
        val params: Map<String, String>
            get() = clientIpThreadLocal.get()

        fun initLocal(map: Map<String, String>) {
            clientIpThreadLocal.set(map)
        }

        fun clearLocal() {
            clientIpThreadLocal.remove()
        }
    }

}

2.1.2 读取请求路径的参数或者解析body里面的值
fun HttpServletRequest.getParams(): Map<String, String> {
    val set: Set<Entry<String, String>> = readParams(this).entries as Set<Entry<String, String>>
    return set.map { it.key to it.value }.toMap()
}

private fun readParams(request: HttpServletRequest): Map<*, *> {
    val bodyMap = readBody(request)
     if (bodyMap.isNotEmpty()) {
        return bodyMap
    }
    val map = mutableMapOf<Any, Any>()
    val paramNames: Enumeration<*> = request.parameterNames
    while (paramNames.hasMoreElements()) {
        val paramName = paramNames.nextElement() as String
        val paramValues = request.getParameterValues(paramName)
        map[paramName] = paramValues.firstOrNull().takeIf { !it.isNullOrBlank() } ?: continue
    }
    return map
}

private fun readBody(request: HttpServletRequest): Map<String, String> {
    val reqBody = RequestWrapper(request).body
    if (reqBody.isNullOrBlank()) {
        return emptyMap()
    }
    return JsonUtil.jsonToObject(reqBody, Map::class.java) as? Map<String, String> ?: emptyMap()
}
2.1.3 增加过滤器,包装每个request,为了copy请求体的数据做解析,若不copy请求体数据在读取解析,会报请求缓冲区空异常,这个是spring 框架web注解代理类抛出的异常.
@WebFilter(urlPatterns = ["/*"], filterName = "streamFilter")
class StreamFilter : Filter {
    @Throws(ServletException::class)
    override fun init(filterConfig: FilterConfig?) {
    }

    @Throws(IOException::class, ServletException::class)
    override fun doFilter(request: ServletRequest?, response: ServletResponse?, chain: FilterChain) {
        var requestWrapper: ServletRequest? = null
        if (request is HttpServletRequest) {
            requestWrapper = RequestWrapper(request)
        }
        if (null == requestWrapper) {
            chain.doFilter(request, response)
        } else {
            chain.doFilter(requestWrapper, response)
        }
    }

    override fun destroy() {
    }
}
class RequestWrapper(request: HttpServletRequest) : HttpServletRequestWrapper(request) {
    val body: String

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

    init {
        val stringBuilder = StringBuilder()
        var bufferedReader: BufferedReader? = null
        var inputStream: InputStream? = null
        try {
            inputStream = request.inputStream
            if (inputStream != null) {
                bufferedReader = BufferedReader(InputStreamReader(inputStream))
                val charBuffer = CharArray(128)
                var bytesRead = bufferedReader.read(charBuffer)
                while (bytesRead > 0) {
                    stringBuilder.append(charBuffer, 0, bytesRead)
                    bytesRead = bufferedReader.read(charBuffer)
                }
            } else {
                stringBuilder.append("")
            }
        } catch (ex: IOException) {

        } finally {
            if (inputStream != null) {
                try {
                    inputStream.close()
                } catch (e: IOException) {
                    e.printStackTrace()
                }

            }
            if (bufferedReader != null) {
                try {
                    bufferedReader.close()
                } catch (e: IOException) {
                   log.error(e.message)
                }
            }
        }
        body = stringBuilder.toString()
    }

    @Throws(IOException::class)
    override fun getInputStream(): ServletInputStream {
        val byteArrayInputStream = ByteArrayInputStream(body.toByteArray())
        return object : ServletInputStream() {
            override fun isFinished(): Boolean {
                return false
            }

            override fun isReady(): Boolean {
                return false
            }

            override fun setReadListener(readListener: ReadListener) {}
            @Throws(IOException::class)
            override fun read(): Int {
                return byteArrayInputStream.read()
            }
        }

    }

    @Throws(IOException::class)
    override fun getReader(): BufferedReader {
        return BufferedReader(InputStreamReader(this.inputStream))
    }

}
2.1.4 通过threadLocal获取得到的参数map执行请求方法
/**
     * 获取推流地址
     */
    @RequestMapping(value = ["/getCoursePushUrl"], method = [RequestMethod.GET, RequestMethod.POST])
    override fun getCoursePushUrl(@RequestParam("courseId", defaultValue = "") courseId: String): GetCoursePushUrlResDto {
        val courseId = ReqParamHolder.params["courseId"] ?: courseId
        if (courseId.isBlank()) {
            throw WebAppException(PARAM_ERROR)
        }
        val pushUrl = liveService.applyStreamUrl(courseId, StreamTypeEnum.RTMP_PUSH)
        CoroutineScope(Dispatchers.IO).launch {
            centerBusinessService.reportLiveUrl(courseId, pushUrl)
        }
        return GetCoursePushUrlResDto(pushUrl)
    }

2.2 应用日志全局过滤敏感字段

log4j2支持使用正则匹配替换日志的匹配到的字符串
<Appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}][%t][%level][%C:%L][%traceId] %m%n">
                <replace regex="手机号?" replacement="***"/>
            </PatternLayout>
        </Console>

        <RollingFile name="DailyRollingFile" fileName="${log_path}/live.log"
                     filePattern="${log_path}/live-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout pattern="[%d{yyyy-MM-dd HH:mm:ss.SSS}][%t][%level][%C:%L][%traceId] %m%n">
                <replace regex="seewo?" replacement="***"/>
            </PatternLayout>
            <SizeBasedTriggeringPolicy size="100MB"/>
            <DefaultRolloverStrategy max="100"/>
        </RollingFile>
    </Appenders>

2.3 修复bug后如何快速推送应用docker镜像,双方公网传输传输

难点: 修复bug后重新打包镜像,应用镜像镜像有点大,若一方上传,一方下载,这个解决bug加上传输过程花费时间效率低,不能快速响应.
解决方式:程序打包的镜像退送阿里云公网,通过docker pull 差异化拉取程序,不是整体覆盖拉取.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值