mirror of
				https://github.com/continew-org/continew-starter.git
				synced 2025-10-31 21:02:23 +08:00 
			
		
		
		
	feat(log): 新增日志模块 - HttpTracePro(Spring Boot Actuator HttpTrace 定制增强版)
This commit is contained in:
		| @@ -0,0 +1,37 @@ | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <project xmlns="http://maven.apache.org/POM/4.0.0" | ||||
|          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
|          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
|     <modelVersion>4.0.0</modelVersion> | ||||
|     <parent> | ||||
|         <groupId>top.charles7c.continew</groupId> | ||||
|         <artifactId>continew-starter-log</artifactId> | ||||
|         <version>${revision}</version> | ||||
|     </parent> | ||||
|  | ||||
|     <artifactId>continew-starter-log-httptrace-pro</artifactId> | ||||
|     <packaging>jar</packaging> | ||||
|  | ||||
|     <name>${project.artifactId}</name> | ||||
|     <description>ContiNew Starter 日志模块 - HttpTracePro(Spring Boot Actuator HttpTrace 定制增强版)</description> | ||||
|  | ||||
|     <dependencies> | ||||
|         <!-- Swagger 注解 --> | ||||
|         <dependency> | ||||
|             <groupId>io.swagger.core.v3</groupId> | ||||
|             <artifactId>swagger-annotations-jakarta</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- TTL(线程间传递 ThreadLocal,异步执行时上下文传递的解决方案) --> | ||||
|         <dependency> | ||||
|             <groupId>com.alibaba</groupId> | ||||
|             <artifactId>transmittable-thread-local</artifactId> | ||||
|         </dependency> | ||||
|  | ||||
|         <!-- 日志模块 - 公共模块 --> | ||||
|         <dependency> | ||||
|             <groupId>top.charles7c.continew</groupId> | ||||
|             <artifactId>continew-starter-log-common</artifactId> | ||||
|         </dependency> | ||||
|     </dependencies> | ||||
| </project> | ||||
| @@ -0,0 +1,33 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.continew.starter.log.httptracepro.autoconfigure; | ||||
|  | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; | ||||
|  | ||||
| import java.lang.annotation.*; | ||||
|  | ||||
| /** | ||||
|  * 是否启用日志记录注解 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.1.0 | ||||
|  */ | ||||
| @Retention(RetentionPolicy.RUNTIME) | ||||
| @Target({ ElementType.TYPE, ElementType.METHOD }) | ||||
| @Documented | ||||
| @ConditionalOnProperty(prefix = "continew-starter.log", name = "enabled", havingValue = "true") | ||||
| public @interface ConditionalOnEnabledLog {} | ||||
| @@ -0,0 +1,77 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.continew.starter.log.httptracepro.autoconfigure; | ||||
|  | ||||
| import jakarta.annotation.PostConstruct; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; | ||||
| import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication; | ||||
| import org.springframework.boot.context.properties.EnableConfigurationProperties; | ||||
| import org.springframework.context.annotation.Bean; | ||||
| import org.springframework.context.annotation.Configuration; | ||||
| import org.springframework.web.servlet.config.annotation.InterceptorRegistry; | ||||
| import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; | ||||
| import top.charles7c.continew.starter.log.common.dao.LogDao; | ||||
| import top.charles7c.continew.starter.log.common.dao.impl.LogDaoDefaultImpl; | ||||
| import top.charles7c.continew.starter.log.httptracepro.handler.LogFilter; | ||||
| import top.charles7c.continew.starter.log.httptracepro.handler.LogInterceptor; | ||||
|  | ||||
| /** | ||||
|  * 日志自动配置 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.1.0 | ||||
|  */ | ||||
| @Slf4j | ||||
| @Configuration | ||||
| @ConditionalOnEnabledLog | ||||
| @RequiredArgsConstructor | ||||
| @EnableConfigurationProperties(LogProperties.class) | ||||
| @ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.SERVLET) | ||||
| public class LogAutoConfiguration implements WebMvcConfigurer { | ||||
|  | ||||
|     private final LogProperties properties; | ||||
|  | ||||
|     @Override | ||||
|     public void addInterceptors(InterceptorRegistry registry) { | ||||
|         registry.addInterceptor(new LogInterceptor(logDao(), properties)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 日志过滤器 | ||||
|      */ | ||||
|     @Bean | ||||
|     @ConditionalOnMissingBean | ||||
|     public LogFilter logFilter() { | ||||
|         return new LogFilter(); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 日志持久层接口 | ||||
|      */ | ||||
|     @Bean | ||||
|     @ConditionalOnMissingBean | ||||
|     public LogDao logDao() { | ||||
|         return new LogDaoDefaultImpl(); | ||||
|     } | ||||
|  | ||||
|     @PostConstruct | ||||
|     public void postConstruct() { | ||||
|         log.info("[ContiNew Starter] - Auto Configuration 'Log-HttpTracePro' completed initialization."); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,45 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.continew.starter.log.httptracepro.autoconfigure; | ||||
|  | ||||
| import lombok.Data; | ||||
| import org.springframework.boot.context.properties.ConfigurationProperties; | ||||
| import top.charles7c.continew.starter.log.common.enums.Include; | ||||
|  | ||||
| import java.util.HashSet; | ||||
| import java.util.Set; | ||||
|  | ||||
| /** | ||||
|  * 日志配置属性 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.1.0 | ||||
|  */ | ||||
| @Data | ||||
| @ConfigurationProperties(prefix = "continew-starter.log") | ||||
| public class LogProperties { | ||||
|  | ||||
|     /** | ||||
|      * 是否启用日志 | ||||
|      */ | ||||
|     private boolean enabled = false; | ||||
|  | ||||
|     /** | ||||
|      * 包含信息 | ||||
|      */ | ||||
|     private Set<Include> include = new HashSet<>(Include.defaultIncludes()); | ||||
| } | ||||
| @@ -0,0 +1,88 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.continew.starter.log.httptracepro.handler; | ||||
|  | ||||
| import jakarta.servlet.FilterChain; | ||||
| import jakarta.servlet.ServletException; | ||||
| import jakarta.servlet.http.HttpServletRequest; | ||||
| import jakarta.servlet.http.HttpServletResponse; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import org.springframework.core.Ordered; | ||||
| import org.springframework.lang.NonNull; | ||||
| import org.springframework.web.filter.OncePerRequestFilter; | ||||
| import org.springframework.web.util.ContentCachingRequestWrapper; | ||||
| import org.springframework.web.util.ContentCachingResponseWrapper; | ||||
| import org.springframework.web.util.WebUtils; | ||||
|  | ||||
| import java.io.IOException; | ||||
| import java.net.URI; | ||||
| import java.net.URISyntaxException; | ||||
| import java.util.Objects; | ||||
|  | ||||
| /** | ||||
|  * 日志过滤器 | ||||
|  * | ||||
|  * @author Dave Syer(Spring Boot Actuator) | ||||
|  * @author Wallace Wadge(Spring Boot Actuator) | ||||
|  * @author Andy Wilkinson(Spring Boot Actuator) | ||||
|  * @author Venil Noronha(Spring Boot Actuator) | ||||
|  * @author Madhura Bhave(Spring Boot Actuator) | ||||
|  * @author Charles7c | ||||
|  * @since 1.1.0 | ||||
|  */ | ||||
| @RequiredArgsConstructor | ||||
| public class LogFilter extends OncePerRequestFilter implements Ordered { | ||||
|  | ||||
|     @Override | ||||
|     public int getOrder() { | ||||
|         return Ordered.LOWEST_PRECEDENCE - 10; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     protected void doFilterInternal(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, | ||||
|                                     @NonNull FilterChain filterChain) throws ServletException, IOException { | ||||
|         if (!isRequestValid(request)) { | ||||
|             filterChain.doFilter(request, response); | ||||
|             return; | ||||
|         } | ||||
|         // 包装输入、输出流,可重复读取 | ||||
|         if (!(request instanceof ContentCachingRequestWrapper)) { | ||||
|             request = new ContentCachingRequestWrapper(request); | ||||
|         } | ||||
|         if (!(response instanceof ContentCachingResponseWrapper)) { | ||||
|             response = new ContentCachingResponseWrapper(response); | ||||
|         } | ||||
|         filterChain.doFilter(request, response); | ||||
|         // 更新响应(不操作这一步,会导致接口响应空白) | ||||
|         updateResponse(response); | ||||
|     } | ||||
|  | ||||
|     private boolean isRequestValid(HttpServletRequest request) { | ||||
|         try { | ||||
|             new URI(request.getRequestURL().toString()); | ||||
|             return true; | ||||
|         } catch (URISyntaxException e) { | ||||
|             return false; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     private void updateResponse(HttpServletResponse response) throws IOException { | ||||
|         ContentCachingResponseWrapper responseWrapper = | ||||
|                 WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class); | ||||
|         Objects.requireNonNull(responseWrapper).copyBodyToResponse(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,153 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.continew.starter.log.httptracepro.handler; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import com.alibaba.ttl.TransmittableThreadLocal; | ||||
| import io.swagger.v3.oas.annotations.Operation; | ||||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||||
| import jakarta.servlet.http.HttpServletRequest; | ||||
| import jakarta.servlet.http.HttpServletResponse; | ||||
| import lombok.RequiredArgsConstructor; | ||||
| import lombok.extern.slf4j.Slf4j; | ||||
| import org.springframework.lang.NonNull; | ||||
| import org.springframework.web.method.HandlerMethod; | ||||
| import org.springframework.web.servlet.HandlerInterceptor; | ||||
| import top.charles7c.continew.starter.log.common.annotation.Log; | ||||
| import top.charles7c.continew.starter.log.common.dao.LogDao; | ||||
| import top.charles7c.continew.starter.log.common.enums.Include; | ||||
| import top.charles7c.continew.starter.log.common.model.LogRecord; | ||||
| import top.charles7c.continew.starter.log.httptracepro.autoconfigure.LogProperties; | ||||
|  | ||||
| import java.time.Clock; | ||||
| import java.util.Set; | ||||
|  | ||||
| /** | ||||
|  * 日志拦截器 | ||||
|  * | ||||
|  * @author Charles7c | ||||
|  * @since 1.1.0 | ||||
|  */ | ||||
| @Slf4j | ||||
| @RequiredArgsConstructor | ||||
| public class LogInterceptor implements HandlerInterceptor { | ||||
|  | ||||
|     private final LogDao dao; | ||||
|     private final LogProperties properties; | ||||
|     private final TransmittableThreadLocal<Clock> timestampTtl = new TransmittableThreadLocal<>(); | ||||
|  | ||||
|     @Override | ||||
|     public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, | ||||
|                              @NonNull Object handler) { | ||||
|         if (this.isRequestRecord(handler)) { | ||||
|             timestampTtl.set(Clock.systemUTC()); | ||||
|         } | ||||
|         return true; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public void afterCompletion(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, | ||||
|                                 @NonNull Object handler, Exception e) { | ||||
|         Clock timestamp = timestampTtl.get(); | ||||
|         if (null == timestamp) { | ||||
|             return; | ||||
|         } | ||||
|         timestampTtl.remove(); | ||||
|         Set<Include> includeSet = properties.getInclude(); | ||||
|         RecordableServletHttpRequest sourceRequest = new RecordableServletHttpRequest(request); | ||||
|         LogRecord.Started startedLogRecord = LogRecord.start(timestamp, sourceRequest); | ||||
|         RecordableServletHttpResponse sourceResponse = new RecordableServletHttpResponse(response, response.getStatus()); | ||||
|         LogRecord finishedLogRecord = startedLogRecord.finish(sourceResponse, includeSet); | ||||
|         HandlerMethod handlerMethod = (HandlerMethod) handler; | ||||
|         if (includeSet.contains(Include.DESCRIPTION)) { | ||||
|             // 记录日志描述 | ||||
|             this.logDescription(finishedLogRecord, handlerMethod); | ||||
|         } | ||||
|         if (includeSet.contains(Include.MODULE)) { | ||||
|             // 记录所属模块 | ||||
|             this.logModule(finishedLogRecord, handlerMethod); | ||||
|         } | ||||
|         dao.add(finishedLogRecord); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 记录描述 | ||||
|      * | ||||
|      * @param logRecord     日志信息 | ||||
|      * @param handlerMethod 处理器方法 | ||||
|      */ | ||||
|     private void logDescription(LogRecord logRecord, HandlerMethod handlerMethod) { | ||||
|         // 例如:@Operation(summary="新增部门") -> 新增部门 | ||||
|         Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class); | ||||
|         if (null != methodOperation) { | ||||
|             logRecord.setDescription(StrUtil.blankToDefault(methodOperation.summary(), "请在该接口方法上指定日志描述")); | ||||
|         } | ||||
|         // 例如:@Log("新增部门") -> 新增部门 | ||||
|         Log methodLog = handlerMethod.getMethodAnnotation(Log.class); | ||||
|         if (null != methodLog && StrUtil.isNotBlank(methodLog.value())) { | ||||
|             logRecord.setDescription(methodLog.value()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 记录模块 | ||||
|      * | ||||
|      * @param logRecord     日志信息 | ||||
|      * @param handlerMethod 处理器方法 | ||||
|      */ | ||||
|     private void logModule(LogRecord logRecord, HandlerMethod handlerMethod) { | ||||
|         // 例如:@Tag(name = "部门管理") -> 部门管理 | ||||
|         Tag classTag = handlerMethod.getBeanType().getDeclaredAnnotation(Tag.class); | ||||
|         if (null != classTag) { | ||||
|             String name = classTag.name(); | ||||
|             logRecord.setModule(StrUtil.blankToDefault(name, "请在该接口类上指定所属模块")); | ||||
|         } | ||||
|         // 例如:@Log(module = "部门管理") -> 部门管理 | ||||
|         Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class); | ||||
|         if (null != classLog && StrUtil.isNotBlank(classLog.module())) { | ||||
|             logRecord.setModule(classLog.module()); | ||||
|         } | ||||
|         Log methodLog = handlerMethod.getMethodAnnotation(Log.class); | ||||
|         if (null != methodLog && StrUtil.isNotBlank(methodLog.module())) { | ||||
|             logRecord.setModule(methodLog.module()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      * 是否要记录日志 | ||||
|      * | ||||
|      * @param handler 处理器 | ||||
|      * @return true:需要记录;false:不需要记录 | ||||
|      */ | ||||
|     private boolean isRequestRecord(Object handler) { | ||||
|         if (!(handler instanceof HandlerMethod handlerMethod)) { | ||||
|             return false; | ||||
|         } | ||||
|         // 如果接口被隐藏,不记录日志 | ||||
|         Operation methodOperation = handlerMethod.getMethodAnnotation(Operation.class); | ||||
|         if (null != methodOperation && methodOperation.hidden()) { | ||||
|             return false; | ||||
|         } | ||||
|         // 如果接口方法或类上有 @Log 注解,且要求忽略该接口,则不记录日志 | ||||
|         Log methodLog = handlerMethod.getMethodAnnotation(Log.class); | ||||
|         if (null != methodLog && methodLog.ignore()) { | ||||
|             return false; | ||||
|         } | ||||
|         Log classLog = handlerMethod.getBeanType().getDeclaredAnnotation(Log.class); | ||||
|         return null == classLog || !classLog.ignore(); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,99 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.continew.starter.log.httptracepro.handler; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.extra.servlet.JakartaServletUtil; | ||||
| import cn.hutool.json.JSONUtil; | ||||
| import jakarta.servlet.http.HttpServletRequest; | ||||
| import org.springframework.web.util.ContentCachingRequestWrapper; | ||||
| import org.springframework.web.util.UriUtils; | ||||
| import org.springframework.web.util.WebUtils; | ||||
| import top.charles7c.continew.starter.core.constant.StringConstants; | ||||
| import top.charles7c.continew.starter.log.common.model.RecordableHttpRequest; | ||||
|  | ||||
| import java.net.URI; | ||||
| import java.net.URISyntaxException; | ||||
| import java.nio.charset.StandardCharsets; | ||||
| import java.util.*; | ||||
|  | ||||
| /** | ||||
|  * 可记录的 HTTP 请求信息适配器 | ||||
|  * | ||||
|  * @author Andy Wilkinson(Spring Boot Actuator) | ||||
|  * @author Charles7c | ||||
|  */ | ||||
| public final class RecordableServletHttpRequest implements RecordableHttpRequest { | ||||
|  | ||||
|     private final HttpServletRequest request; | ||||
|  | ||||
|     public RecordableServletHttpRequest(HttpServletRequest request) { | ||||
|         this.request = request; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getMethod() { | ||||
|         return request.getMethod(); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public URI getUri() { | ||||
|         String queryString = request.getQueryString(); | ||||
|         if (StrUtil.isBlank(queryString)) { | ||||
|             return URI.create(request.getRequestURL().toString()); | ||||
|         } | ||||
|         try { | ||||
|             StringBuffer urlBuffer = this.appendQueryString(queryString); | ||||
|             return new URI(urlBuffer.toString()); | ||||
|         } catch (URISyntaxException e) { | ||||
|             String encoded = UriUtils.encodeQuery(queryString, StandardCharsets.UTF_8); | ||||
|             StringBuffer urlBuffer = this.appendQueryString(encoded); | ||||
|             return URI.create(urlBuffer.toString()); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getIp() { | ||||
|         return JakartaServletUtil.getClientIP(request); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Map<String, List<String>> getHeaders() { | ||||
|         return JakartaServletUtil.getHeadersMap(request); | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getBody() { | ||||
|         ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class); | ||||
|         if (null != wrapper) { | ||||
|             return StrUtil.utf8Str(wrapper.getContentAsByteArray()); | ||||
|         } | ||||
|         return StringConstants.EMPTY; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Map<String, Object> getParam() { | ||||
|         String body = this.getBody(); | ||||
|         return StrUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) | ||||
|                 ? JSONUtil.toBean(body, Map.class) | ||||
|                 : Collections.unmodifiableMap(request.getParameterMap()); | ||||
|     } | ||||
|  | ||||
|     private StringBuffer appendQueryString(String queryString) { | ||||
|         return request.getRequestURL().append(StringConstants.QUESTION_MARK).append(queryString); | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1,76 @@ | ||||
| /* | ||||
|  * Copyright (c) 2022-present Charles7c Authors. All Rights Reserved. | ||||
|  * <p> | ||||
|  * Licensed under the GNU LESSER GENERAL PUBLIC LICENSE 3.0; | ||||
|  * you may not use this file except in compliance with the License. | ||||
|  * You may obtain a copy of the License at | ||||
|  * <p> | ||||
|  * http://www.gnu.org/licenses/lgpl.html | ||||
|  * <p> | ||||
|  * Unless required by applicable law or agreed to in writing, software | ||||
|  * distributed under the License is distributed on an "AS IS" BASIS, | ||||
|  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||||
|  * See the License for the specific language governing permissions and | ||||
|  * limitations under the License. | ||||
|  */ | ||||
|  | ||||
| package top.charles7c.continew.starter.log.httptracepro.handler; | ||||
|  | ||||
| import cn.hutool.core.util.StrUtil; | ||||
| import cn.hutool.json.JSONUtil; | ||||
| import jakarta.servlet.http.HttpServletResponse; | ||||
| import org.springframework.web.util.ContentCachingResponseWrapper; | ||||
| import org.springframework.web.util.WebUtils; | ||||
| import top.charles7c.continew.starter.core.constant.StringConstants; | ||||
| import top.charles7c.continew.starter.log.common.model.RecordableHttpResponse; | ||||
|  | ||||
| import java.util.*; | ||||
|  | ||||
| /** | ||||
|  * 可记录的 HTTP 响应信息适配器 | ||||
|  * | ||||
|  * @author Andy Wilkinson(Spring Boot Actuator) | ||||
|  * @author Charles7c | ||||
|  */ | ||||
| public final class RecordableServletHttpResponse implements RecordableHttpResponse { | ||||
|  | ||||
|     private final HttpServletResponse response; | ||||
|  | ||||
|     private final int status; | ||||
|  | ||||
|     public RecordableServletHttpResponse(HttpServletResponse response, int status) { | ||||
|         this.response = response; | ||||
|         this.status = status; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public int getStatus() { | ||||
|         return this.status; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Map<String, List<String>> getHeaders() { | ||||
|         Map<String, List<String>> headers = new LinkedHashMap<>(); | ||||
|         for (String name : response.getHeaderNames()) { | ||||
|             headers.put(name, new ArrayList<>(response.getHeaders(name))); | ||||
|         } | ||||
|         return headers; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public String getBody() { | ||||
|         ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class); | ||||
|         if (null != wrapper) { | ||||
|             return StrUtil.utf8Str(wrapper.getContentAsByteArray()); | ||||
|         } | ||||
|         return StringConstants.EMPTY; | ||||
|     } | ||||
|  | ||||
|     @Override | ||||
|     public Map<String, Object> getParam() { | ||||
|         String body = this.getBody(); | ||||
|         return StrUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) | ||||
|                 ? JSONUtil.toBean(body, Map.class) | ||||
|                 : null; | ||||
|     } | ||||
| } | ||||
| @@ -0,0 +1 @@ | ||||
| top.charles7c.continew.starter.log.httptracepro.autoconfigure.LogAutoConfiguration | ||||
		Reference in New Issue
	
	Block a user