zhanghan

6年Java互联网研发经验,坐标北京;擅长微服务和中间件。
6年Java互联网研发经验,坐标北京;擅长微服务和中间件。

SpringBoot实战(十三):Spring Boot Admin 动态修改日志级别

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

本文链接:
https://zhanghan.blog.csdn.net/article/details/101282082

【前言】

之前关于线上输出日志一直有个困惑:如何可以动态调整的日志级别,来保证系统在正常运行时性能同时又能在出现问题时打印详细的信息来快速定位问题;最近研究一下Spring Boot Admin中动态日志级别调整,并集成项目中,在此与大家共享;

【动态修改日志级别】

         一、生产环境日志输出的状况
1、生产环境日志输出的困惑
A.设置日志输出级别为info;
(1)优点:可以详细的打印日志,有利于排错;
(2)缺点:日志消耗系统的性能较大;只能针对整个系统整体设置日志输出较多,定位具体类或方法日志效率较低;
B.设置日志输出级别为error;
(1)优点:日志消耗系统性能较小;
(2)缺点:当遇到bug时,无法追踪到详细信息难以定位问题;
2、解决办法:
A.比较被动的妥协解决方案:项目发布时设置日志输出级别为error,当线上遇到问题时,再重启项目修改日志的输出级别为info;
(1)优点:无需改造现有项目,无需增加监控系统管理日志级别;
(2)缺点:每次都需要重启项目,项目多不利于统一管理;
B.动态配置日志级别:通过Spring Boot Admin的日志级别管理来动态调整日志级别;
(1)优点:动态更新日志级别,细粒度控制(项目,包,类,方法)日志级别,便于快速定位问题;
(2)缺点:需要对现有系统进行改造,增加Spring Boot Admin监控系统;
         二、项目集成
参考上篇博文《SpringBoot实战(十二):集成 Spring Boot Admin 监控
         三、效果展示
                 1、查看项目启动时日志级别

2、访问系统接口,查看日志

3、在Admin管理控制台修改类 com.zhanghan.zhboot.controller.CheckMobileController 的日志级别为Info

4、再次访问接口,查看日志(很显然,已经将修改为info

         四、项目地址:
1、地址:https://github.com/dangnianchuntian/springboot
2、代码版本:1.5.0-Release

【总结】

1、动态修改日志级别将大大提高我们排错的效率,尽快定位问题,减少损失;
2、接下来会为大家共享更多关于SpringBootAdmin的特性。

Posted by zhanghan in SpringBoot实战, 0 comments

数据库主从延迟导致查询不准确的解决思路

【前言】
当数据达到一定量的时候,数据库会成为整个系统的瓶颈,一般采取的优化策略为读写分离,数据库通过分主库从库从而实现读写分离(写请求操作主库,读请求操作从库);

【解决数据延迟思路】
一、主从同步原理(在此以目前最普及的MySQL为例)

以下是一张经典的MySQL通过binlog实现主从数据同步的原理图:

二、问题是如何产生?

1、从上面原理图中不难发现,主从同步是有一定的延迟,影响延迟大小因素:

(1)延迟的大小取决于从上次同步到现在产生数据量

(2)当前服务器间网络情况

(3)主从服务器本身压力(CPU,内存,IO等)

2、由于数据库服务一般都是在内网当中,而且服务器在购置时都会高一些配置(比实际需要)所以基本上同步都很快,一般为毫秒级;

3、一般的业务场景中毫秒级延迟是可以忽略;

4、有一般就有特殊,有一些特殊情况需要实时毫秒级的时间差也不允许;以下是针对这些特殊情况给出常见的解决方案。

         三、数据延迟解决方案:

1、方案一:写程序双写(写主库同时写读库)

2、方案二:读程序查主库

3、方案三:写程序写主库且写缓存(设置一定的失效时间,一般略大于数据库同步最大时延即可),读程序读缓存读从库

四、三种方案优缺点:

1、方案一:双写会消耗一定的性能,实现起来比较简单,高并发写的场景不适用;

2、方案二:读程序会影响主库的性能,实现起来比较简单,高并发读的场景不适用;

3、方案三:大部分情况下读写均多消耗写性能,实现较为复杂,高并发读写都适用(缓存读写非常快);

【总结】
1、实现固然重要,但更为重要的是思路;

2、很多底层的原理与思想是通用的。

Posted by zhanghan in 技术, 3 comments

Required request body is missing

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:
https://zhanghan.blog.csdn.net/article/details/98673078 强烈推荐一个大神的人工智能的教程:http://www.captainbed.net/zhanghan

【前言】

最近对代码进行统一日志处理,通过拦截器,打印请求日志,方便排查问题,通过拦截器取参数后遇到一个问题:Required request body is missing;在进行了相关实验后最终解决此问题。

【解决问题】

        一、问题复现
              1、代码实现
(1)演示拦截控制器

/*
 * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved.
 * 项目名称:实战SpringBoot
 * 类名称:InterceptController.java
 * 创建人:张晗
 * 联系方式:zhanghan_java@163.com
 * 开源地址: https://github.com/dangnianchuntian/springboot
 * 博客地址: https://zhanghan.blog.csdn.net
 */
package com.zhanghan.zhboot.controller;
import com.zhanghan.zhboot.controller.request.InterceptRequest;
import com.zhanghan.zhboot.util.wrapper.WrapMapper;
import com.zhanghan.zhboot.util.wrapper.Wrapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
@RestController
@Api(value = "演示拦截控制器",tags = {"演示拦截控制器"})
public class InterceptController {
    @ApiOperation(value="演示拦截",tags = {"演示拦截控制器"})
    @RequestMapping(value = "/intercept", method = RequestMethod.POST)
    public Wrapper intercept(@Valid @RequestBody  InterceptRequest interceptRequest) {
        System.out.println(interceptRequest.toString());
        Map<string, object=""> map = new HashMap();
        map.put("intLombok", interceptRequest.getIntLombok());
        map.put("strLombok", interceptRequest.getStrLombok());
        return WrapMapper.ok(map);
    }
}
</string,>

(2)所需的请求实体

/*
 * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved.
 * 项目名称:实战SpringBoot
 * 类名称:InterceptRequest.java
 * 创建人:张晗
 * 联系方式:zhanghan_java@163.com
 * 开源地址: https://github.com/dangnianchuntian/springboot
 * 博客地址: https://zhanghan.blog.csdn.net
 */
package com.zhanghan.zhboot.controller.request;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
@ApiModel("拦截器演示请求实体")
@Data
public class InterceptRequest {
    @ApiModelProperty(value = "整数类型")
    private Integer intLombok;
    @ApiModelProperty(value = "字符串类型")
    private String strLombok;
}

(3)日志拦截器

/*
 * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved.
 * 项目名称:实战SpringBoot
 * 类名称:InterceptLog.java
 * 创建人:张晗
 * 联系方式:zhanghan_java@163.com
 * 开源地址: https://github.com/dangnianchuntian/springboot
 * 博客地址: https://zhanghan.blog.csdn.net
 */
package com.zhanghan.zhboot.intercept;
import io.micrometer.core.instrument.util.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.StreamUtils;
import org.springframework.web.servlet.HandlerInterceptor;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.nio.charset.Charset;
@Component
@Order(10000)
public class InterceptLog implements HandlerInterceptor {
    //日志操作
    private static final Logger log = LoggerFactory.getLogger(InterceptLog.class);
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        beforeRequestLogInfo(request);
        return true;
    }
    //记录请求日志
    private void beforeRequestLogInfo(HttpServletRequest request) {
        ///获取请求方式
        String method = request.getMethod();
        ///请求地址
        String url = request.getRequestURI();
        ///获取请求参数
        String paramsStr = "";
        if ("POST".equals(method)) {
            ///获取post请求参数
            paramsStr = this.getPostParm(request);
        } else if ("GET".equals(method)) {
            //Get方式请求参数
            paramsStr = request.getQueryString();
        }
        ///日志模板
        String Template = "请求方式:%s ;请求地址:%s ;请求参数:%s 。";
        ///记录日志
        log.info(String.format(Template, method, url, paramsStr));
    }
    private String getPostParm(HttpServletRequest request) {
        try {
            return getPostBodyParams(request.getInputStream(), request.getCharacterEncoding());
        } catch (Exception e) {
            ///记录日志
            log.error("getPostParm exception", e);
            return "getPostParm exception";
        }
    }
    /**
     * 获取POST请求中Body参数
     *
     * @param
     * @return 字符串
     */
    private String getPostBodyParams(ServletInputStream inputStream, String charset) throws Exception {
        try {
            String body = StreamUtils.copyToString(inputStream, Charset.forName(charset));
            if (StringUtils.isEmpty(body)) {
                return "";
            }
            return body;
        } catch (Exception e) {
            throw e;
        }
    }
}

(4)项目启动时加载拦截器

/*
 * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved.
 * 项目名称:实战SpringBoot
 * 类名称:WebMvcConfig.java
 * 创建人:张晗
 * 联系方式:zhanghan_java@163.com
 * 开源地址: https://github.com/dangnianchuntian/springboot
 * 博客地址: https://zhanghan.blog.csdn.net
 */
package com.zhanghan.zhboot.config;
import com.zhanghan.zhboot.intercept.InterceptLog;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new InterceptLog()).addPathPatterns("/**");
        super.addInterceptors(registry);
    }
}

              2、启动项目进行相关验证
(1)在浏览器访问swagger:http://localhost:8080/swagger-ui.html

(2)查看日志

2019-08-07 14:58:55.333  INFO 12320 --- [io-8080-exec-10] c.z.zhboot.intercept.InterceptLog        : 请求方式:POST ;请求地址:/intercept ;请求参数:{
  "intLombok": 0,
  "strLombok": "string"
} 。
2019-08-07 14:58:57.743 ERROR 12320 --- [io-8080-exec-10] c.z.z.filter.GlobalExceptionHandler      : HttpMessageNotReadableException=Required request body is missing: public com.zhanghan.zhboot.util.wrapper.Wrapper com.zhanghan.zhboot.controller.InterceptController.lombok(com.zhanghan.zhboot.controller.request.InterceptRequest)
org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public com.zhanghan.zhboot.util.wrapper.Wrapper com.zhanghan.zhboot.controller.InterceptController.lombok(com.zhanghan.zhboot.controller.request.InterceptRequest)
	at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters(RequestResponseBodyMethodProcessor.java:160) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.resolveArgument(RequestResponseBodyMethodProcessor.java:130) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.method.support.HandlerMethodArgumentResolverComposite.resolveArgument(HandlerMethodArgumentResolverComposite.java:126) ~[spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.getMethodArgumentValues(InvocableHandlerMethod.java:166) ~[spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:134) ~[spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:102) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:800) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1038) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:942) ~[spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1005) [spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:908) [spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:660) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:882) [spring-webmvc-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at javax.servlet.http.HttpServlet.service(HttpServlet.java:741) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:231) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) [tomcat-embed-websocket-9.0.13.jar:9.0.13]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.springframework.boot.actuate.web.trace.servlet.HttpTraceFilter.doFilterInternal(HttpTraceFilter.java:90) [spring-boot-actuator-2.1.1.RELEASE.jar:2.1.1.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:92) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:93) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.filterAndRecordMetrics(WebMvcMetricsFilter.java:117) [spring-boot-actuator-2.1.1.RELEASE.jar:2.1.1.RELEASE]
	at org.springframework.boot.actuate.metrics.web.servlet.WebMvcMetricsFilter.doFilterInternal(WebMvcMetricsFilter.java:106) [spring-boot-actuator-2.1.1.RELEASE.jar:2.1.1.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) [spring-web-5.1.3.RELEASE.jar:5.1.3.RELEASE]
	at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:490) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:139) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:343) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:408) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:791) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1417) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_144]
	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_144]
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-9.0.13.jar:9.0.13]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_144]

3、报错原理示意图

        二、解决方案
              1、解决方案一:去除控制器方法上的@RequestBody或将其设置为false

              2、解决方案二:增加过滤器保存流
(1)增加Body封装类,用来保存requestBody

/*
 * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved.
 * 项目名称:实战SpringBoot
 * 类名称:BodyReaderWrapper.java
 * 创建人:张晗
 * 联系方式:zhanghan_java@163.com
 * 开源地址: https://github.com/dangnianchuntian/springboot
 * 博客地址: https://zhanghan.blog.csdn.net
 */
package com.zhanghan.zhboot.intercept;
import org.springframework.util.StreamUtils;
import javax.servlet.ReadListener;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
public class BodyReaderWrapper extends HttpServletRequestWrapper {
    //用于将流保存下来
    private byte[] requestBody;
    public BodyReaderWrapper(HttpServletRequest request) throws IOException {
        super(request);
        requestBody = StreamUtils.copyToByteArray(request.getInputStream());
    }
    @Override
    public ServletInputStream getInputStream() throws IOException {
        final ByteArrayInputStream bais = new ByteArrayInputStream(requestBody);
        return new ServletInputStream() {
            @Override
            public int read() throws IOException {
                return bais.read();
            }
            @Override
            public boolean isFinished() {
                return false;
            }
            @Override
            public boolean isReady() {
                return false;
            }
            @Override
            public void setReadListener(ReadListener readListener) {
            }
        };
    }
    @Override
    public BufferedReader getReader() throws IOException {
        return new BufferedReader(new InputStreamReader(getInputStream()));
    }
}

(2)增加过滤器

/*
 * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved.
 * 项目名称:实战SpringBoot
 * 类名称:BodyReaderFilter.java
 * 创建人:张晗
 * 联系方式:zhanghan_java@163.com
 * 开源地址: https://github.com/dangnianchuntian/springboot
 * 博客地址: https://zhanghan.blog.csdn.net
 */
package com.zhanghan.zhboot.filter;
import com.zhanghan.zhboot.intercept.BodyReaderWrapper;
import javax.servlet.*;
import javax.servlet.annotation.WebFilter;
import javax.servlet.http.HttpServletRequest;
import java.io.IOException;
@WebFilter(filterName="bodyReaderFilter",urlPatterns="/*")
public class BodyReaderFilter implements Filter {
    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
        // do nothing
    }
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        ServletRequest requestWrapper=null;
        if(request instanceof HttpServletRequest) {
            requestWrapper=new BodyReaderWrapper((HttpServletRequest)request);
        }
        if(requestWrapper==null) {
            chain.doFilter(request, response);
        }else {
            chain.doFilter(requestWrapper, response);
        }
    }
    @Override
    public void destroy() {
        // do nothing
    }
}

(3)启动类增加Bean,识别过滤器

/*
 * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved.
 * 项目名称:实战SpringBoot
 * 类名称:ZhBootApplication.java
 * 创建人:张晗
 * 联系方式:zhanghan_java@163.com
 * 开源地址: https://github.com/dangnianchuntian/springboot
 * 博客地址: https://zhanghan.blog.csdn.net
 */
package com.zhanghan.zhboot;
import com.zhanghan.zhboot.filter.BodyReaderFilter;
import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
@SpringBootApplication(exclude = {
        DataSourceAutoConfiguration.class,
        MybatisAutoConfiguration.class})
public class ZhBootApplication {
    public static void main(String[] args) {
        SpringApplication.run(ZhBootApplication.class, args);
    }
    @Bean
    public FilterRegistrationBean Filters() {
        FilterRegistrationBean registrationBean = new FilterRegistrationBean();
        registrationBean.setFilter(new BodyReaderFilter());
        registrationBean.addUrlPatterns("/*");
        registrationBean.setName("bodyReaderFilter");
        return registrationBean;
    }
}

(4)修改拦截器

(5)原理分析示意图

              3、启动项目进行相关验证
(1)在浏览器访问swagger:http://localhost:8080/swagger-ui.html

(2)查看日志,项目正常记录了访问日志

2019-08-07 15:33:10.738  INFO 10232 --- [nio-8080-exec-2] c.z.zhboot.intercept.InterceptLog        : 请求方式:POST ;请求地址:/intercept ;请求参数:{
  "intLombok": 0,
  "strLombok": "string"
} 。
InterceptRequest(intLombok=0, strLombok=string)

        三、项目地址
https://github.com/dangnianchuntian/springboot

【总结】

1、实践和理论是相辅相成;
2、原理可以让我们举一反三,实践可以让我们检验原理,并且加深对原理的理解;

Posted by zhanghan in 技术, 0 comments

BigDecimal 校验格式

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:
https://zhanghan.blog.csdn.net/article/details/97649212 强烈推荐一个大神的人工智能的教程:http://www.captainbed.net/zhanghan

【前言】

        最近收到一个需求,在管理后台对系统中的金额进行操作时,对操作的金额需要进行相关校验,如果不校验,在进行相关的计算时就可能会出问题。

【BigDecimal 校验格式】

        一、业务需求
               前端接收到金额传给后端时需要对金额的格式进行校验,校验规则:如果有小数,则小数的位数不能超过两位;如:
                1888          符合要求
                1888.1       符合要求
                1888.12     符合要求
                1888.123   不符合要求                 
        二、相关代码及相应测试
               1、代码
                  (1)工具类

/*
 * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved.
 * 项目名称:实战SpringBoot
 * 类名称:MoneyUtil.java
 * 创建人:张晗
 * 联系方式:zhanghan_java@163.com
 * 开源地址: https://github.com/dangnianchuntian/springboot
 * 博客地址: https://zhanghan.blog.csdn.net
 */
package com.zhanghan.zhboot.util;
import java.util.regex.Pattern;
public class MoneyUtil {
    //两位小数金额校验
    public static boolean judgeTwoDecimal(Object obj) {
        boolean flag = false;
        try {
            if (obj != null) {
                String source = obj.toString();
                // 判断是否是整数或者是携带一位或者两位的小数
                Pattern pattern = Pattern.compile("^[+]?([0-9]+(.[0-9]{1,2})?)$");
                if (pattern.matcher(source).matches()) {
                    flag = true;
                }
            }
        } catch (Exception e) {
            e.getMessage();
        }
        return flag;
    }
}

                  (2)测试的Request

/*
 * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved.
 * 项目名称:实战SpringBoot
 * 类名称:CheckMoneyRequest.java
 * 创建人:张晗
 * 联系方式:zhanghan_java@163.com
 * 开源地址: https://github.com/dangnianchuntian/springboot
 * 博客地址: https://zhanghan.blog.csdn.net
 */
package com.zhanghan.zhboot.controller.request;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.math.BigDecimal;
@ApiModel("校验金额演示请求实体")
@Data
public class CheckMoneyRequest {
    @ApiModelProperty(value = "金额")
    private BigDecimal money;
}

                  (3)测试Controller

/*
 * Copyright (c) 2019. zhanghan_java@163.com All Rights Reserved.
 * 项目名称:实战SpringBoot
 * 类名称:CheckMoneyController.java
 * 创建人:张晗
 * 联系方式:zhanghan_java@163.com
 * 开源地址: https://github.com/dangnianchuntian/springboot
 * 博客地址: https://zhanghan.blog.csdn.net
 */
package com.zhanghan.zhboot.controller;
import com.zhanghan.zhboot.controller.request.CheckMoneyRequest;
import com.zhanghan.zhboot.util.MoneyUtil;
import com.zhanghan.zhboot.util.wrapper.WrapMapper;
import com.zhanghan.zhboot.util.wrapper.Wrapper;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.Map;
@RestController
@Api(value = "演示校验金额控制器", tags = {"演示校验金额控制器"})
public class CheckMoneyController {
    @ApiOperation(value = "演示金额校验", tags = {"演示校验金额控制器"})
    @RequestMapping(value = "/check/money", method = RequestMethod.POST)
    public Wrapper lombok(@RequestBody CheckMoneyRequest checkMoneyRequest) {
        BigDecimal money = checkMoneyRequest.getMoney();
        //校验金额是否符合要求
        boolean legal = MoneyUtil.judgeTwoDecimal(money);
        Map map = new HashMap();
        map.put("money", money);
        map.put("islegal",legal);
        return WrapMapper.ok(map);
    }
}

               2、测试结果
                  (1)符合条件:

                  (2)不符合条件

         

【总结】

        1、金额一定要有检验,不然进入系统中不符合要求的数据,在进行相关计算的时候会出问题,有时每笔订单只差几分但是订单量多了,差的钱就多了;
        2、做程序需时刻谨慎,什么时候都不可大意。

Posted by zhanghan in 技术, 0 comments