日志输出-第三章-接口级出入参输出完整数据的实现

embedded/2024/9/25 4:11:47/

在这里插入图片描述

文章目录

  • 日志输出-第三章-接口级出入参输出完整数据的实现
    • 一、概述
    • 二、如何输出 Request 的 body
      • 2.1、工具类
      • 2.2、包装类
      • 2.3、如何使用
    • 三、如何输出 Response 的 body
      • 3.1、包装类
      • 3.2、如何使用

日志输出-第三章-接口级出入参输出完整数据的实现

前置内容

  1. 日志输出指南
  2. 日志输出-第二章-接口级出入参的实现

一、概述

上一章贴了日志出入参的代码,在第三节的内容中描述了为什么没有输出 body 内容的原因(我个人认为上一章的方案是最优解),本章的教程主要是如何输出 body 中的数据。

正常情况下也是拿来做数据加解密处理之类的

二、如何输出 Request 的 body

这一块的主要问题在于流不可重复读,所以我们通过对 ServletRequest 进行包装的方式,来达到重复读取流的效果。

但是弊端也会很明显,body 的数据会被拷贝多份,一方面内存压力会增大,另一方面性能损耗也不小,所以一般在日志中很少会输出 Requestbody 的数据。

2.1、工具类

java">package com.lzl.study.scaffold.studyscaffold.common.log;import lombok.extern.slf4j.Slf4j;import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;/*** @ClassName RequestResponseUtil* @Author lizelin* @Description request、response 工具类* @Date 2024/5/27 18:26* @Version 1.0*/
@Slf4j
public abstract class RequestResponseUtil {/*** @Param request* @Return java.lang.String* @Description 获取 Request 中 Body* @Author lizelin* @Date 2023/11/3 16:08**/public static String getRequestBody(HttpServletRequest request) {StringBuilder stringBuilder = new StringBuilder();BufferedReader bufferedReader = null;InputStream inputStream = null;InputStreamReader inputStreamReader = null;try {inputStream = request.getInputStream();if (inputStream != null) {inputStreamReader = new InputStreamReader(inputStream);bufferedReader = new BufferedReader(inputStreamReader);char[] charBuffer = new char[128];int bytesRead;while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {stringBuilder.append(charBuffer, 0, bytesRead);}}} catch (IOException e) {log.error("读取流失败:",e);} finally {close(bufferedReader, inputStream,inputStreamReader);}return stringBuilder.toString();}/*** @Param bufferedReader* @Param inputStream* @Return void* @Description 关闭流* @Author lizelin* @Date 2023/11/3 16:08**/private static void close(BufferedReader bufferedReader, InputStream inputStream,InputStreamReader inputStreamReader) {try {if (inputStream != null) {inputStream.close();}if (bufferedReader != null) {bufferedReader.close();}if (inputStreamReader != null){inputStreamReader.close();}} catch (IOException e) {log.info("流关闭失败", e);}}}

2.2、包装类

java">package com.lzl.study.scaffold.studyscaffold.common.log;import cn.hutool.core.collection.CollUtil;
import org.apache.catalina.util.ParameterMap;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;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Map;/*** @ClassName LogHttpServletRequestWrapper* @Author lizelin* @Description 日志 Http Servlet 请求包装器* @Date 2024/5/27 18:26* @Version 1.0*/
public class LogHttpServletRequestWrapper extends HttpServletRequestWrapper {/*** 所有参数的 Map 集合*/private Map<String, String[]> parameterMap;/*** 存储 body 数据的容器(这里存储为解析流后的JSON)*/private String body;/*** @Param* @Return java.lang.String* @Description 获取Body* @Author lizelin* @Date 2023/11/3 16:09**/public String getBody() {return this.body;}/*** @Param body* @Return void* @Description 修改 body* @Author lizelin* @Date 2023/11/3 16:09**/public void setBody(String body) {this.body = body;}public LogHttpServletRequestWrapper(HttpServletRequest request) {super(request);// 给参数集合赋值parameterMap = request.getParameterMap();// 获取Bodybody = RequestResponseUtil.getRequestBody(request);}/*** @Param parameterMap* @Return void* @Description 替换整个参数 Map* @Author lizelin* @Date 2023/11/3 14:59**/public void setParameterMap(Map<String, String[]> parameterMap) {this.parameterMap = parameterMap;}/*** @Param key* @Param value* @Return void* @Description 向参数集合中添加参数* @Author lizelin* @Date 2023/11/3 14:59**/public void putParameterMap(String key, String[] value) {if (this.parameterMap instanceof ParameterMap) {((ParameterMap<String, String[]>) this.parameterMap).setLocked(false);}this.parameterMap.put(key, value);}/*** @Param* @Return java.util.Enumeration<java.lang.String>* @Description 获取所有参数名* @Author lizelin* @Date 2023/11/3 14:59**/@Overridepublic Enumeration<String> getParameterNames() {return CollUtil.asEnumeration(parameterMap.keySet().iterator());}/*** @Param name* @Return java.lang.String* @Description 获取指定参数名的值,如果有重复的参数名,则返回第一个的值 接收一般变量 ,如 text 类型* @Author lizelin* @Date 2023/11/3 14:59**/@Overridepublic String getParameter(String name) {ArrayList<String> values = CollUtil.toList(parameterMap.get(name));if (CollUtil.isNotEmpty(values)) {return values.get(0);} else {return null;}}/*** @Param name* @Return java.lang.String[]* @Description 获取单个的某个 key 的 value* @Author lizelin* @Date 2023/11/3 14:58**/@Overridepublic String[] getParameterValues(String name) {return parameterMap.get(name);}/*** @Param* @Return java.util.Map<java.lang.String, java.lang.String [ ]>* @Description 获取值列表* @Author lizelin* @Date 2023/11/3 14:58**/@Overridepublic Map<String, String[]> getParameterMap() {return parameterMap;}/*** @Param* @Return java.lang.String* @Description 获取 queryString* @Author lizelin* @Date 2023/11/3 14:57**/@Overridepublic String getQueryString() {return super.getQueryString();}@Overridepublic BufferedReader getReader() throws IOException {return new BufferedReader(new InputStreamReader(this.getInputStream()));}/*** @Param* @Return javax.servlet.ServletInputStream* @Description 重写获取输入流,因为在输出日志的时候会读取输入流,而流只能读取一次,所以在向后传递的时候就需要做特殊处理* @Author lizelin* @Date 2023/11/3 14:55**/@Overridepublic ServletInputStream getInputStream() throws IOException {final ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(body.getBytes());return new ServletInputStream() {@Overridepublic boolean isFinished() {return false;}@Overridepublic boolean isReady() {return false;}@Overridepublic void setReadListener(ReadListener listener) {//不需要重写}@Overridepublic int read() {//重写读return byteArrayInputStream.read();}};}
}

2.3、如何使用

做一个转换就完事了

java">/*** @ClassName LogHandlerInterceptor* @Author lizelin* @Description 日志请求入参过滤器* @Date 2023/11/3 12:15* @Version 1.0*/
@Slf4j
@Component
@AllArgsConstructor
public class LogRecordRequestFilter implements Filter, Ordered {@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {HttpServletRequest httpServletRequest = (HttpServletRequest) request;//这里主要是由于流不可重复读取,采用包装类的方式LogHttpServletRequestWrapper requestWrapper = new LogHttpServletRequestWrapper(httpServletRequest);String requestId = requestWrapper.getHeader("requestId");String requestWrapperBody = requestWrapper.getBody();String queryString = requestWrapper.getQueryString();String servletPath = requestWrapper.getServletPath();//输出请求日志log.info("LogRecordRequestFilter HttpServletRequest servletPath:{},requestId:{},queryString:{},body:{}", servletPath, requestId, queryString, requestWrapperBody);chain.doFilter(requestWrapper, response);}@Overridepublic int getOrder() {return Ordered.HIGHEST_PRECEDENCE;}
}

三、如何输出 Response 的 body

操作和 第二章 的内容差别不大,思路一致。

3.1、包装类

java">package com.lzl.study.scaffold.studyscaffold.common.log;import lombok.extern.slf4j.Slf4j;import javax.servlet.ServletOutputStream;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;/*** @ClassName LogHttpServletResponseWrapper* @Author lizelin* @Description 日志 Http Servlet 响应 包装器* @Date 2024/5/27 18:25* @Version 1.0*/
@Slf4j
public class LogHttpServletResponseWrapper extends HttpServletResponseWrapper {private ByteArrayOutputStream byteArrayOutputStream = null;private ServletOutputStream servletOutputStream = null;private PrintWriter printWriter = null;public LogHttpServletResponseWrapper(HttpServletResponse response) throws IOException {super(response);byteArrayOutputStream = new ByteArrayOutputStream();servletOutputStream = new ResponseWrapperOutputStream(byteArrayOutputStream, response);printWriter = new PrintWriter(new OutputStreamWriter(byteArrayOutputStream, this.getCharacterEncoding()));}/*** @Param* @Return javax.servlet.ServletOutputStream* @Description 获取 OutputStream* @Author lizelin* @Date 2023/11/3 16:47**/@Overridepublic ServletOutputStream getOutputStream() throws IOException {return servletOutputStream;}/*** @Param* @Return java.io.PrintWriter* @Description 获取 Writer* @Author lizelin* @Date 2023/11/3 16:46**/@Overridepublic PrintWriter getWriter() throws UnsupportedEncodingException {return printWriter;}/*** @Param* @Return void* @Description 获取 flushBuffer* @Author lizelin* @Date 2023/11/3 16:46**/@Overridepublic void flushBuffer() throws IOException {if (servletOutputStream != null) {servletOutputStream.flush();}if (printWriter != null) {printWriter.flush();}}/*** @Param* @Return void* @Description 重置流* @Author lizelin* @Date 2023/11/3 16:46**/@Overridepublic void reset() {byteArrayOutputStream.reset();}/*** @Param* @Return String* @Description 读取流中的数据* @Author lizelin* @Date 2023/11/3 16:43**/public String getBody() throws IOException {//刷新缓冲区flushBuffer();//读取流中的数据return new String(byteArrayOutputStream.toByteArray(), StandardCharsets.UTF_8);}/*** @Param* @Return* @Description 内部类,对 ServletOutputStream 进行包装,方便日志记录* @Author lizelin* @Date 2023/11/3 16:43**/private class ResponseWrapperOutputStream extends ServletOutputStream {private ByteArrayOutputStream bos = null;private HttpServletResponse response = null;public ResponseWrapperOutputStream(ByteArrayOutputStream stream, HttpServletResponse response) {bos = stream;this.response = response;}/*** @Param b* @Return void* @Description 将写暂存一份方便日志* @Author lizelin* @Date 2023/11/3 17:31**/@Overridepublic void write(int b) throws IOException {bos.write(b);response.getOutputStream().write(b);}/*** @Param b* @Return void* @Description 将写暂存一份方便日志* @Author lizelin* @Date 2023/11/3 17:32**/@Overridepublic void write(byte[] b) throws IOException {bos.write(b, 0, b.length);response.getOutputStream().write(b, 0, b.length);}@Overridepublic boolean isReady() {return false;}@Overridepublic void setWriteListener(WriteListener writeListener) {//不需要重新}}
}

3.2、如何使用

这个其实也和 2.3 的内容基本一样,我只输出一个简单示例,其他的可以自己往里面添加

java">/*** @ClassName LogHandlerInterceptor* @Author lizelin* @Description 日志请求入参过滤器* @Date 2023/11/3 12:15* @Version 1.0*/
@Slf4j
@Component
@AllArgsConstructor
public class LogRecordRequestFilter implements Filter, Ordered {private final NacosConfig nacosConfig;@Overridepublic void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {//热更新//自定义配置 headerHttpServletRequest httpServletRequest = (HttpServletRequest) request;//post put 读//todo 配置读取 body 类型为json//这里主要是由于流不可重复读取,采用包装类的方式LogHttpServletRequestWrapper requestWrapper = new LogHttpServletRequestWrapper(httpServletRequest);String requestId = requestWrapper.getHeader("requestId");String requestWrapperBody = requestWrapper.getBody();String queryString = requestWrapper.getQueryString();String servletPath = requestWrapper.getServletPath();//输出请求日志log.info("LogRecordRequestFilter HttpServletRequest servletPath:{},requestId:{},queryString:{},body:{}", servletPath, requestId, queryString, requestWrapperBody);LogHttpServletResponseWrapper responseWrapper = new LogHttpServletResponseWrapper((HttpServletResponse) response);chain.doFilter(requestWrapper, responseWrapper);//输出响应日志String responseWrapperBody = responseWrapper.getBody();log.info("LogRecordRequestFilter HttpServletResponse servletPath:{},requestId:{},body:{}",servletPath,requestId,responseWrapperBody);}@Overridepublic int getOrder() {return Ordered.HIGHEST_PRECEDENCE;}
}

如何输出 body 的内容就已经完成了,下一章内容为如何对这些数据进行加解密(单体应用的前后端数据加解密)。


http://www.ppmy.cn/embedded/44051.html

相关文章

向npm发布自己写的vue组件,使用vite创建项目

向npm发布自己写的vue组件&#xff0c;使用vite创建项目 创建项目 pnpm create vite输入项目名称 由于我的组件是基于 ant-design-vue和vue的&#xff0c;需要解析.vue文件&#xff0c;我又安装了下面4个。 然后执行 pnpm i安装依赖 vite.config.ts import { defineC…

专业的力量-在成为专家的道路上前进

专业的力量-在成为专家的道路上前进 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 现在稀缺的已不再是信息资源&#xff0c;而是运用信息的能力。过去…

微信小程序路由跳转

1. wx.navigateTo 作用&#xff1a;保留当前页面&#xff0c;跳转到应用内的某个页面。特点&#xff1a;跳转后目标页面的生命周期函数 onLoad 和 onShow 会被触发。使用场景&#xff1a;一般用于跳转到应用内的其他页面&#xff0c;保留当前页面的状态&#xff0c;例如从文章…

渗透测试工具Cobalt strike-2.CS基础使用

三、结合metasploit,反弹shell 在kali中开启使用命令开启metasploit msfconsole ┌──(root㉿oldboy)-[~] └─# msfconsole --- msf6 > use exploit/multi/handler [*] Using configured payload generic/shell_reverse_tcp --- msf6 exploit(multi/handler) > show …

vue3学习(四)

前言 接上篇学习笔记&#xff0c;分享3个内置组件&#xff1a;动态组件、缓存组件、分发组件基本用法。大家一起通过code的示例&#xff0c;从现象理解,注意再次理解生命周期。 一、code示例 组件A&#xff1a;CompA <script setup> import {onMounted, onUnmounted} f…

后端技术栈都有哪些

在后端技术领域&#xff0c;有很多不同的技术栈可供选择&#xff0c;这取决于项目的具体需求、团队的技能和经验以及所使用的框架或库的流行程度。以下是一些常见的后端技术栈的示例&#xff1a; Node.js&#xff1a; 框架&#xff1a;Express.js, Koa.js, NestJS, Hapi.js数据…

spring session+redis存储session,实现用户登录功能,并在拦截器里面判断用户session是否过期,过期就跳转到登录页面

在Spring应用中&#xff0c;使用Redis存储Session是一种常见的方式&#xff0c;可以实现分布式环境下的Session管理。以下是实现用户登录功能&#xff0c;并在拦截器中判断Session是否过期并跳转到登录页面的基本步骤&#xff1a; 添加依赖&#xff1a;首先&#xff0c;确保你的…

kotlin基础之泛型和委托

Kotlin泛型的概念及使用 泛型概念 在Kotlin中&#xff0c;泛型&#xff08;Generics&#xff09;是一种允许在类、接口和方法中使用类型参数的技术。这些类型参数在实例化类、实现接口或调用方法时会被具体的类型所替代。泛型的主要目的是提高代码的复用性、类型安全性和可读…