springboot打印接口調(diào)用日志的實(shí)例
概述
請求日志幾乎是所有大型企業(yè)級項(xiàng)目的必要的模塊,請求日志對于我們來說后期在項(xiàng)目運(yùn)行上線一段時(shí)間用于排除異常、請求分流處理、限制流量等。
請求日志一般都會記錄請求參數(shù)、請求地址、請求狀態(tài)(Status Code)、SessionId、請求方法方式(Method)、請求時(shí)間、客戶端IP地址、請求返回內(nèi)容、耗時(shí)等等。如果你得系統(tǒng)還有其他個(gè)性化的配置,也可以完成記錄。
記錄請求參數(shù)時(shí),由于servlet.getInputStream的數(shù)據(jù)只能讀取一次,因此需要先把數(shù)據(jù)緩存下來,構(gòu)造返回流,保證之后的Controller可以正常讀取到請求體的數(shù)據(jù)。
方案思路
- 封裝HttpServletRequest請求類,改類在構(gòu)造方法中將請求的輸入流中的數(shù)據(jù)緩存了起來,保證之后的處理可以重復(fù)讀取輸入流中的數(shù)據(jù)。
- 實(shí)現(xiàn)過濾器,把上步封裝的請求類傳下去,保證Controller可以正常讀取輸入流中的數(shù)據(jù)。
- 添加攔截器,讀取輸入流中的數(shù)據(jù)。
- 讀取返回參數(shù)。
封裝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;
? ? }
}把可重復(fù)讀請求體通過過濾器往下傳
防止請求流讀取一次后就沒有了,之后的不管是過濾器、攔截器、處理器都是讀的已經(jīng)緩存好的數(shù)據(jù),實(shí)現(xiàn)可重復(fù)讀。
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() {
? ? }
}記錄入?yún)⑷罩?/h2>
實(shí)現(xiàn)入?yún)⒂涗洈r截器
通過攔截器的方式實(shí)現(xiàn)用戶入?yún)⒂涗洝?/p>
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 記錄用戶操作記錄入?yún)?
?*/
@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);
? ? ? ? ? ? ? ? ? ? ? ? ? ? //獲取入?yún)?
? ? ? ? ? ? ? ? ? ? ? ? ? ? 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 = "項(xiàng)目包")
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("功能表參數(shù)", account);
? ? ? ? ? ? ? ? list.sort((Object1,Object2)->Object2.getTime().compareTo(Object1.getTime()));//將查詢到的操作記錄按時(shí)間降序排列
? ? ? ? ? ? ? ? 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;
? ? }
}以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
- springboot整合mybatis將sql打印到日志的實(shí)例詳解
- springboot配置aop切面日志打印過程解析
- SpringBoot AOP處理請求日志打印功能代碼實(shí)例
- springboot+mybatis配置控制臺打印sql日志的方法
- springboot下mybatis-plus如何打印sql日志和參數(shù)到日志文件
- SpringBoot項(xiàng)目實(shí)現(xiàn)日志打印SQL的常用方法(包括SQL語句和參數(shù))
- 在SpringBoot框架中實(shí)現(xiàn)打印響應(yīng)的日志
- SpringBoot整合MyBatis和MyBatis-Plus請求后不打印sql日志的問題解決
相關(guān)文章
Java設(shè)計(jì)模式之訪問者模式使用場景及代碼示例
這篇文章主要介紹了Java設(shè)計(jì)模式之訪問者模式使用場景及代碼示例,小編覺得還是挺不錯(cuò)的,這里分享給大家,供需要的朋友參考。2017-11-11
Springboot教程之如何設(shè)置springboot熱重啟
這篇文章主要介紹了Springboot教程之如何設(shè)置springboot熱重啟,本文通過實(shí)例圖文相結(jié)合給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-07-07
mybatis 如何判斷l(xiāng)ist集合是否包含指定數(shù)據(jù)
這篇文章主要介紹了mybatis 判斷l(xiāng)ist集合是否包含指定數(shù)據(jù)的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
SpringBoot?@InitBinder注解綁定請求參數(shù)的過程詳解
這篇文章主要介紹了SpringBoot?@InitBinder注解綁定請求參數(shù),本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-04-04
基于Springboot實(shí)現(xiàn)定時(shí)發(fā)送郵件功能
這篇文章主要為大家詳細(xì)介紹了基于Springboot實(shí)現(xiàn)定時(shí)發(fā)送郵件功能的相關(guān)知識,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-03-03

