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 差异化拉取程序,不是整体覆盖拉取.