概述

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

请求日志一般都会记录请求参数、请求地址、请求状态(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;
    }
}

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

springboot打印接口调用日志的实例的更多相关文章

  1. Html5页面二次分享的实现

    这篇文章主要介绍了Html5页面二次分享的实现的相关资料,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧

  2. HTML5跳转小程序wx-open-launch-weapp的示例代码

    这篇文章主要介绍了HTML5跳转小程序wx-open-launch-weapp的相关知识,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

  3. ios – Objective C接口,委托和协议

    所以我试图围绕Objctive-C接口,代理和协议.所以我有一个问题:委托是否必须在单独的文件中,或者它是否是您班级中定义的方法?协议就像java接口吗?

  4. ios – watchOS错误:控制器的接口描述中的未知属性

    解决方法创建IBOutlet作为WKInterfacePicker的属性,您将不会收到消息.

  5. 泛型 – MonoTouch和支持变体通用接口

    如果是这样,MonoTouch中针对这种情况的推荐解决方法是什么?解决方法这实际上取决于编译器而不是Mono版本.IOW有些东西可能适用于Mono2.10而不适用于MonoTouch6.x.当前版本的MonoTouch附带了smcs编译器和基于2.1的配置文件.较新的功能,如协方差,需要一个完整的4.0编译器和运行时.未来版本的MonoTouch将提供4.0/4.5运行时和编译器.

  6. ios – 用于 – 在Counterparts中的ViewController.swift(接口)文件是什么

    有人可以这么善良并解释这个文件的目的是什么?

  7. ios6 – 检测UIViewController上的接口旋转,即使未在 – (NSUInteger)supportedInterfaceOrientations中定义

    解决方法尝试使用UIDevice实例来检测设备物理方向的变化.要开始接收通知,您可以使用类似的内容:要取消注册接收设备旋转事件,请使用此选项:这是deviceDidRotate函数的一个例子:

  8. 接口和扩展

    classSimpleClass:ExampleProtocol{String="Averysimpleclass."varanotherProperty:Int=120funcadjust(){simpleDescription+="Now100%adjust."}funcadd(){simpleDescription+="Now50%add."}}vara=SimpleClass()a.adjust()letaDescription=a.simpleDescriptionstructSimpleStr

  9. swift类和接口的使用

    1类的使用2接口的使用

  10. Swift学习 接口的创建与使用

    =""varage:Int!

随机推荐

  1. 基于EJB技术的商务预订系统的开发

    用EJB结构开发的应用程序是可伸缩的、事务型的、多用户安全的。总的来说,EJB是一个组件事务监控的标准服务器端的组件模型。基于EJB技术的系统结构模型EJB结构是一个服务端组件结构,是一个层次性结构,其结构模型如图1所示。图2:商务预订系统的构架EntityBean是为了现实世界的对象建造的模型,这些对象通常是数据库的一些持久记录。

  2. Java利用POI实现导入导出Excel表格

    这篇文章主要为大家详细介绍了Java利用POI实现导入导出Excel表格,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

  3. Mybatis分页插件PageHelper手写实现示例

    这篇文章主要为大家介绍了Mybatis分页插件PageHelper手写实现示例,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  4. (jsp/html)网页上嵌入播放器(常用播放器代码整理)

    网页上嵌入播放器,只要在HTML上添加以上代码就OK了,下面整理了一些常用的播放器代码,总有一款适合你,感兴趣的朋友可以参考下哈,希望对你有所帮助

  5. Java 阻塞队列BlockingQueue详解

    本文详细介绍了BlockingQueue家庭中的所有成员,包括他们各自的功能以及常见使用场景,通过实例代码介绍了Java 阻塞队列BlockingQueue的相关知识,需要的朋友可以参考下

  6. Java异常Exception详细讲解

    异常就是不正常,比如当我们身体出现了异常我们会根据身体情况选择喝开水、吃药、看病、等 异常处理方法。 java异常处理机制是我们java语言使用异常处理机制为程序提供了错误处理的能力,程序出现的错误,程序可以安全的退出,以保证程序正常的运行等

  7. Java Bean 作用域及它的几种类型介绍

    这篇文章主要介绍了Java Bean作用域及它的几种类型介绍,Spring框架作为一个管理Bean的IoC容器,那么Bean自然是Spring中的重要资源了,那Bean的作用域又是什么,接下来我们一起进入文章详细学习吧

  8. 面试突击之跨域问题的解决方案详解

    跨域问题本质是浏览器的一种保护机制,它的初衷是为了保证用户的安全,防止恶意网站窃取数据。那怎么解决这个问题呢?接下来我们一起来看

  9. Mybatis-Plus接口BaseMapper与Services使用详解

    这篇文章主要为大家介绍了Mybatis-Plus接口BaseMapper与Services使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪

  10. mybatis-plus雪花算法增强idworker的实现

    今天聊聊在mybatis-plus中引入分布式ID生成框架idworker,进一步增强实现生成分布式唯一ID,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

返回
顶部