当前位置 : 主页 > 编程语言 > java >

springboot打印接口调用日志的实例

来源:互联网 收集:自由互联 发布时间:2023-01-30
目录 概述 方案思路 封装HttpServletRequest请求 把可重复读请求体通过过滤器往下传 记录入参日志 实现入参记录拦截器 注册拦截器 记录返参日志 概述 请求日志几乎是所有大型企业级项目
目录
  • 概述
  • 方案思路
  • 封装HttpServletRequest请求
  • 把可重复读请求体通过过滤器往下传
  • 记录入参日志
    • 实现入参记录拦截器
    • 注册拦截器
  • 记录返参日志

    概述

    请求日志几乎是所有大型企业级项目的必要的模块,请求日志对于我们来说后期在项目运行上线一段时间用于排除异常、请求分流处理、限制流量等。

    请求日志一般都会记录请求参数、请求地址、请求状态(Status Code)、SessionId、请求方法方式(Method)、请求时间、客户端IP地址、请求返回内容、耗时等等。如果你得系统还有其他个性化的配置,也可以完成记录。

    记录请求参数时,由于servlet.getInputStream的数据只能读取一次,因此需要先把数据缓存下来,构造返回流,保证之后的Controller可以正常读取到请求体的数据。

    方案思路

    • 封装HttpServletRequest请求类,改类在构造方法中将请求的输入流中的数据缓存了起来,保证之后的处理可以重复读取输入流中的数据。
    • 实现过滤器,把上步封装的请求类传下去,保证Controller可以正常读取输入流中的数据。
    • 添加拦截器,读取输入流中的数据。
    • 读取返回参数。

    封装HttpServletRequest请求

    package com.example.demo.intercept;
    import javax.servlet.ReadListener;
    import javax.servlet.ServletInputStream;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletRequestWrapper;
    import java.io.*;
    /**
     * @author 
     * @date 封装HttpServletRequest请求
     */
    public class RequestWrapper extends HttpServletRequestWrapper {
        private final String body;
        public RequestWrapper(HttpServletRequest request) {
            super(request);
            StringBuilder stringBuilder = new StringBuilder();
            BufferedReader bufferedReader = null;
            InputStream inputStream = null;
            try {
                inputStream = request.getInputStream();
                if (inputStream != null) {
                    bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
                    char[] charBuffer = new char[128];
                    int bytesBody = -1;
                    while ((bytesBody = bufferedReader.read(charBuffer)) > 0) {
                        stringBuilder.append(charBuffer, 0, bytesBody);
                    }
                } else {
                    stringBuilder.append("");
                }
            } catch (IOException e) {
            } finally {
                if (inputStream != null) {
                    try {
                        inputStream.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
                if (bufferedReader != null) {
                    try {
                        bufferedReader.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }
            body = stringBuilder.toString();
        }
        @Override
        public ServletInputStream getInputStream() throws IOException {
            final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());
            ServletInputStream servletInputStream = new ServletInputStream() {
                @Override
                public boolean isFinished() {
                    return false;
                }
                @Override
                public boolean isReady() {
                    return false;
                }
                @Override
                public void setReadListener(ReadListener readListener) {
                }
                @Override
                public int read() throws IOException {
                    return byteArrayInputStream.read();
                }
            };
            return servletInputStream;
        }
        @Override
        public BufferedReader getReader() throws IOException {
            return new BufferedReader(new InputStreamReader(this.getInputStream()));
        }
        public String getBody() {
            return this.body;
        }
    }

    把可重复读请求体通过过滤器往下传

    防止请求流读取一次后就没有了,之后的不管是过滤器、拦截器、处理器都是读的已经缓存好的数据,实现可重复读。

    package com.example.demo.intercept;
    import org.springframework.stereotype.Component;
    import javax.servlet.*;
    import javax.servlet.annotation.WebFilter;
    import javax.servlet.http.HttpServletRequest;
    import java.io.IOException;
    /**
     * @author 
     * @date 防止请求流读取一次后就没有了
     */
    @Component
    @WebFilter(urlPatterns = "/**")
    public class RecordChannelFilter implements Filter {
        @Override
        public void init(FilterConfig filterConfig) throws ServletException {
        }
        @Override
        public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
            ServletRequest request = null;
            if (servletRequest instanceof HttpServletRequest) {
                request = new RequestWrapper((HttpServletRequest) servletRequest);
            }
            if (request ==null){
                //防止流读取一次就没有了,将流传递下去
                filterChain.doFilter(servletRequest,servletResponse);
            }else {
                filterChain.doFilter(request,servletResponse);
            }
        }
        @Override
        public void destroy() {
        }
    }

    记录入参日志

    实现入参记录拦截器

    通过拦截器的方式实现用户入参记录。

    package com.example.demo.intercept;
    import com.alibaba.fastjson.JSONObject;
    import org.bouncycastle.util.encoders.Base64;
    import org.springframework.stereotype.Component;
    import org.springframework.web.servlet.HandlerInterceptor;
    import io.jsonwebtoken.Claims;
    import javax.annotation.Resource;
    import javax.crypto.spec.SecretKeySpec;
    import javax.servlet.http.Cookie;
    import javax.servlet.http.HttpServletRequest;
    import javax.servlet.http.HttpServletResponse;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    /**
     * @author 
     * @date 记录用户操作记录入参
     */
    @Component
    public class OperationLogInterceptor implements HandlerInterceptor {
        /**
         * Jwt secert串,需要与加密token的秘钥一致
         */
        public static final String JWT_SECERT = "23142d7a9s7d66970ad07d8sa";
        /**
         * 需要记录的接口URL
         */
        private static List<String> pathList = new ArrayList<>();
        static {
            pathList.add("/mdms/model");
        }
        @Resource
        private FunctionDOMapper functionDOMapper;//菜单动能sql
        @Resource
        private UserOperationHistoryDOMapper historyDOMapper;//操作日志记录表
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
            String servletPath = "" + request.getServletPath();
            String method = request.getMethod();
            pathList.forEach(path -> {
                if (servletPath.contains(path)){
                    Cookie[] cookies = request.getCookies();
                    if (cookies != null) {
                        for (Cookie cookie : cookies) {
                            //获取token在请求中
                            if (cookie.getName().equals("_qjt_ac_")) {
                                String token = cookie.getValue();
                                /**解密token**/
                                byte[] encodeKey = Base64.decode(JWT_SECERT);
                                Claims claims = null;
                                try {
                                    SecretKeySpec keySpec = new SecretKeySpec(encodeKey, 0, encodeKey.length, "AES");
                                    claims = Jwts.parser().setSigningKey(keySpec).parseClaimsJws(token).getBody();
                                } catch (Exception e) {
                                    return;
                                }
                                //用户账号
                                String account = claims.getSubject();
                                //查询URL在功能表中的功能
                                functionDOMapper.selectOne(servletPath, method);
                                //获取入参
                                RequestWrapper requestWrapper = null;
                                if (request instanceof HttpServletRequest) {
                                    requestWrapper = new RequestWrapper(request);
                                }
                                Map<String,Object> map = new HashMap<>();
                                map.put("parameter", JSONObject.parse(requestWrapper.getBody()));
                                historyDOMapper.insert(map);//将操作信息入库
                            }
                        }
                    } 
                }
            });
            return true;
        }
    }

    注册拦截器

    package com.example.demo.config;
    import com.example.demo.intercept.OperationLogInterceptor;
    import org.springframework.context.annotation.Bean;
    import org.springframework.web.servlet.HandlerInterceptor;
    import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
    import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
    /**
     * @author 
     * @date 注册拦截器
     */
    public class WebConfig implements WebMvcConfigurer {
        @Bean
        public HandlerInterceptor getOperationLogInterceptor() {
            return new OperationLogInterceptor();
        }
        @Override
        public void addInterceptors(InterceptorRegistry registry){
            registry.addInterceptor(getOperationLogInterceptor()).addPathPatterns("/**").excludePathPatterns();
        }
    }

    记录返参日志

    package com.example.demo.intercept;
    import com.alibaba.fastjson.JSONObject;
    import org.bouncycastle.util.encoders.Base64;
    import org.springframework.core.MethodParameter;
    import org.springframework.http.HttpHeaders;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.server.ServerHttpRequest;
    import org.springframework.http.server.ServerHttpResponse;
    import org.springframework.util.CollectionUtils;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
    import javax.annotation.Resource;
    import javax.crypto.spec.SecretKeySpec;
    import javax.servlet.http.HttpServletRequest;
    import javax.xml.ws.Response;
    import java.util.*;
    /**
     * @author 
     * @date 记录用户操作日志返参
     */
    @ControllerAdvice(basePackages = "项目包")
    public class GetResponseBody implements ResponseBodyAdvice<Object> {
        /**
         * Jwt secert串,需要与加密token的秘钥一致
         */
        public static final String JWT_SECERT = "23142d7a9s7d66970ad07d8sa";
        /**
         * 需要记录的接口URL
         */
        private static List<String> pathList = new ArrayList<>();
        static {
            pathList.add("/mdms/model");
        }
        @Resource
        private FunctionDOMapper functionDOMapper;//菜单动能sql
        @Resource
        private UserOperationHistoryDOMapper historyDOMapper;//操作日志记录表
        @Override
        public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> aClass) {
            return false;
        }
        @Override
        public Object beforeBodyWrite(Object body, MethodParameter methodParameter, MediaType mediaType, Class<? extends HttpMessageConverter<?>> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) {
            String path = serverHttpRequest.getURI().getPath();
            String methodValue = serverHttpRequest.getMethodValue();
            pathList.forEach(serverPath -> {
                if (path.contains(serverPath)) {
                    HashMap<String, String> cookieMap = new HashMap<>();
                    HttpHeaders headers = serverHttpRequest.getHeaders();
                    List<String> cookieList = headers.get("cookie");
                    if (CollectionUtils.isEmpty(cookieList)) {
                        return;
                    }
                    String replaceAll = cookieList.get(0).replaceAll(";", "").replaceAll(";", "");
                    String[] split = replaceAll.split(";");
                    for (String cookie : split) {
                        String[] param = cookie.split("=");
                        cookieMap.put(param[0], param[1]);
                    }
                    String token = cookieMap.get("_qjt_ac_");
                    byte[] encodeKey = Base64.decode(JWT_SECERT);
                    Claims claims = null;
                    try {
                        SecretKeySpec keySpec = new SecretKeySpec(encodeKey, 0, encodeKey.length, "AES");
                        claims = Jwts.parser().setSigningKey(keySpec).parseClaimsJws(token).getBody();
                    } catch (Exception e) {
                        return;
                    }
                    //用户账号
                    String account = claims.getSubject();
                    //查询URL在功能表中的功能
                    functionDOMapper.selectOne(servletPath, method);
                    //获取返参
                    List<Object> list = historyDOMapper.select("功能表参数", account);
                    list.sort((Object1,Object2)->Object2.getTime().compareTo(Object1.getTime()));//将查询到的操作记录按时间降序排列
                    Object history = list.get(0);
                    if (body instanceof Response) {
                        Response response = (Response) body;
                        JSONObject jsonObject = JSONObject.parseObject(history.getParam());
                        jsonObject.put("message",response.getMessage());
                        jsonObject.put("body",response.getData());
                        history.setParam(jsonObject.toString());
                        history.setDes(response.getMessage());
                    }
                    historyDOMapper.updateByPrimaryKeySelective(history);//将操作信息更新
                }
            });
            return body;
        }
    }

    以上为个人经验,希望能给大家一个参考,也希望大家多多支持自由互联。 

    上一篇:Java设计模式之外观模式
    下一篇:没有了
    网友评论