diff --git a/pom.xml b/pom.xml index 55899e5..b13688f 100644 --- a/pom.xml +++ b/pom.xml @@ -5,8 +5,8 @@ 4.0.0 cn.zhangdeman - exception - 1.0-SNAPSHOT + springboot-common + 0.0.1-SNAPSHOT 21 @@ -19,11 +19,6 @@ spring-context 6.1.14 - - org.projectlombok - lombok - 1.18.30 - jakarta.servlet jakarta.servlet-api @@ -36,6 +31,38 @@ 4.13.2 test + + org.projectlombok + lombok + 1.18.38 + provided + + + org.slf4j + slf4j-api + 2.0.17 + + + + com.fasterxml.jackson.core + jackson-core + 2.19.0 + + + com.fasterxml.jackson.core + jackson-databind + 2.19.0 + + + com.fasterxml.jackson.core + jackson-annotations + 2.19.0 + + + org.springframework.boot + spring-boot-starter-web + 3.4.7-SNAPSHOT + diff --git a/src/main/java/cn/zhangdeman/filter/HttpField.java b/src/main/java/cn/zhangdeman/filter/HttpField.java new file mode 100644 index 0000000..e25465a --- /dev/null +++ b/src/main/java/cn/zhangdeman/filter/HttpField.java @@ -0,0 +1,9 @@ +package cn.zhangdeman.filter; + +// http 一些枚举key +public class HttpField { + private static final String HEADER_TRACE_ID = "X-Front-Trace-Id"; // 透传过来的trace id + private static final String HEADER_CLIENT_IP = "X-Client-Ip"; // 请求来源IP + private static final String HEADER_USER_AGENT = "User-Agent"; // 请求来源 ua + private static final String HEADER_AUTHORIZATION = "Authorization"; // 请求来源 authorization +} diff --git a/src/main/java/cn/zhangdeman/filter/LogField.java b/src/main/java/cn/zhangdeman/filter/LogField.java new file mode 100644 index 0000000..cec5bdc --- /dev/null +++ b/src/main/java/cn/zhangdeman/filter/LogField.java @@ -0,0 +1,22 @@ +package cn.zhangdeman.filter; + +// 日志相关数据的枚举值 +public class LogField { + // 定义日志类型 + public static final String LOG_TYPE_ACCESS = "access"; // 服务请求记录 + public static final String LOG_TYPE_INPUT = "input"; // 请求输入信息 + public static final String LOG_TYPE_OUTPUT = "output"; // 请求输出信息 + public static final String LOG_TYPE_API = "api"; // 接口请求记录 + public static final String LOG_TYPE_MONITOR = "monitor"; // 系统监控 + public static final String LOG_TYPE_DATABASE = "database"; // 数据库 + public static final String LOG_TYPE_REDIS = "redis"; // redis日志 + public static final String LOG_TYPE_KAFKA = "kafka"; // kafka日志 + // 定义各种日志文件名称 + public static final String LOG_ACCESS_NAME = "access.log"; // 访问日志 + public static final String LOG_REQUEST_NAME = "request.log"; // 请求日志 + public static final String LOG_API_NAME = "api.log"; // 三方调用日志 + public static final String LOG_MONITOR_NAME = "monitor.log"; // 监控日志 + public static final String LOG_DATABASE_NAME = "database.log"; // 数据库日志 + public static final String LOG_CACHE_NAME = "cache.log"; // 缓存日志 + public static final String LOG_MESSAGE_NAME = "message.log"; // 消息队列日志 +} diff --git a/src/main/java/cn/zhangdeman/filter/Main.java b/src/main/java/cn/zhangdeman/filter/Main.java new file mode 100644 index 0000000..5c853b6 --- /dev/null +++ b/src/main/java/cn/zhangdeman/filter/Main.java @@ -0,0 +1,7 @@ +package cn.zhangdeman.filter; + + +public class Main { + public static void main(String[] args) { + } +} \ No newline at end of file diff --git a/src/main/java/cn/zhangdeman/filter/Ordered.java b/src/main/java/cn/zhangdeman/filter/Ordered.java new file mode 100644 index 0000000..da9676f --- /dev/null +++ b/src/main/java/cn/zhangdeman/filter/Ordered.java @@ -0,0 +1,5 @@ +package cn.zhangdeman.filter; + +public class Ordered { + public static final int REQUEST_ID = -10000; // 请求ID生成过滤器 +} diff --git a/src/main/java/cn/zhangdeman/filter/RecordField.java b/src/main/java/cn/zhangdeman/filter/RecordField.java new file mode 100644 index 0000000..21f765c --- /dev/null +++ b/src/main/java/cn/zhangdeman/filter/RecordField.java @@ -0,0 +1,37 @@ +package cn.zhangdeman.filter; + +// 记录的各种字段枚举值 +public class RecordField { + public static final String TRACE_ID = "trace_id"; // trace_id + public static final String REQUEST_ID = "request_id"; // request_id + public static final String START_TIMESTAMP = "start_timestamp"; // 开始请求时间 + public static final String FINISH_TIMESTAMP = "finish_timestamp"; // 完成请求时间 + public static final String SERVER_IP = "server_ip"; // 服务器IP + public static final String SERVER_HOSTNAME = "server_hostname"; // 服务器名称 + public static final String COST = "cost"; // 请求耗时 + public static final String CONTEXT_DATA = "context_data"; // 本条日志的上下文信息 + // 请求信息 + public static final String REQUEST_INFO = "request_info"; // 请求信息 + public static final String REQUEST_UA = "user_agent"; // 请求ua + public static final String REQUEST_CLIENT_IP = "client_ip"; // 请求客户端 + public static final String REQUEST_METHOD = "method"; // 请求方法 + public static final String REQUEST_CONTENT_TYPE = "content_type"; // 请求类型 + public static final String REQUEST_QUERY = "_query"; // query参数 + public static final String REQUEST_BODY = "body"; // body参数 + public static final String REQUEST_HEADER = "header"; // header参数 + public static final String REQUEST_COOKIE = "cookie"; // cookie参数 + public static final String REQUEST_PATH_PARAM = "path_param"; // path参数 + public static final String REQUEST_SIZE = "size"; // 参数大小 + public static final String REQUEST_URI = "uri"; // 请求接口 + + public static final String RUNTIME_THREAD_CONTEXT = "runtime_thread_context"; // 运行时线程上下文 + + public static final String RESPONSE_INFO = "response_data"; // 接口响应数据 + public static final String RESPONSE_CODE = "code"; // 接口响应数据状态码 + public static final String RESPONSE_MESSAGE = "message"; // 接口响应数据状态码描述 + public static final String RESPONSE_DATA = "data"; // 接口响应数据 + public static final String RESPONSE_ERROR_DETAIL = "err_detail"; // 异常详细信息 + public static final String RESPONSE_HTTP_CODE = "http_code"; // 苍蝇状态码,默认200 + public static final String RESPONSE_HEADER = "header"; // 响应header + public static final String RESPONSE_COOKIE = "cookie"; // 响应header +} diff --git a/src/main/java/cn/zhangdeman/filter/Request.java b/src/main/java/cn/zhangdeman/filter/Request.java new file mode 100644 index 0000000..3c25e1c --- /dev/null +++ b/src/main/java/cn/zhangdeman/filter/Request.java @@ -0,0 +1,41 @@ +package cn.zhangdeman.filter; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.servlet.http.HttpServletRequest; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; + +// 请求相关数据 +@Getter +@Setter +@JsonIgnoreProperties(ignoreUnknown = true) // 忽略未知属性字段 +public class Request { + @JsonIgnore + HttpServletRequest httpServletRequest;// 原始请求 + @JsonProperty(RecordField.REQUEST_UA) + private String userAgent; // 客户端ua + @JsonProperty(RecordField.REQUEST_CLIENT_IP) + private String clientIp; // 客户端IP + @JsonProperty(RecordField.REQUEST_METHOD) + private String requestMethod; // 请求方法 + @JsonProperty(RecordField.REQUEST_CONTENT_TYPE) + private String requestContentType; // 请求类型 + @JsonProperty(RecordField.REQUEST_QUERY) + private Map requestQuery; // 请求query + @JsonProperty(RecordField.REQUEST_HEADER) + private Map requestHeader; // 请求header + @JsonProperty(RecordField.REQUEST_COOKIE) + private Map requestCookie; // 请求cookie + @JsonProperty(RecordField.REQUEST_PATH_PARAM) + private Map requestPathParam; // path 参数 + @JsonProperty(RecordField.REQUEST_BODY) + private String requestBody; // 请求body + @JsonIgnore + private byte[] requestBodyRaw; // 原始请求body + @JsonProperty(RecordField.REQUEST_URI) + private String requestUri; // 请求接口 +} diff --git a/src/main/java/cn/zhangdeman/filter/Response.java b/src/main/java/cn/zhangdeman/filter/Response.java new file mode 100644 index 0000000..cb0e54f --- /dev/null +++ b/src/main/java/cn/zhangdeman/filter/Response.java @@ -0,0 +1,48 @@ +package cn.zhangdeman.filter; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.servlet.http.HttpServletResponse; +import lombok.Getter; +import lombok.Setter; + +import java.util.HashMap; +import java.util.Map; + +// 响应数据 +@Getter +@Setter +@JsonIgnoreProperties(ignoreUnknown = true) // 忽略未知属性字段 +public class Response { + @JsonProperty(RecordField.RESPONSE_CODE) + private String code; // 响应业务状态码 + @JsonProperty(RecordField.RESPONSE_MESSAGE) + private String message; // 响应消息 + @JsonProperty(RecordField.RESPONSE_DATA) + private Object data; // 响应数据 + @JsonProperty(RecordField.RESPONSE_ERROR_DETAIL) + private Object errorDetail; // 异常详情 + @JsonProperty(RecordField.RESPONSE_HTTP_CODE) + private Integer httpCode = 200; // 响应http状态码 + @JsonProperty(RecordField.RESPONSE_HEADER) + private Map header; // 响应头 + @JsonProperty(RecordField.RESPONSE_COOKIE) + private Map cookie; // 响应cookie + @JsonIgnore + private HttpServletResponse httpServletResponse; // 响应实例 + + public void addHeader(String key, String value) { + if (null == this.header) { + this.header = new HashMap<>(); + } + this.header.put(key, value); + } + + public void addCookie(String key, String value) { + if (null == this.cookie) { + this.cookie = new HashMap<>(); + } + this.cookie.put(key, value); + } +} diff --git a/src/main/java/cn/zhangdeman/filter/RuntimeContext.java b/src/main/java/cn/zhangdeman/filter/RuntimeContext.java new file mode 100644 index 0000000..a654311 --- /dev/null +++ b/src/main/java/cn/zhangdeman/filter/RuntimeContext.java @@ -0,0 +1,62 @@ +package cn.zhangdeman.filter; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.Getter; +import lombok.Setter; + +import java.io.Serializable; +import java.util.Map; + +// 初始化的请求信息 +@Getter +@Setter +@JsonIgnoreProperties(ignoreUnknown = true) // 忽略未知属性字段 +public class RuntimeContext implements Serializable { + @JsonProperty(RecordField.REQUEST_ID) + private String requestId; // 本次请求request id + @JsonProperty(RecordField.COST) + private Long cost; // 请求耗时 + @JsonProperty(RecordField.SERVER_IP) + private String serverIp; // 服务器Ip + @JsonProperty(RecordField.SERVER_HOSTNAME) + private String serverHostname; // 服务器hostname + @JsonProperty(RecordField.FINISH_TIMESTAMP) + private Long finishTimeStamp; // 完成请求时间 + @JsonProperty(RecordField.TRACE_ID) + private String traceId; // 全局trace id + @JsonProperty(RecordField.START_TIMESTAMP) + private Long startTimeStamp; // 开始请求时间 + @JsonProperty(RecordField.CONTEXT_DATA) + private Map logData; // 本条日志的上下文信息 + @JsonProperty(RecordField.REQUEST_INFO) + private Request requestInfo;// 请求信息 + @JsonProperty(RecordField.RESPONSE_INFO) + private Response responseInfo; // 响应信息 + + // 序列化 + @Override + public String toString() { + ObjectMapper mapper = new ObjectMapper(); + try { + return mapper.writeValueAsString(this); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + // 序列化 + public String serializable() { + return toString(); + } + + // 反序列化 + public RuntimeContext unserializable(String json) throws JsonProcessingException { + ObjectMapper mapper = new ObjectMapper(); + return mapper.readValue(json, new TypeReference() { + }); + } +} diff --git a/src/main/java/cn/zhangdeman/filter/filter/RequestInitFilter.java b/src/main/java/cn/zhangdeman/filter/filter/RequestInitFilter.java new file mode 100644 index 0000000..d182740 --- /dev/null +++ b/src/main/java/cn/zhangdeman/filter/filter/RequestInitFilter.java @@ -0,0 +1,129 @@ +package cn.zhangdeman.filter; + + +import jakarta.servlet.FilterChain; +import jakarta.servlet.FilterConfig; +import jakarta.servlet.ServletException; +import jakarta.servlet.ServletInputStream; +import jakarta.servlet.annotation.WebFilter; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpFilter; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.core.annotation.Order; + +import java.io.IOException; +import java.util.Arrays; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; + + +// 初始化请求 +@WebFilter("/*") // 所有接口请求均会触发次过滤器 +@Order(Ordered.REQUEST_ID) // 足够小, 保证最先执行 +public class RequestInitFilter extends HttpFilter { + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + } + + @Override + public void destroy() { + } + + @Override + public void doFilter(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws IOException, ServletException { + RuntimeContext runtimeContext = new RuntimeContext(); // 运行时上下文数据 + setBaseInfo(httpServletRequest,httpServletResponse, runtimeContext); // 设置基础信息 + setRequestQuery(runtimeContext.getRequestInfo()); // 设置请求query信息 + setRequestBody(runtimeContext.getRequestInfo()); // 设置请求Body + setRequestHeaderAndCookie(runtimeContext.getRequestInfo()); // 填充请求信息: header + cookie + setRequestId(runtimeContext); // 设置请求ID, 每次请求需要重新生成 + httpServletRequest.setAttribute(RecordField.RUNTIME_THREAD_CONTEXT, runtimeContext); // 记录到请求上下文中 + // 继续向后执行 + filterChain.doFilter(httpServletRequest, httpServletResponse); + System.out.println("init request do filter 请求执行完成"); + } + + // 设置基础信息 + private void setBaseInfo(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, RuntimeContext runtimeContext) { + // request 信息 + Request request = new Request(); + request.setHttpServletRequest(httpServletRequest); + request.setClientIp(httpServletRequest.getRemoteAddr()); // client ip + request.setRequestMethod(httpServletRequest.getMethod()); // 请求类型 + request.setRequestContentType(httpServletRequest.getContentType()); // 请求类型 + request.setRequestUri(httpServletRequest.getRequestURI()); // 请求uri + runtimeContext.setRequestInfo(request); // 请求信息 + // server 信息 + runtimeContext.setServerIp(httpServletRequest.getLocalAddr()); // 服务器IP + runtimeContext.setServerHostname(httpServletRequest.getLocalName()); // 服务器名称 + runtimeContext.setStartTimeStamp(System.currentTimeMillis()); // 开始请求时间 + // 响应信息 + Response response = new Response(); + response.setHttpServletResponse(httpServletResponse); + response.setHeader(new HashMap<>()); + response.setCookie(new HashMap<>()); + runtimeContext.setResponseInfo(response); // 响应信息 + } + + // 设置请求Query + private void setRequestQuery(Request request) { + Map query = new HashMap<>(); + HttpServletRequest httpServletRequest = request.getHttpServletRequest(); // 原始请求实例 + Map queryParamList = httpServletRequest.getParameterMap(); // query参数列表 + for (Map.Entry entity : queryParamList.entrySet()) { + if (entity.getValue().length == 0) { + // 参数值为空 + query.put(entity.getKey(), ""); + } else { + // 多个参数值, 取第一个 + query.put(entity.getKey(), entity.getValue()[0]); + } + } + request.setRequestQuery(query); + } + + // 设置请求Body + private void setRequestBody(Request request) throws IOException { + HttpServletRequest httpServletRequest = request.getHttpServletRequest(); // 原始请求实例 + ServletInputStream servletInputStream = httpServletRequest.getInputStream(); + byte[] requestBody = servletInputStream.readAllBytes(); + request.setRequestBodyRaw(requestBody); // 原始请求Body + request.setRequestBody(Arrays.toString(requestBody)); // 请求body + } + + + // 设置请求信息: header + cookie + private void setRequestHeaderAndCookie(Request request) { + HttpServletRequest httpServletRequest = request.getHttpServletRequest(); // 原始请求实例 + + request.setRequestHeader(new HashMap<>()); // 请求header + request.setRequestCookie(new HashMap<>()); // 请求cookie + // 全部请求header + Enumeration enumeration = httpServletRequest.getHeaderNames(); + while (enumeration.hasMoreElements()) { + String name = enumeration.nextElement(); + if (name.equalsIgnoreCase("cookie")) { + // cookie单独摘出来处理 + continue; + } + String value = httpServletRequest.getHeader(name); + request.getRequestHeader().put(name, value); + } + // 全部请求cookie + Cookie[] cookieList = httpServletRequest.getCookies(); + for (Cookie cookie : cookieList) { + request.getRequestCookie().put(cookie.getName(), cookie.getValue()); + } + } + + // 设置请求ID + private void setRequestId(RuntimeContext runtimeContext) { + } + + // 响应信息 + private void setResponse(Response response) { + } +}