From fe3db4cbceac88158fb4996b419fe5d91f2322ca Mon Sep 17 00:00:00 2001 From: buxue Date: Thu, 22 Jan 2026 11:07:12 +0800 Subject: [PATCH] 1 --- demo-0.0.1-SNAPSHOT.jar.original | Bin 16338 -> 0 bytes .../com/example/demo/DemoApplication.java | 14 +++ .../demo/config/LoggingFilterConfig.java | 31 ++++++ .../com/example/demo/config/WebMvcConfig.java | 21 ++++ .../demo/controller/BaseController.java | 22 ++++ .../controller/CheckNotBlankController.java | 28 +++++ .../demo/controller/FilterTestController.java | 65 ++++++++++++ .../java/com/example/demo/entity/UserDTO.java | 16 +++ .../example/demo/exception/ErrorResponse.java | 41 ++++++++ .../exception/GlobalExceptionHandler.java | 47 +++++++++ .../filter/MultiReadHttpServletFilter.java | 96 ++++++++++++++++++ src/main/resources/application.properties | 4 + src/main/resources/logback-spring.xml | 70 +++++++++++++ .../example/demo/DemoApplicationTests.java | 13 +++ 14 files changed, 468 insertions(+) delete mode 100644 demo-0.0.1-SNAPSHOT.jar.original create mode 100644 src/main/java/com/example/demo/DemoApplication.java create mode 100644 src/main/java/com/example/demo/config/LoggingFilterConfig.java create mode 100644 src/main/java/com/example/demo/config/WebMvcConfig.java create mode 100644 src/main/java/com/example/demo/controller/BaseController.java create mode 100644 src/main/java/com/example/demo/controller/CheckNotBlankController.java create mode 100644 src/main/java/com/example/demo/controller/FilterTestController.java create mode 100644 src/main/java/com/example/demo/entity/UserDTO.java create mode 100644 src/main/java/com/example/demo/exception/ErrorResponse.java create mode 100644 src/main/java/com/example/demo/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/com/example/demo/filter/MultiReadHttpServletFilter.java create mode 100644 src/main/resources/application.properties create mode 100644 src/main/resources/logback-spring.xml create mode 100644 src/test/java/com/example/demo/DemoApplicationTests.java diff --git a/demo-0.0.1-SNAPSHOT.jar.original b/demo-0.0.1-SNAPSHOT.jar.original deleted file mode 100644 index 21a6d510301409883c75f40b2e63726b5d074978..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16338 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 + */ + @Bean + public FilterRegistrationBean<@NonNull MultiReadHttpServletFilter> registerApiFilter() { + FilterRegistrationBean<@NonNull MultiReadHttpServletFilter> registrationBean = new FilterRegistrationBean<>(); + registrationBean.setFilter(new MultiReadHttpServletFilter()); + registrationBean.setName("MultiReadHttpServletFilter"); + registrationBean.addUrlPatterns("/*"); + registrationBean.setOrder(-900); + + return registrationBean; + } +} diff --git a/src/main/java/com/example/demo/config/WebMvcConfig.java b/src/main/java/com/example/demo/config/WebMvcConfig.java new file mode 100644 index 0000000..2b3f768 --- /dev/null +++ b/src/main/java/com/example/demo/config/WebMvcConfig.java @@ -0,0 +1,21 @@ +package com.example.demo.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.HandlerExceptionResolver; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +import java.util.List; + +/** + * @author buxue + */ +@Configuration +public class WebMvcConfig implements WebMvcConfigurer { + /** + * 禁用Spring默认的异常解析器,让参数缺失等异常能抛回过滤器的catch块 + */ + @Override + public void configureHandlerExceptionResolvers(List resolvers) { + resolvers.clear(); // 清空所有默认的异常解析器 + } +} diff --git a/src/main/java/com/example/demo/controller/BaseController.java b/src/main/java/com/example/demo/controller/BaseController.java new file mode 100644 index 0000000..3969c2e --- /dev/null +++ b/src/main/java/com/example/demo/controller/BaseController.java @@ -0,0 +1,22 @@ +package com.example.demo.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; + +/** + * @author buxue + */ +//@RestController +@RequestMapping("/extend") +public class BaseController { + + /** + * 验证类继承问题 + * + * @return + */ + @GetMapping("testGet") + public String testGet() { + return "步雪"; + } +} diff --git a/src/main/java/com/example/demo/controller/CheckNotBlankController.java b/src/main/java/com/example/demo/controller/CheckNotBlankController.java new file mode 100644 index 0000000..2bef956 --- /dev/null +++ b/src/main/java/com/example/demo/controller/CheckNotBlankController.java @@ -0,0 +1,28 @@ +package com.example.demo.controller; + +import com.example.demo.entity.UserDTO; +import jakarta.validation.Valid; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +/** + * @author buxue + */ +@Slf4j +@RestController +//@RequestMapping("/check") +public class CheckNotBlankController extends BaseController { + /** + * RequestMapping 继承 + 触发@NotBlank校验 + * + * @param userDTO + */ + @PostMapping("/one") + public void check(@Valid @RequestBody UserDTO userDTO) { + UserDTO user = new UserDTO(); + user.setAge(userDTO.getAge()); + user.setName(userDTO.getName()); + log.info("user:{}", user); + + } +} diff --git a/src/main/java/com/example/demo/controller/FilterTestController.java b/src/main/java/com/example/demo/controller/FilterTestController.java new file mode 100644 index 0000000..edc8252 --- /dev/null +++ b/src/main/java/com/example/demo/controller/FilterTestController.java @@ -0,0 +1,65 @@ +package com.example.demo.controller; + +import cn.hutool.core.lang.Assert; +import com.example.demo.entity.UserDTO; +import lombok.extern.slf4j.Slf4j; +import org.springframework.web.bind.annotation.*; + +import java.util.Map; + +/** + * @author buxue + */ +@Slf4j +@RestController +@RequestMapping("/test/filter") +public class FilterTestController { + + /** + * get请求记录请求的入参/出参,以及请求的响应耗时。 并写到一个单独的日志文件中 + * + * @return Map + */ + @GetMapping("/get") + public Map testGet( + @RequestParam String name, + @RequestParam Integer age) { + try { + log.info("进入get请求"); + Thread.sleep(50); + } catch (Exception e) { + Thread.currentThread().interrupt(); + log.error("进入get请求异常{}", e.getMessage()); + } + return Map.of( + "code", 200, + "msg", "GET请求成功", + "data", Map.of("name", name, "age", age) + ); + } + + /** + * post请求记录请求的入参/出参,以及请求的响应耗时。 并写到一个单独的日志文件中 + * + * @return Map + */ + + @PostMapping("/post") + public Map testPost( + @RequestBody UserDTO user) { + try { + log.info("进入post请求"); + Thread.sleep(80); + } catch (Exception e) { + Thread.currentThread().interrupt(); + log.error("进入post请求异常{}", e.getMessage()); + } + return Map.of( + "code", 200, + "msg", "POST请求成功", + "data", user + ); + } + + +} diff --git a/src/main/java/com/example/demo/entity/UserDTO.java b/src/main/java/com/example/demo/entity/UserDTO.java new file mode 100644 index 0000000..f636c83 --- /dev/null +++ b/src/main/java/com/example/demo/entity/UserDTO.java @@ -0,0 +1,16 @@ +package com.example.demo.entity; + +import jakarta.validation.constraints.NotBlank; +import lombok.Data; + +/** + * @author buxue + */ +@Data +public class UserDTO { + @NotBlank(message = "姓名不能为空") + private String name; + + @NotBlank(message = "年纪不能为空") + private String age; +} diff --git a/src/main/java/com/example/demo/exception/ErrorResponse.java b/src/main/java/com/example/demo/exception/ErrorResponse.java new file mode 100644 index 0000000..e969f4d --- /dev/null +++ b/src/main/java/com/example/demo/exception/ErrorResponse.java @@ -0,0 +1,41 @@ +package com.example.demo.exception; + +import lombok.Data; + +import java.time.LocalDateTime; + + +/** + * @author buxue + */ +@Data +public class ErrorResponse { + + private LocalDateTime timestamp; + + private String traceId; + + private Integer status; + + private String exceptionType; + + private String message; + + private String path; + + /** + * 快速构建异常响应 + */ + public static ErrorResponse build(String traceId, Integer status, String exceptionType, String message, String path) { + ErrorResponse response = new ErrorResponse(); + response.setTimestamp(LocalDateTime.now()); + response.setTraceId(traceId); + response.setStatus(status); + response.setExceptionType(exceptionType); + response.setMessage(message); + response.setPath(path); + return response; + } +} + + diff --git a/src/main/java/com/example/demo/exception/GlobalExceptionHandler.java b/src/main/java/com/example/demo/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..e942dcc --- /dev/null +++ b/src/main/java/com/example/demo/exception/GlobalExceptionHandler.java @@ -0,0 +1,47 @@ +package com.example.demo.exception; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.extern.slf4j.Slf4j; +import org.springframework.core.annotation.AnnotationUtils; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +/** + * @author buxue + */ +@Slf4j +@RestControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(Exception.class) + @ResponseBody + public ErrorResponse handleAllException(Exception e, HttpServletRequest request) { + + String traceId = (String) request.getAttribute("traceId"); + + String exceptionType = e.getClass().getSimpleName(); + String errorMsg = e.getMessage() != null ? e.getMessage() : "服务器处理异常"; + String requestPath = request.getRequestURI(); + + HttpStatus httpStatus = getHttpStatus(e); + Integer status = httpStatus.value(); + log.error("[{}] 异常捕获 | 类型:{} | 路径:{} | 提示:{}", traceId, exceptionType, requestPath, errorMsg, e); + + return ErrorResponse.build(traceId, status, exceptionType, errorMsg, requestPath); + } + + private HttpStatus getHttpStatus(Exception e) { + ResponseStatus responseStatus = AnnotationUtils.findAnnotation(e.getClass(), ResponseStatus.class); + + if (responseStatus != null) { + // 如果注解存在,返回注解中定义的状态码(贴合HTTP标准) + return responseStatus.code(); + } else { + // 无注解的异常,兜底返回500(服务器内部错误) + return HttpStatus.INTERNAL_SERVER_ERROR; + } + } +} diff --git a/src/main/java/com/example/demo/filter/MultiReadHttpServletFilter.java b/src/main/java/com/example/demo/filter/MultiReadHttpServletFilter.java new file mode 100644 index 0000000..23c21dc --- /dev/null +++ b/src/main/java/com/example/demo/filter/MultiReadHttpServletFilter.java @@ -0,0 +1,96 @@ +package com.example.demo.filter; + +import com.hengspire.common.http.servlet.MultiReadHttpServletRequestWrapper; +import com.hengspire.common.http.servlet.MultiReadHttpServletResponseWrapper; +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.slf4j.MDC; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; +import java.util.UUID; + +/** + * @author buxue + */ +@Slf4j(topic = "httpLog") +@RequiredArgsConstructor +public class MultiReadHttpServletFilter extends OncePerRequestFilter { + /** + * 过滤器处理方法,实现请求/响应包装、链路追踪、请求耗时统计及日志记录。 + * + * @param request 原生HTTP请求对象 + * @param response 原生HTTP响应对象 + * @param filterChain 过滤器链 + * @throws ServletException Servlet相关异常 + * @throws IOException io流异常 + */ + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { + MultiReadHttpServletRequestWrapper req = new MultiReadHttpServletRequestWrapper(request); + MultiReadHttpServletResponseWrapper resp = new MultiReadHttpServletResponseWrapper(response); + String originalTraceId = MDC.get("traceId"); + String traceId; + + if (originalTraceId != null && !originalTraceId.isEmpty()) { + traceId = originalTraceId; + } else { + traceId = UUID.randomUUID().toString().replace("-", ""); + } + MDC.put("traceId", traceId); + req.setAttribute("traceId", traceId); + long startTime = System.currentTimeMillis(); + try { + filterChain.doFilter(req, resp); + } catch (Exception e) { + log.error("[{}] 请求处理发生异常,请求路径:{},请求方法:{}", traceId, request.getRequestURI(), request.getMethod(), e); + throw new ServletException(e); + } finally { + long costTime = System.currentTimeMillis() - startTime; + logRequestLog(req, resp, costTime, traceId); + MDC.remove("traceId"); + } + } + + /** + * 返回请求日志 + * + * @param req 原生HTTP请求对象 + * @param resp 原生HTTP响应对象 + * @param costTime 请求耗时 + * @param traceId traceId + */ + private void logRequestLog(MultiReadHttpServletRequestWrapper req, + MultiReadHttpServletResponseWrapper resp, + long costTime, + String traceId) { + String requestUrl = req.getRequestURL().toString(); + String requestMethod = req.getMethod(); + String requestParams = ""; + if ("GET".equalsIgnoreCase(requestMethod)) { + requestParams = req.getQueryString() == null ? "无" : req.getQueryString(); + } else { + requestParams = req.getBodyAsString().isEmpty() ? "无" : req.getBodyAsString(); + } + String responseBody = resp.getBodyAsString(); + + String logContent = String.format( + "【请求详情】\n" + + "traceId: %s\n" + + "请求URL: %s\n" + + "请求方法: %s\n" + + "请求入参: %s\n" + + "响应出参: %s\n" + + "响应耗时: %d ms\n" + + "------------------------", + traceId, requestUrl, requestMethod, requestParams, responseBody, costTime + ); + log.info(logContent); + } + + +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..a12cfa6 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,4 @@ +spring.application.name=demo +server.port=8082 +logback.home.path=/Users/hengspire/data/demo/log/ + diff --git a/src/main/resources/logback-spring.xml b/src/main/resources/logback-spring.xml new file mode 100644 index 0000000..87202cb --- /dev/null +++ b/src/main/resources/logback-spring.xml @@ -0,0 +1,70 @@ + + + + + + + + + + ${LOG_PATTERN} + + + + + + ${LOG_PATTERN} + UTF-8 + + + INFO + ACCEPT + DENY + + + ${LOG_HOME}/info/%d{yyyy-MM-dd}-%i.log + 100MB + 180 + + + + + + ${LOG_PATTERN} + UTF-8 + + + ERROR + ACCEPT + DENY + + + ${LOG_HOME}/error/%d{yyyy-MM-dd}-%i.log + 100MB + 180 + + + + + + ${LOG_PATTERN} + UTF-8 + + + ${LOG_HOME}/httpLogs/%d{yyyy-MM-dd}-%i.log + 100MB + 180 + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/test/java/com/example/demo/DemoApplicationTests.java b/src/test/java/com/example/demo/DemoApplicationTests.java new file mode 100644 index 0000000..2778a6a --- /dev/null +++ b/src/test/java/com/example/demo/DemoApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.demo; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class DemoApplicationTests { + + @Test + void contextLoads() { + } + +}