目录 概述 方案思路 封装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; } }
以上为个人经验,希望能给大家一个参考,也希望大家多多支持自由互联。