mirror of
https://github.com/continew-org/continew-starter.git
synced 2026-01-12 06:57:11 +08:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 580aa00af9 | |||
| 25fb9e0a27 | |||
| 8ce00d8892 | |||
| 4f8d7725e6 |
@@ -18,10 +18,10 @@ package top.continew.starter.core.util;
|
||||
|
||||
import cn.hutool.core.map.MapUtil;
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import cn.hutool.extra.servlet.JakartaServletUtil;
|
||||
import cn.hutool.http.useragent.UserAgent;
|
||||
import cn.hutool.http.useragent.UserAgentUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
@@ -29,24 +29,15 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.web.context.request.RequestAttributes;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.wrapper.RepeatReadRequestWrapper;
|
||||
import top.continew.starter.core.wrapper.RepeatReadResponseWrapper;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Servlet 工具类
|
||||
*
|
||||
* @author Charles7c
|
||||
* @author echo
|
||||
* @since 1.0.0
|
||||
*/
|
||||
public class ServletUtils extends JakartaServletUtil {
|
||||
@@ -118,138 +109,13 @@ public class ServletUtils extends JakartaServletUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求方法
|
||||
*
|
||||
* @return {@link String }
|
||||
* @since 2.11.0
|
||||
*/
|
||||
public static String getRequestMethod() {
|
||||
HttpServletRequest request = getRequest();
|
||||
return request != null ? request.getMethod() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求参数
|
||||
*
|
||||
* @param name 参数名
|
||||
* @return {@link String }
|
||||
* @since 2.11.0
|
||||
*/
|
||||
public static String getRequestParameter(String name) {
|
||||
HttpServletRequest request = getRequest();
|
||||
return request != null ? request.getParameter(name) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求 Ip
|
||||
*
|
||||
* @return {@link String }
|
||||
* @since 2.11.0
|
||||
*/
|
||||
public static String getRequestIp() {
|
||||
HttpServletRequest request = getRequest();
|
||||
return request != null ? getClientIP(request) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求头信息
|
||||
*
|
||||
* @return {@link Map }<{@link String }, {@link String }>
|
||||
* @since 2.11.0
|
||||
*/
|
||||
public static Map<String, String> getRequestHeaders() {
|
||||
HttpServletRequest request = getRequest();
|
||||
return request != null ? getHeaderMap(request) : Collections.emptyMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求 URL(包含 query 参数)
|
||||
* <p>{@code http://localhost:8000/system/user?page=1&size=10}</p>
|
||||
*
|
||||
* @return {@link URI }
|
||||
* @since 2.11.0
|
||||
*/
|
||||
public static URI getRequestUrl() {
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request == null) {
|
||||
return null;
|
||||
}
|
||||
String queryString = request.getQueryString();
|
||||
if (CharSequenceUtil.isBlank(queryString)) {
|
||||
return URI.create(request.getRequestURL().toString());
|
||||
}
|
||||
try {
|
||||
StringBuilder urlBuilder = appendQueryString(queryString);
|
||||
return new URI(urlBuilder.toString());
|
||||
} catch (URISyntaxException e) {
|
||||
String encoded = UriUtils.encodeQuery(queryString, StandardCharsets.UTF_8);
|
||||
StringBuilder urlBuilder = appendQueryString(encoded);
|
||||
return URI.create(urlBuilder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求路径
|
||||
*
|
||||
* @return {@link URI }
|
||||
* @since 2.11.0
|
||||
*/
|
||||
public static String getRequestPath() {
|
||||
HttpServletRequest request = getRequest();
|
||||
return request != null ? request.getRequestURI() : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求 body 参数
|
||||
*
|
||||
* @return {@link String }
|
||||
* @since 2.11.0
|
||||
*/
|
||||
public static String getRequestBody() {
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request instanceof RepeatReadRequestWrapper wrapper && !wrapper.isMultipartContent(request)) {
|
||||
String body = JakartaServletUtil.getBody(request);
|
||||
return JSONUtil.isTypeJSON(body) ? body : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取请求参数
|
||||
*
|
||||
* @return {@link Map }<{@link String }, {@link Object }>
|
||||
* @since 2.11.0
|
||||
*/
|
||||
public static Map<String, Object> getRequestParams() {
|
||||
String body = getRequestBody();
|
||||
return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body)
|
||||
? JSONUtil.toBean(body, Map.class)
|
||||
: Collections.unmodifiableMap(JakartaServletUtil.getParamMap(Objects.requireNonNull(getRequest())));
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应状态
|
||||
*
|
||||
* @return int
|
||||
* @since 2.11.0
|
||||
*/
|
||||
public static int getResponseStatus() {
|
||||
HttpServletResponse response = getResponse();
|
||||
return response != null ? response.getStatus() : -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应所有的头(header)信息
|
||||
*
|
||||
* @param response 响应对象{@link HttpServletResponse}
|
||||
* @return header值
|
||||
* @since 2.11.0
|
||||
*/
|
||||
public static Map<String, String> getResponseHeaders() {
|
||||
HttpServletResponse response = getResponse();
|
||||
if (response == null) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
public static Map<String, String> getHeaderMap(HttpServletResponse response) {
|
||||
final Collection<String> headerNames = response.getHeaderNames();
|
||||
final Map<String, String> headerMap = MapUtil.newHashMap(headerNames.size(), true);
|
||||
for (String name : headerNames) {
|
||||
@@ -258,32 +124,6 @@ public class ServletUtils extends JakartaServletUtil {
|
||||
return headerMap;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应 body 参数
|
||||
*
|
||||
* @return {@link String }
|
||||
* @since 2.11.0
|
||||
*/
|
||||
public static String getResponseBody() {
|
||||
HttpServletResponse response = getResponse();
|
||||
if (response instanceof RepeatReadResponseWrapper wrapper && !wrapper.isStreamingResponse()) {
|
||||
String body = wrapper.getResponseContent();
|
||||
return JSONUtil.isTypeJSON(body) ? body : null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取响应参数
|
||||
*
|
||||
* @return {@link Map }<{@link String }, {@link Object }>
|
||||
* @since 2.11.0
|
||||
*/
|
||||
public static Map<String, Object> getResponseParams() {
|
||||
String body = getResponseBody();
|
||||
return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) ? JSONUtil.toBean(body, Map.class) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 HTTP Session
|
||||
*
|
||||
@@ -351,18 +191,37 @@ public class ServletUtils extends JakartaServletUtil {
|
||||
}
|
||||
|
||||
/**
|
||||
* 追加查询字符串
|
||||
* 检查请求是否为 {@code multipart/form-data} 格式(常用于文件上传)
|
||||
*
|
||||
* @param queryString 查询字符串
|
||||
* @return {@link StringBuilder }
|
||||
* @param request 请求对象
|
||||
* @return true: 是; false: 否
|
||||
* @since 2.15.1
|
||||
*/
|
||||
private static StringBuilder appendQueryString(String queryString) {
|
||||
HttpServletRequest request = getRequest();
|
||||
if (request == null) {
|
||||
return new StringBuilder();
|
||||
}
|
||||
return new StringBuilder().append(request.getRequestURL())
|
||||
.append(StringConstants.QUESTION_MARK)
|
||||
.append(queryString);
|
||||
public static boolean isMultipart(HttpServletRequest request) {
|
||||
return StrUtil.startWithIgnoreCase(request.getContentType(), "multipart/");
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 HTTP 请求是否为 {@code application/x-www-form-urlencoded} 格式(标准表单提交)
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @return true: 是; false: 否
|
||||
* @see MediaType#APPLICATION_FORM_URLENCODED_VALUE
|
||||
* @since 2.15.1
|
||||
*/
|
||||
public static boolean isForm(HttpServletRequest request) {
|
||||
return StrUtil.contains(request.getContentType(), MediaType.APPLICATION_FORM_URLENCODED_VALUE);
|
||||
}
|
||||
|
||||
/**
|
||||
* 检查 HTTP 响应是否为 {@code Server-Sent Events (SSE)} 流格式
|
||||
*
|
||||
* @param response 响应对象
|
||||
* @return true: 是; false: 否
|
||||
* @see MediaType#TEXT_EVENT_STREAM_VALUE
|
||||
* @since 2.15.1
|
||||
*/
|
||||
public static boolean isStream(HttpServletResponse response) {
|
||||
return StrUtil.contains(response.getContentType(), MediaType.TEXT_EVENT_STREAM_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,8 +16,30 @@
|
||||
|
||||
package top.continew.starter.core.util;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Spring 工具类
|
||||
@@ -27,6 +49,9 @@ import org.springframework.beans.factory.NoSuchBeanDefinitionException;
|
||||
*/
|
||||
public class SpringUtils {
|
||||
|
||||
private static final AntPathMatcher ANT_PATH_MATCHER = new AntPathMatcher();
|
||||
private static final PathPatternParser PATH_PATTERN_PARSER = PathPatternParser.defaultInstance;
|
||||
|
||||
private SpringUtils() {
|
||||
}
|
||||
|
||||
@@ -62,4 +87,147 @@ public class SpringUtils {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径是否匹配
|
||||
*
|
||||
* @param path 路径
|
||||
* @param patterns 匹配模式列表
|
||||
* @return 是否匹配
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public static boolean isMatch(String path, List<String> patterns) {
|
||||
return patterns.stream().anyMatch(pattern -> isMatch(path, pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径是否匹配
|
||||
*
|
||||
* @param path 路径
|
||||
* @param patterns 匹配模式列表
|
||||
* @return 是否匹配
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public static boolean isMatch(String path, String... patterns) {
|
||||
return Arrays.stream(patterns).anyMatch(pattern -> isMatch(path, pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径是否匹配
|
||||
*
|
||||
* @param path 路径
|
||||
* @param pattern 匹配模式
|
||||
* @return 是否匹配
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public static boolean isMatch(String path, String pattern) {
|
||||
PathPattern pathPattern = PATH_PATTERN_PARSER.parse(pattern);
|
||||
PathContainer pathContainer = PathContainer.parsePath(path);
|
||||
return pathPattern.matches(pathContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径是否匹配 - Ant 风格
|
||||
*
|
||||
* @param path 路径
|
||||
* @param pattern 匹配模式
|
||||
* @return 是否匹配
|
||||
* @author echo
|
||||
* @since 2.15.0
|
||||
*/
|
||||
public static boolean isMatchAnt(String path, String pattern) {
|
||||
return ANT_PATH_MATCHER.match(pattern, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径是否匹配 - Ant 风格
|
||||
*
|
||||
* @param path 路径
|
||||
* @param patterns 匹配模式列表
|
||||
* @return 是否匹配
|
||||
* @author echo
|
||||
* @since 2.15.0
|
||||
*/
|
||||
public static boolean isMatchAnt(String path, List<String> patterns) {
|
||||
return patterns.stream().anyMatch(pattern -> isMatchAnt(path, pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消注册静态资源映射
|
||||
*
|
||||
* @param handlerMap 静态资源映射
|
||||
*/
|
||||
public static void deRegisterResourceHandler(Map<String, String> handlerMap) {
|
||||
ApplicationContext applicationContext = SpringUtil.getApplicationContext();
|
||||
// 获取已经注册的映射
|
||||
final HandlerMapping resourceHandlerMapping = applicationContext
|
||||
.getBean("resourceHandlerMapping", HandlerMapping.class);
|
||||
final Map<String, Object> oldHandlerMap = (Map<String, Object>)ReflectUtil
|
||||
.getFieldValue(resourceHandlerMapping, "handlerMap");
|
||||
// 移除之前注册的映射
|
||||
for (Map.Entry<String, String> entry : handlerMap.entrySet()) {
|
||||
String pathPattern = CharSequenceUtil.appendIfMissing(entry.getKey(), StringConstants.PATH_PATTERN);
|
||||
oldHandlerMap.remove(pathPattern);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册静态资源映射
|
||||
*
|
||||
* @param handlerMap 静态资源映射
|
||||
*/
|
||||
public static void registerResourceHandler(Map<String, String> handlerMap) {
|
||||
ApplicationContext applicationContext = SpringUtil.getApplicationContext();
|
||||
// 获取已经注册的映射
|
||||
final HandlerMapping resourceHandlerMapping = applicationContext
|
||||
.getBean("resourceHandlerMapping", HandlerMapping.class);
|
||||
final Map<String, Object> oldHandlerMap = (Map<String, Object>)ReflectUtil
|
||||
.getFieldValue(resourceHandlerMapping, "handlerMap");
|
||||
// 重新注册映射
|
||||
final ServletContext servletContext = applicationContext.getBean(ServletContext.class);
|
||||
final ContentNegotiationManager contentNegotiationManager = applicationContext
|
||||
.getBean("mvcContentNegotiationManager", ContentNegotiationManager.class);
|
||||
final UrlPathHelper urlPathHelper = applicationContext.getBean("mvcUrlPathHelper", UrlPathHelper.class);
|
||||
final ResourceHandlerRegistry resourceHandlerRegistry = new ResourceHandlerRegistry(applicationContext, servletContext, contentNegotiationManager, urlPathHelper);
|
||||
for (Map.Entry<String, String> entry : handlerMap.entrySet()) {
|
||||
// 移除之前注册的映射
|
||||
String pathPattern = CharSequenceUtil.appendIfMissing(CharSequenceUtil.removeSuffix(entry
|
||||
.getKey(), StringConstants.SLASH), StringConstants.PATH_PATTERN);
|
||||
oldHandlerMap.remove(pathPattern);
|
||||
// 重新注册映射
|
||||
String resourceLocations = CharSequenceUtil.appendIfMissing(entry.getValue(), StringConstants.SLASH);
|
||||
resourceHandlerRegistry.addResourceHandler(pathPattern).addResourceLocations("file:" + resourceLocations);
|
||||
}
|
||||
final Map<String, ?> additionalUrlMap = ReflectUtil
|
||||
.<SimpleUrlHandlerMapping>invoke(resourceHandlerRegistry, "getHandlerMapping")
|
||||
.getUrlMap();
|
||||
ReflectUtil.<Void>invoke(resourceHandlerMapping, "registerHandlers", additionalUrlMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取处理器方法
|
||||
*
|
||||
* @param request 请求
|
||||
* @return 处理器方法
|
||||
* @since 2.14.0
|
||||
*/
|
||||
public static HandlerMethod getHandlerMethod(HttpServletRequest request) {
|
||||
try {
|
||||
RequestMappingHandlerMapping handlerMapping = SpringUtil
|
||||
.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
|
||||
HandlerExecutionChain handlerExecutionChain = handlerMapping.getHandler(request);
|
||||
// 检查是否存在处理链
|
||||
if (handlerExecutionChain == null) {
|
||||
return null;
|
||||
}
|
||||
// 获取处理器
|
||||
Object handler = handlerExecutionChain.getHandler();
|
||||
if (handler instanceof HandlerMethod handlerMethod) {
|
||||
return handlerMethod;
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,198 +0,0 @@
|
||||
/*
|
||||
* 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.continew.starter.core.util;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ReflectUtil;
|
||||
import cn.hutool.extra.spring.SpringUtil;
|
||||
import jakarta.servlet.ServletContext;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.http.server.PathContainer;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.web.accept.ContentNegotiationManager;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerExecutionChain;
|
||||
import org.springframework.web.servlet.HandlerMapping;
|
||||
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
|
||||
import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping;
|
||||
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;
|
||||
import org.springframework.web.util.UrlPathHelper;
|
||||
import org.springframework.web.util.pattern.PathPattern;
|
||||
import org.springframework.web.util.pattern.PathPatternParser;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Spring Web 工具类
|
||||
*
|
||||
* @author Charles7c
|
||||
* @since 1.1.1
|
||||
*/
|
||||
public class SpringWebUtils {
|
||||
|
||||
private static final AntPathMatcher MATCHER = new AntPathMatcher();
|
||||
|
||||
private SpringWebUtils() {
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径是否匹配
|
||||
*
|
||||
* @param path 路径
|
||||
* @param patterns 匹配模式列表
|
||||
* @return 是否匹配
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public static boolean isMatch(String path, List<String> patterns) {
|
||||
return patterns.stream().anyMatch(pattern -> isMatch(path, pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径是否匹配
|
||||
*
|
||||
* @param path 路径
|
||||
* @param patterns 匹配模式列表
|
||||
* @return 是否匹配
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public static boolean isMatch(String path, String... patterns) {
|
||||
return Arrays.stream(patterns).anyMatch(pattern -> isMatch(path, pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径是否匹配
|
||||
*
|
||||
* @param path 路径
|
||||
* @param pattern 匹配模式
|
||||
* @return 是否匹配
|
||||
* @since 2.4.0
|
||||
*/
|
||||
public static boolean isMatch(String path, String pattern) {
|
||||
PathPattern pathPattern = PathPatternParser.defaultInstance.parse(pattern);
|
||||
PathContainer pathContainer = PathContainer.parsePath(path);
|
||||
return pathPattern.matches(pathContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径是否匹配 - Ant 风格
|
||||
*
|
||||
* @param path 路径
|
||||
* @param pattern 匹配模式
|
||||
* @return 是否匹配
|
||||
* @author echo
|
||||
* @since 2.15.0
|
||||
*/
|
||||
public static boolean isMatchAnt(String path, String pattern) {
|
||||
return MATCHER.match(pattern, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* 路径是否匹配 - Ant 风格
|
||||
*
|
||||
* @param path 路径
|
||||
* @param patterns 匹配模式列表
|
||||
* @return 是否匹配
|
||||
* @author echo
|
||||
* @since 2.15.0
|
||||
*/
|
||||
public static boolean isMatchAnt(String path, List<String> patterns) {
|
||||
return patterns.stream().anyMatch(pattern -> isMatchAnt(path, pattern));
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消注册静态资源映射
|
||||
*
|
||||
* @param handlerMap 静态资源映射
|
||||
*/
|
||||
public static void deRegisterResourceHandler(Map<String, String> handlerMap) {
|
||||
ApplicationContext applicationContext = SpringUtil.getApplicationContext();
|
||||
// 获取已经注册的映射
|
||||
final HandlerMapping resourceHandlerMapping = applicationContext
|
||||
.getBean("resourceHandlerMapping", HandlerMapping.class);
|
||||
final Map<String, Object> oldHandlerMap = (Map<String, Object>)ReflectUtil
|
||||
.getFieldValue(resourceHandlerMapping, "handlerMap");
|
||||
// 移除之前注册的映射
|
||||
for (Map.Entry<String, String> entry : handlerMap.entrySet()) {
|
||||
String pathPattern = CharSequenceUtil.appendIfMissing(entry.getKey(), StringConstants.PATH_PATTERN);
|
||||
oldHandlerMap.remove(pathPattern);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 注册静态资源映射
|
||||
*
|
||||
* @param handlerMap 静态资源映射
|
||||
*/
|
||||
public static void registerResourceHandler(Map<String, String> handlerMap) {
|
||||
ApplicationContext applicationContext = SpringUtil.getApplicationContext();
|
||||
// 获取已经注册的映射
|
||||
final HandlerMapping resourceHandlerMapping = applicationContext
|
||||
.getBean("resourceHandlerMapping", HandlerMapping.class);
|
||||
final Map<String, Object> oldHandlerMap = (Map<String, Object>)ReflectUtil
|
||||
.getFieldValue(resourceHandlerMapping, "handlerMap");
|
||||
// 重新注册映射
|
||||
final ServletContext servletContext = applicationContext.getBean(ServletContext.class);
|
||||
final ContentNegotiationManager contentNegotiationManager = applicationContext
|
||||
.getBean("mvcContentNegotiationManager", ContentNegotiationManager.class);
|
||||
final UrlPathHelper urlPathHelper = applicationContext.getBean("mvcUrlPathHelper", UrlPathHelper.class);
|
||||
final ResourceHandlerRegistry resourceHandlerRegistry = new ResourceHandlerRegistry(applicationContext, servletContext, contentNegotiationManager, urlPathHelper);
|
||||
for (Map.Entry<String, String> entry : handlerMap.entrySet()) {
|
||||
// 移除之前注册的映射
|
||||
String pathPattern = CharSequenceUtil.appendIfMissing(CharSequenceUtil.removeSuffix(entry
|
||||
.getKey(), StringConstants.SLASH), StringConstants.PATH_PATTERN);
|
||||
oldHandlerMap.remove(pathPattern);
|
||||
// 重新注册映射
|
||||
String resourceLocations = CharSequenceUtil.appendIfMissing(entry.getValue(), StringConstants.SLASH);
|
||||
resourceHandlerRegistry.addResourceHandler(pathPattern).addResourceLocations("file:" + resourceLocations);
|
||||
}
|
||||
final Map<String, ?> additionalUrlMap = ReflectUtil
|
||||
.<SimpleUrlHandlerMapping>invoke(resourceHandlerRegistry, "getHandlerMapping")
|
||||
.getUrlMap();
|
||||
ReflectUtil.<Void>invoke(resourceHandlerMapping, "registerHandlers", additionalUrlMap);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取处理器方法
|
||||
*
|
||||
* @param request 请求
|
||||
* @return 处理器方法
|
||||
* @since 2.14.0
|
||||
*/
|
||||
public static HandlerMethod getHandlerMethod(HttpServletRequest request) {
|
||||
try {
|
||||
RequestMappingHandlerMapping handlerMapping = SpringUtil
|
||||
.getBean("requestMappingHandlerMapping", RequestMappingHandlerMapping.class);
|
||||
HandlerExecutionChain handlerExecutionChain = handlerMapping.getHandler(request);
|
||||
// 检查是否存在处理链
|
||||
if (handlerExecutionChain == null) {
|
||||
return null;
|
||||
}
|
||||
// 获取处理器
|
||||
Object handler = handlerExecutionChain.getHandler();
|
||||
if (handler instanceof HandlerMethod handlerMethod) {
|
||||
return handlerMethod;
|
||||
}
|
||||
return null;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,13 +18,12 @@ package top.continew.starter.core.wrapper;
|
||||
|
||||
import jakarta.servlet.ReadListener;
|
||||
import jakarta.servlet.ServletInputStream;
|
||||
import jakarta.servlet.ServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletRequestWrapper;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.util.FastByteArrayOutputStream;
|
||||
import org.springframework.util.StreamUtils;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.ServletUtils;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.URLEncoder;
|
||||
@@ -77,8 +76,8 @@ public class RepeatReadRequestWrapper extends HttpServletRequestWrapper {
|
||||
? new FastByteArrayOutputStream(contentLength)
|
||||
: new FastByteArrayOutputStream();
|
||||
// 判断是否为文件上传请求
|
||||
if (!isMultipartContent(request)) {
|
||||
if (isFormRequest()) {
|
||||
if (!ServletUtils.isMultipart(request)) {
|
||||
if (ServletUtils.isForm(request)) {
|
||||
writeRequestParametersToCachedContent();
|
||||
} else {
|
||||
StreamUtils.copy(request.getInputStream(), cachedContent);
|
||||
@@ -90,7 +89,7 @@ public class RepeatReadRequestWrapper extends HttpServletRequestWrapper {
|
||||
@Override
|
||||
public ServletInputStream getInputStream() throws IOException {
|
||||
// 如果是文件上传,直接返回原始输入流
|
||||
if (isMultipartContent(super.getRequest())) {
|
||||
if (ServletUtils.isMultipart((HttpServletRequest)super.getRequest())) {
|
||||
return super.getRequest().getInputStream();
|
||||
}
|
||||
synchronized (this) {
|
||||
@@ -102,7 +101,7 @@ public class RepeatReadRequestWrapper extends HttpServletRequestWrapper {
|
||||
@Override
|
||||
public BufferedReader getReader() throws IOException {
|
||||
// 如果是文件上传,直接返回原始Reader
|
||||
if (isMultipartContent(super.getRequest())) {
|
||||
if (ServletUtils.isMultipart((HttpServletRequest)super.getRequest())) {
|
||||
return super.getRequest().getReader();
|
||||
}
|
||||
|
||||
@@ -153,22 +152,6 @@ public class RepeatReadRequestWrapper extends HttpServletRequestWrapper {
|
||||
return cachedContent;
|
||||
}
|
||||
|
||||
/**
|
||||
* 判断当前请求是否为 multipart/form-data 类型的文件上传请求。 该类型一般用于表单上传文件的场景,例如 enctype="multipart/form-data"。
|
||||
*
|
||||
* @param request 当前 HTTP 请求对象
|
||||
* @return true 表示为 multipart 文件上传请求;否则为 false
|
||||
*/
|
||||
public boolean isMultipartContent(ServletRequest request) {
|
||||
String contentType = request.getContentType();
|
||||
return contentType != null && contentType.toLowerCase().startsWith("multipart/");
|
||||
}
|
||||
|
||||
private boolean isFormRequest() {
|
||||
String contentType = getContentType();
|
||||
return (contentType != null && contentType.contains(MediaType.APPLICATION_FORM_URLENCODED_VALUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* Body 缓存的ServletInputStream实现 DefaultServerRequestBuilder.BodyInputStream
|
||||
*/
|
||||
|
||||
@@ -20,7 +20,7 @@ import jakarta.servlet.ServletOutputStream;
|
||||
import jakarta.servlet.WriteListener;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import jakarta.servlet.http.HttpServletResponseWrapper;
|
||||
import org.springframework.http.MediaType;
|
||||
import top.continew.starter.core.util.ServletUtils;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
@@ -46,30 +46,11 @@ public class RepeatReadResponseWrapper extends HttpServletResponseWrapper {
|
||||
|
||||
public RepeatReadResponseWrapper(HttpServletResponse response) {
|
||||
super(response);
|
||||
checkStreamingResponse();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setContentType(String type) {
|
||||
super.setContentType(type);
|
||||
// 根据 Content-Type 判断是否为流式响应
|
||||
if (type != null) {
|
||||
String lowerType = type.toLowerCase();
|
||||
isStreamingResponse = lowerType.contains(MediaType.TEXT_EVENT_STREAM_VALUE);
|
||||
}
|
||||
}
|
||||
|
||||
private void checkStreamingResponse() {
|
||||
String contentType = getContentType();
|
||||
if (contentType != null) {
|
||||
String lowerType = contentType.toLowerCase();
|
||||
isStreamingResponse = lowerType.contains(MediaType.TEXT_EVENT_STREAM_VALUE);
|
||||
}
|
||||
isStreamingResponse = ServletUtils.isStream(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServletOutputStream getOutputStream() throws IOException {
|
||||
checkStreamingResponse();
|
||||
// 对于 SSE 流式响应,直接返回原始响应流,不做额外处理
|
||||
if (isStreamingResponse) {
|
||||
return super.getOutputStream();
|
||||
@@ -103,7 +84,6 @@ public class RepeatReadResponseWrapper extends HttpServletResponseWrapper {
|
||||
|
||||
@Override
|
||||
public PrintWriter getWriter() throws IOException {
|
||||
checkStreamingResponse();
|
||||
if (isStreamingResponse) {
|
||||
// 对于 SSE 流式响应,直接返回原始响应写入器,不做额外处理
|
||||
return super.getWriter();
|
||||
|
||||
@@ -22,7 +22,7 @@ import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import top.continew.starter.core.util.SpringWebUtils;
|
||||
import top.continew.starter.core.util.SpringUtils;
|
||||
import top.continew.starter.encrypt.api.annotation.ApiEncrypt;
|
||||
import top.continew.starter.encrypt.api.autoconfigure.ApiEncryptProperties;
|
||||
|
||||
@@ -93,7 +93,7 @@ public class ApiEncryptFilter implements Filter {
|
||||
*/
|
||||
private boolean isResponseEncrypt(HttpServletRequest request) {
|
||||
// 获取 API 加密注解
|
||||
ApiEncrypt apiEncrypt = Optional.ofNullable(SpringWebUtils.getHandlerMethod(request))
|
||||
ApiEncrypt apiEncrypt = Optional.ofNullable(SpringUtils.getHandlerMethod(request))
|
||||
.map(h -> h.getMethodAnnotation(ApiEncrypt.class))
|
||||
.orElse(null);
|
||||
return apiEncrypt != null && apiEncrypt.response();
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package top.continew.starter.log.aspect;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
import org.aspectj.lang.annotation.Aspect;
|
||||
@@ -23,6 +25,8 @@ import org.aspectj.lang.annotation.Pointcut;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import top.continew.starter.log.handler.LogHandler;
|
||||
import top.continew.starter.log.http.servlet.RecordableServletHttpRequest;
|
||||
import top.continew.starter.log.http.servlet.RecordableServletHttpResponse;
|
||||
import top.continew.starter.log.model.AccessLogContext;
|
||||
import top.continew.starter.log.model.LogProperties;
|
||||
|
||||
@@ -103,16 +107,22 @@ public class AccessLogAspect {
|
||||
if (attributes == null) {
|
||||
return joinPoint.proceed();
|
||||
}
|
||||
HttpServletRequest request = attributes.getRequest();
|
||||
HttpServletResponse response = attributes.getResponse();
|
||||
try {
|
||||
// 开始访问日志记录
|
||||
logHandler.accessLogStart(AccessLogContext.builder()
|
||||
.startTime(startTime)
|
||||
.request(new RecordableServletHttpRequest(request))
|
||||
.properties(logProperties)
|
||||
.build());
|
||||
return joinPoint.proceed();
|
||||
} finally {
|
||||
Instant endTime = Instant.now();
|
||||
logHandler.accessLogFinish(AccessLogContext.builder().endTime(endTime).build());
|
||||
logHandler.accessLogFinish(AccessLogContext.builder()
|
||||
.endTime(endTime)
|
||||
.response(new RecordableServletHttpResponse(response))
|
||||
.build());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package top.continew.starter.log.aspect;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.aspectj.lang.JoinPoint;
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.aspectj.lang.annotation.Around;
|
||||
@@ -27,6 +29,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import top.continew.starter.core.util.ServletUtils;
|
||||
import top.continew.starter.log.annotation.Log;
|
||||
import top.continew.starter.log.dao.LogDao;
|
||||
import top.continew.starter.log.handler.LogHandler;
|
||||
@@ -75,6 +78,7 @@ public class LogAspect {
|
||||
public Object around(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||
Instant startTime = Instant.now();
|
||||
// 指定规则不记录
|
||||
HttpServletRequest request = ServletUtils.getRequest();
|
||||
Method targetMethod = this.getMethod(joinPoint);
|
||||
Class<?> targetClass = joinPoint.getTarget().getClass();
|
||||
if (!isRecord(targetMethod, targetClass)) {
|
||||
@@ -82,7 +86,7 @@ public class LogAspect {
|
||||
}
|
||||
String errorMsg = null;
|
||||
// 开始记录
|
||||
LogRecord.Started startedLogRecord = logHandler.start(startTime);
|
||||
LogRecord.Started startedLogRecord = logHandler.start(startTime, request);
|
||||
try {
|
||||
// 执行目标方法
|
||||
return joinPoint.proceed();
|
||||
@@ -92,7 +96,8 @@ public class LogAspect {
|
||||
} finally {
|
||||
try {
|
||||
Instant endTime = Instant.now();
|
||||
LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, logProperties
|
||||
HttpServletResponse response = ServletUtils.getResponse();
|
||||
LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, response, logProperties
|
||||
.getIncludes(), targetMethod, targetClass);
|
||||
// 记录异常信息
|
||||
if (errorMsg != null) {
|
||||
@@ -120,7 +125,7 @@ public class LogAspect {
|
||||
return false;
|
||||
}
|
||||
// 如果接口匹配排除列表,不记录日志
|
||||
if (logProperties.isMatch(attributes.getRequest().getRequestURI())) {
|
||||
if (logProperties.isMatchExcludeUri(attributes.getRequest().getRequestURI())) {
|
||||
return false;
|
||||
}
|
||||
return logHandler.isRecord(targetMethod, targetClass);
|
||||
|
||||
@@ -64,7 +64,6 @@ public class LogAutoConfiguration {
|
||||
* 日志过滤器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public FilterRegistrationBean<LogFilter> logFilter() {
|
||||
FilterRegistrationBean<LogFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new LogFilter(logProperties));
|
||||
|
||||
@@ -51,44 +51,41 @@ public class LogFilter extends OncePerRequestFilter {
|
||||
protected void doFilterInternal(@NonNull HttpServletRequest request,
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull FilterChain filterChain) throws ServletException, IOException {
|
||||
if (!this.isFilter(request)) {
|
||||
if (this.isNotFilter(request)) {
|
||||
filterChain.doFilter(request, response);
|
||||
return;
|
||||
}
|
||||
|
||||
boolean isExcludeUri = logProperties.isMatch(request.getRequestURI());
|
||||
|
||||
// 处理可重复读取的请求
|
||||
HttpServletRequest requestWrapper = (isExcludeUri || !this.isRequestWrapper(request))
|
||||
? request
|
||||
// 包装可重复读取请求及响应
|
||||
RepeatReadRequestWrapper wrappedRequest = request instanceof RepeatReadRequestWrapper wrapped
|
||||
? wrapped
|
||||
: new RepeatReadRequestWrapper(request);
|
||||
|
||||
// 处理可重复读取的响应
|
||||
HttpServletResponse responseWrapper = (isExcludeUri || !this.isResponseWrapper(response))
|
||||
? response
|
||||
RepeatReadResponseWrapper wrappedResponse = response instanceof RepeatReadResponseWrapper wrapped
|
||||
? wrapped
|
||||
: new RepeatReadResponseWrapper(response);
|
||||
filterChain.doFilter(wrappedRequest, wrappedResponse);
|
||||
|
||||
filterChain.doFilter(requestWrapper, responseWrapper);
|
||||
|
||||
// 如果响应被包装了,复制缓存数据到原始响应
|
||||
if (responseWrapper instanceof RepeatReadResponseWrapper wrappedResponse) {
|
||||
wrappedResponse.copyBodyToResponse();
|
||||
}
|
||||
// 复制缓存数据到原始响应
|
||||
wrappedResponse.copyBodyToResponse();
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否过滤请求
|
||||
* 是否不过滤
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @return 是否过滤请求
|
||||
* @return true: 不过滤; false: 过滤
|
||||
*/
|
||||
private boolean isFilter(HttpServletRequest request) {
|
||||
private boolean isNotFilter(HttpServletRequest request) {
|
||||
if (!isRequestValid(request)) {
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
// 不拦截 /error
|
||||
ServerProperties serverProperties = SpringUtil.getBean(ServerProperties.class);
|
||||
return !request.getRequestURI().equals(serverProperties.getError().getPath());
|
||||
if (request.getRequestURI().equals(serverProperties.getError().getPath())) {
|
||||
return true;
|
||||
}
|
||||
// 放行路径
|
||||
return logProperties.isMatchExcludeUri(request.getRequestURI());
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -105,24 +102,4 @@ public class LogFilter extends OncePerRequestFilter {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要包装输入流
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @return true:是;false:否
|
||||
*/
|
||||
private boolean isRequestWrapper(HttpServletRequest request) {
|
||||
return !(request instanceof RepeatReadRequestWrapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* 是否需要包装输出流
|
||||
*
|
||||
* @param response 响应对象
|
||||
* @return true:是;false:否
|
||||
*/
|
||||
private boolean isResponseWrapper(HttpServletResponse response) {
|
||||
return !(response instanceof RepeatReadResponseWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,15 +23,20 @@ import com.alibaba.ttl.TransmittableThreadLocal;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
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 org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.starter.log.annotation.Log;
|
||||
import top.continew.starter.log.enums.Include;
|
||||
import top.continew.starter.log.http.RecordableHttpRequest;
|
||||
import top.continew.starter.log.http.RecordableHttpResponse;
|
||||
import top.continew.starter.log.http.servlet.RecordableServletHttpRequest;
|
||||
import top.continew.starter.log.http.servlet.RecordableServletHttpResponse;
|
||||
import top.continew.starter.log.model.AccessLogContext;
|
||||
import top.continew.starter.log.model.AccessLogProperties;
|
||||
import top.continew.starter.log.model.LogRecord;
|
||||
import top.continew.starter.log.util.AccessLogUtils;
|
||||
import top.continew.starter.core.util.ServletUtils;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.time.Duration;
|
||||
@@ -74,18 +79,19 @@ public abstract class AbstractLogHandler implements LogHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogRecord.Started start(Instant startTime) {
|
||||
return LogRecord.start(startTime);
|
||||
public LogRecord.Started start(Instant startTime, HttpServletRequest request) {
|
||||
return LogRecord.start(startTime, new RecordableServletHttpRequest(request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogRecord finish(LogRecord.Started started,
|
||||
Instant endTime,
|
||||
HttpServletResponse response,
|
||||
Set<Include> includes,
|
||||
Method targetMethod,
|
||||
Class<?> targetClass) {
|
||||
Set<Include> includeSet = this.getIncludes(includes, targetMethod, targetClass);
|
||||
LogRecord logRecord = this.finish(started, endTime, includeSet);
|
||||
LogRecord logRecord = this.finish(started, endTime, response, includeSet);
|
||||
// 记录日志描述
|
||||
if (includeSet.contains(Include.DESCRIPTION)) {
|
||||
this.logDescription(logRecord, targetMethod);
|
||||
@@ -98,8 +104,11 @@ public abstract class AbstractLogHandler implements LogHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public LogRecord finish(LogRecord.Started started, Instant endTime, Set<Include> includes) {
|
||||
return started.finish(endTime, includes);
|
||||
public LogRecord finish(LogRecord.Started started,
|
||||
Instant endTime,
|
||||
HttpServletResponse response,
|
||||
Set<Include> includes) {
|
||||
return started.finish(endTime, new RecordableServletHttpResponse(response), includes);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -186,30 +195,33 @@ public abstract class AbstractLogHandler implements LogHandler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accessLogStart(AccessLogContext accessLogContext) {
|
||||
AccessLogProperties properties = accessLogContext.getProperties().getAccessLog();
|
||||
// 是否需要打印 规则: 是否打印开关 或 放行路径
|
||||
if (!properties.isEnabled() || AccessLogUtils.exclusionPath(accessLogContext.getProperties(), ServletUtils
|
||||
.getRequestPath())) {
|
||||
public void accessLogStart(AccessLogContext context) {
|
||||
AccessLogProperties properties = context.getProperties().getAccessLog();
|
||||
// 是否需要打印
|
||||
if (!properties.isEnabled() || AccessLogUtils.exclusionPath(context.getProperties(), context.getRequest()
|
||||
.getPath())) {
|
||||
return;
|
||||
}
|
||||
// 构建上下文
|
||||
logContextThread.set(accessLogContext);
|
||||
String param = AccessLogUtils.getParam(properties);
|
||||
log.info(param != null ? "[Start] [{}] {} param: {}" : "[Start] [{}] {}", ServletUtils
|
||||
.getRequestMethod(), ServletUtils.getRequestPath(), param);
|
||||
logContextThread.set(context);
|
||||
RecordableHttpRequest request = context.getRequest();
|
||||
String param = AccessLogUtils.getParam(request, properties);
|
||||
log.info(param != null ? "[Start] [{}] {} param: {}" : "[Start] [{}] {}", request.getMethod(), request
|
||||
.getPath(), param);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void accessLogFinish(AccessLogContext accessLogContext) {
|
||||
public void accessLogFinish(AccessLogContext context) {
|
||||
AccessLogContext logContext = logContextThread.get();
|
||||
if (ObjectUtil.isEmpty(logContext)) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Duration timeTaken = Duration.between(logContext.getStartTime(), accessLogContext.getEndTime());
|
||||
log.info("[End] [{}] {} {} {}ms", ServletUtils.getRequestMethod(), ServletUtils
|
||||
.getRequestPath(), ServletUtils.getResponseStatus(), timeTaken.toMillis());
|
||||
RecordableHttpRequest request = logContext.getRequest();
|
||||
RecordableHttpResponse response = context.getResponse();
|
||||
Duration timeTaken = Duration.between(logContext.getStartTime(), context.getEndTime());
|
||||
log.info("[End] [{}] {} {} {}ms", request.getMethod(), request.getPath(), response.getStatus(), timeTaken
|
||||
.toMillis());
|
||||
} finally {
|
||||
logContextThread.remove();
|
||||
}
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
|
||||
package top.continew.starter.log.handler;
|
||||
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import top.continew.starter.log.enums.Include;
|
||||
import top.continew.starter.log.model.AccessLogContext;
|
||||
import top.continew.starter.log.model.LogRecord;
|
||||
@@ -46,25 +48,28 @@ public interface LogHandler {
|
||||
* 开始日志记录
|
||||
*
|
||||
* @param startTime 开始时间
|
||||
* @param request 请求对象
|
||||
* @return 日志记录器
|
||||
*/
|
||||
LogRecord.Started start(Instant startTime);
|
||||
LogRecord.Started start(Instant startTime, HttpServletRequest request);
|
||||
|
||||
/**
|
||||
* 结束日志记录
|
||||
*
|
||||
* @param started 开始日志记录器
|
||||
* @param endTime 结束时间
|
||||
* @param response 响应对象
|
||||
* @param includes 包含信息
|
||||
* @return 日志记录
|
||||
*/
|
||||
LogRecord finish(LogRecord.Started started, Instant endTime, Set<Include> includes);
|
||||
LogRecord finish(LogRecord.Started started, Instant endTime, HttpServletResponse response, Set<Include> includes);
|
||||
|
||||
/**
|
||||
* 结束日志记录
|
||||
*
|
||||
* @param started 开始日志记录器-
|
||||
* @param endTime 结束时间
|
||||
* @param response 响应对象
|
||||
* @param includes 包含信息
|
||||
* @param targetMethod 目标方法
|
||||
* @param targetClass 目标类
|
||||
@@ -72,6 +77,7 @@ public interface LogHandler {
|
||||
*/
|
||||
LogRecord finish(LogRecord.Started started,
|
||||
Instant endTime,
|
||||
HttpServletResponse response,
|
||||
Set<Include> includes,
|
||||
Method targetMethod,
|
||||
Class<?> targetClass);
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* 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.continew.starter.log.http;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 可记录的 HTTP 请求信息
|
||||
*
|
||||
* @author Andy Wilkinson(Spring Boot Actuator)
|
||||
* @author Phillip Webb(Spring Boot Actuator)
|
||||
* @author Charles7c
|
||||
* @author echo
|
||||
* @see RecordableHttpResponse
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public interface RecordableHttpRequest {
|
||||
|
||||
/**
|
||||
* 获取请求方式
|
||||
*
|
||||
* @return 请求方式
|
||||
*/
|
||||
String getMethod();
|
||||
|
||||
/**
|
||||
* 获取 URL
|
||||
*
|
||||
* @return URL
|
||||
*/
|
||||
URI getUrl();
|
||||
|
||||
/**
|
||||
* 获取路径
|
||||
* <p>/foo/bar</p>
|
||||
*
|
||||
* @return 路径
|
||||
* @since 2.10.0
|
||||
*/
|
||||
String getPath();
|
||||
|
||||
/**
|
||||
* 获取请求头
|
||||
*
|
||||
* @return 请求头
|
||||
*/
|
||||
Map<String, String> getHeaders();
|
||||
|
||||
/**
|
||||
* 获取请求体
|
||||
*
|
||||
* @return 请求体
|
||||
*/
|
||||
String getBody();
|
||||
|
||||
/**
|
||||
* 获取请求参数
|
||||
*
|
||||
* @return 请求参数
|
||||
*/
|
||||
String getParams();
|
||||
|
||||
/**
|
||||
* 获取 IP
|
||||
*
|
||||
* @return IP
|
||||
*/
|
||||
String getIp();
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.continew.starter.log.http;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 可记录的 HTTP 响应信息
|
||||
*
|
||||
* @author Andy Wilkinson(Spring Boot Actuator)
|
||||
* @author Charles7c
|
||||
* @see RecordableHttpRequest
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public interface RecordableHttpResponse {
|
||||
|
||||
/**
|
||||
* 获取状态码
|
||||
*
|
||||
* @return 状态码
|
||||
*/
|
||||
int getStatus();
|
||||
|
||||
/**
|
||||
* 获取响应头
|
||||
*
|
||||
* @return 响应头
|
||||
*/
|
||||
Map<String, String> getHeaders();
|
||||
|
||||
/**
|
||||
* 获取响应体
|
||||
*
|
||||
* @return 响应体
|
||||
*/
|
||||
String getBody();
|
||||
|
||||
/**
|
||||
* 获取响应参数
|
||||
*
|
||||
* @return 响应参数
|
||||
*/
|
||||
String getParams();
|
||||
}
|
||||
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* 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.continew.starter.log.http.servlet;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.springframework.web.util.UriUtils;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.ServletUtils;
|
||||
import top.continew.starter.core.wrapper.RepeatReadRequestWrapper;
|
||||
import top.continew.starter.log.http.RecordableHttpRequest;
|
||||
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 可记录的 HTTP 请求信息适配器
|
||||
*
|
||||
* @author Andy Wilkinson(Spring Boot Actuator)
|
||||
* @author Charles7c
|
||||
* @author echo
|
||||
* @since 1.1.0
|
||||
*/
|
||||
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 getUrl() {
|
||||
String queryString = request.getQueryString();
|
||||
if (CharSequenceUtil.isBlank(queryString)) {
|
||||
return URI.create(request.getRequestURL().toString());
|
||||
}
|
||||
try {
|
||||
StringBuilder urlBuilder = this.appendQueryString(queryString);
|
||||
return new URI(urlBuilder.toString());
|
||||
} catch (URISyntaxException e) {
|
||||
String encoded = UriUtils.encodeQuery(queryString, StandardCharsets.UTF_8);
|
||||
StringBuilder urlBuilder = this.appendQueryString(encoded);
|
||||
return URI.create(urlBuilder.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getPath() {
|
||||
return request.getRequestURI();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getHeaders() {
|
||||
return ServletUtils.getHeaderMap(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody() {
|
||||
try {
|
||||
RepeatReadRequestWrapper wrappedRequest = WebUtils
|
||||
.getNativeRequest(request, RepeatReadRequestWrapper.class);
|
||||
if (wrappedRequest == null || ServletUtils.isMultipart(request)) {
|
||||
return null;
|
||||
}
|
||||
String body = wrappedRequest.getContentAsString();
|
||||
return JSONUtil.isTypeJSON(body) ? body : null;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParams() {
|
||||
String body = this.getBody();
|
||||
return CharSequenceUtil.isNotBlank(body) ? body : JSONUtil.toJsonStr(ServletUtils.getParamMap(request));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getIp() {
|
||||
return ServletUtils.getClientIP(request);
|
||||
}
|
||||
|
||||
/**
|
||||
* URL 追加查询字符串
|
||||
*
|
||||
* @param queryString 查询字符串
|
||||
* @return StringBuilder
|
||||
*/
|
||||
private StringBuilder appendQueryString(String queryString) {
|
||||
return new StringBuilder().append(request.getRequestURL())
|
||||
.append(StringConstants.QUESTION_MARK)
|
||||
.append(queryString);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* 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.continew.starter.log.http.servlet;
|
||||
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import jakarta.servlet.http.HttpServletResponse;
|
||||
import org.springframework.web.util.WebUtils;
|
||||
import top.continew.starter.core.util.ServletUtils;
|
||||
import top.continew.starter.core.wrapper.RepeatReadResponseWrapper;
|
||||
import top.continew.starter.log.http.RecordableHttpResponse;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 可记录的 HTTP 响应信息适配器
|
||||
*
|
||||
* @author Andy Wilkinson(Spring Boot Actuator)
|
||||
* @author Charles7c
|
||||
* @author echo
|
||||
* @since 1.1.0
|
||||
*/
|
||||
public final class RecordableServletHttpResponse implements RecordableHttpResponse {
|
||||
|
||||
private final HttpServletResponse response;
|
||||
private final int status;
|
||||
|
||||
public RecordableServletHttpResponse(HttpServletResponse response) {
|
||||
this.response = response;
|
||||
this.status = response.getStatus();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getStatus() {
|
||||
return this.status;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, String> getHeaders() {
|
||||
return ServletUtils.getHeaderMap(response);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBody() {
|
||||
try {
|
||||
RepeatReadResponseWrapper wrappedResponse = WebUtils
|
||||
.getNativeResponse(response, RepeatReadResponseWrapper.class);
|
||||
if (wrappedResponse == null || wrappedResponse.isStreamingResponse()) {
|
||||
return null;
|
||||
}
|
||||
String body = wrappedResponse.getResponseContent();
|
||||
return JSONUtil.isTypeJSON(body) ? body : null;
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getParams() {
|
||||
return this.getBody();
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,9 @@
|
||||
|
||||
package top.continew.starter.log.model;
|
||||
|
||||
import top.continew.starter.log.http.RecordableHttpRequest;
|
||||
import top.continew.starter.log.http.RecordableHttpResponse;
|
||||
|
||||
import java.time.Instant;
|
||||
|
||||
/**
|
||||
@@ -36,6 +39,16 @@ public class AccessLogContext {
|
||||
*/
|
||||
private Instant endTime;
|
||||
|
||||
/**
|
||||
* 请求信息
|
||||
*/
|
||||
private final RecordableHttpRequest request;
|
||||
|
||||
/**
|
||||
* 响应信息
|
||||
*/
|
||||
private final RecordableHttpResponse response;
|
||||
|
||||
/**
|
||||
* 配置信息
|
||||
*/
|
||||
@@ -44,6 +57,8 @@ public class AccessLogContext {
|
||||
private AccessLogContext(Builder builder) {
|
||||
this.startTime = builder.startTime;
|
||||
this.endTime = builder.endTime;
|
||||
this.request = builder.request;
|
||||
this.response = builder.response;
|
||||
this.properties = builder.properties;
|
||||
}
|
||||
|
||||
@@ -55,6 +70,14 @@ public class AccessLogContext {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
public RecordableHttpRequest getRequest() {
|
||||
return request;
|
||||
}
|
||||
|
||||
public RecordableHttpResponse getResponse() {
|
||||
return response;
|
||||
}
|
||||
|
||||
public LogProperties getProperties() {
|
||||
return properties;
|
||||
}
|
||||
@@ -74,6 +97,8 @@ public class AccessLogContext {
|
||||
|
||||
private Instant startTime;
|
||||
private Instant endTime;
|
||||
private RecordableHttpRequest request;
|
||||
private RecordableHttpResponse response;
|
||||
private LogProperties properties;
|
||||
|
||||
private Builder() {
|
||||
@@ -89,6 +114,16 @@ public class AccessLogContext {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder request(RecordableHttpRequest request) {
|
||||
this.request = request;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder response(RecordableHttpResponse response) {
|
||||
this.response = response;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder properties(LogProperties properties) {
|
||||
this.properties = properties;
|
||||
return this;
|
||||
|
||||
@@ -19,8 +19,8 @@ package top.continew.starter.log.model;
|
||||
import org.springframework.boot.context.properties.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.NestedConfigurationProperty;
|
||||
import top.continew.starter.core.constant.PropertiesConstants;
|
||||
import top.continew.starter.core.util.SpringUtils;
|
||||
import top.continew.starter.log.enums.Include;
|
||||
import top.continew.starter.core.util.SpringWebUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
@@ -92,9 +92,9 @@ public class LogProperties {
|
||||
* 是否匹配放行路由
|
||||
*
|
||||
* @param uri 请求 URI
|
||||
* @return 是否匹配
|
||||
* @return true: 匹配; false: 不匹配
|
||||
*/
|
||||
public boolean isMatch(String uri) {
|
||||
return this.getExcludePatterns().stream().anyMatch(pattern -> SpringWebUtils.isMatch(uri, pattern));
|
||||
public boolean isMatchExcludeUri(String uri) {
|
||||
return this.getExcludePatterns().stream().anyMatch(pattern -> SpringUtils.isMatch(uri, pattern));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
package top.continew.starter.log.model;
|
||||
|
||||
import top.continew.starter.log.enums.Include;
|
||||
import top.continew.starter.log.http.RecordableHttpRequest;
|
||||
import top.continew.starter.log.http.RecordableHttpResponse;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
@@ -78,20 +80,22 @@ public class LogRecord {
|
||||
/**
|
||||
* 开始记录日志
|
||||
*
|
||||
* @param request 请求信息
|
||||
* @return 日志记录器
|
||||
*/
|
||||
public static Started start() {
|
||||
return start(Instant.now());
|
||||
public static Started start(RecordableHttpRequest request) {
|
||||
return start(Instant.now(), request);
|
||||
}
|
||||
|
||||
/**
|
||||
* 开始记录日志
|
||||
*
|
||||
* @param timestamp 开始时间
|
||||
* @param request 请求信息
|
||||
* @return 日志记录器
|
||||
*/
|
||||
public static Started start(Instant timestamp) {
|
||||
return new Started(timestamp);
|
||||
public static Started start(Instant timestamp, RecordableHttpRequest request) {
|
||||
return new Started(timestamp, request);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -101,20 +105,24 @@ public class LogRecord {
|
||||
|
||||
private final Instant timestamp;
|
||||
|
||||
private Started(Instant timestamp) {
|
||||
private final RecordableHttpRequest request;
|
||||
|
||||
private Started(Instant timestamp, RecordableHttpRequest request) {
|
||||
this.timestamp = timestamp;
|
||||
this.request = request;
|
||||
}
|
||||
|
||||
/**
|
||||
* 结束日志记录
|
||||
*
|
||||
* @param timestamp 结束时间
|
||||
* @param response 响应信息
|
||||
* @param includes 包含信息
|
||||
* @return 日志记录
|
||||
*/
|
||||
public LogRecord finish(Instant timestamp, Set<Include> includes) {
|
||||
LogRequest logRequest = new LogRequest(includes);
|
||||
LogResponse logResponse = new LogResponse(includes);
|
||||
public LogRecord finish(Instant timestamp, RecordableHttpResponse response, Set<Include> includes) {
|
||||
LogRequest logRequest = new LogRequest(this.request, includes);
|
||||
LogResponse logResponse = new LogResponse(response, includes);
|
||||
Duration duration = Duration.between(this.timestamp, timestamp);
|
||||
return new LogRecord(this.timestamp, logRequest, logResponse, duration);
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import top.continew.starter.core.util.ExceptionUtils;
|
||||
import top.continew.starter.core.util.IpUtils;
|
||||
import top.continew.starter.core.util.ServletUtils;
|
||||
import top.continew.starter.log.enums.Include;
|
||||
import top.continew.starter.log.http.RecordableHttpRequest;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Map;
|
||||
@@ -63,7 +64,7 @@ public class LogRequest {
|
||||
/**
|
||||
* 请求参数
|
||||
*/
|
||||
private Map<String, Object> param;
|
||||
private String params;
|
||||
|
||||
/**
|
||||
* IP 归属地
|
||||
@@ -80,15 +81,15 @@ public class LogRequest {
|
||||
*/
|
||||
private String os;
|
||||
|
||||
public LogRequest(Set<Include> includes) {
|
||||
this.method = ServletUtils.getRequestMethod();
|
||||
this.url = ServletUtils.getRequestUrl();
|
||||
this.ip = ServletUtils.getRequestIp();
|
||||
this.headers = (includes.contains(Include.REQUEST_HEADERS)) ? ServletUtils.getRequestHeaders() : null;
|
||||
public LogRequest(RecordableHttpRequest request, Set<Include> includes) {
|
||||
this.method = request.getMethod();
|
||||
this.url = request.getUrl();
|
||||
this.ip = request.getIp();
|
||||
this.headers = (includes.contains(Include.REQUEST_HEADERS)) ? request.getHeaders() : null;
|
||||
if (includes.contains(Include.REQUEST_BODY)) {
|
||||
this.body = ServletUtils.getRequestBody();
|
||||
this.body = request.getBody();
|
||||
} else if (includes.contains(Include.REQUEST_PARAM)) {
|
||||
this.param = ServletUtils.getRequestParams();
|
||||
this.params = request.getParams();
|
||||
}
|
||||
this.address = (includes.contains(Include.IP_ADDRESS))
|
||||
? ExceptionUtils.exToNull(() -> IpUtils.getIpv4Address(this.ip))
|
||||
@@ -148,12 +149,12 @@ public class LogRequest {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public Map<String, Object> getParam() {
|
||||
return param;
|
||||
public String getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public void setParam(Map<String, Object> param) {
|
||||
this.param = param;
|
||||
public void setParams(String params) {
|
||||
this.params = params;
|
||||
}
|
||||
|
||||
public String getAddress() {
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
package top.continew.starter.log.model;
|
||||
|
||||
import top.continew.starter.log.enums.Include;
|
||||
import top.continew.starter.core.util.ServletUtils;
|
||||
import top.continew.starter.log.http.RecordableHttpResponse;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -48,15 +48,15 @@ public class LogResponse {
|
||||
/**
|
||||
* 响应参数
|
||||
*/
|
||||
private Map<String, Object> param;
|
||||
private String params;
|
||||
|
||||
public LogResponse(Set<Include> includes) {
|
||||
this.status = ServletUtils.getResponseStatus();
|
||||
this.headers = (includes.contains(Include.RESPONSE_HEADERS)) ? ServletUtils.getResponseHeaders() : null;
|
||||
public LogResponse(RecordableHttpResponse response, Set<Include> includes) {
|
||||
this.status = response.getStatus();
|
||||
this.headers = (includes.contains(Include.RESPONSE_HEADERS)) ? response.getHeaders() : null;
|
||||
if (includes.contains(Include.RESPONSE_BODY)) {
|
||||
this.body = ServletUtils.getResponseBody();
|
||||
this.body = response.getBody();
|
||||
} else if (includes.contains(Include.RESPONSE_PARAM)) {
|
||||
this.param = ServletUtils.getResponseParams();
|
||||
this.params = response.getParams();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,11 +84,11 @@ public class LogResponse {
|
||||
this.body = body;
|
||||
}
|
||||
|
||||
public Map<String, Object> getParam() {
|
||||
return param;
|
||||
public String getParams() {
|
||||
return params;
|
||||
}
|
||||
|
||||
public void setParam(Map<String, Object> param) {
|
||||
this.param = param;
|
||||
public void setParams(String params) {
|
||||
this.params = params;
|
||||
}
|
||||
}
|
||||
@@ -17,14 +17,15 @@
|
||||
package top.continew.starter.log.util;
|
||||
|
||||
import cn.hutool.core.text.CharSequenceUtil;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.json.JSONUtil;
|
||||
import top.continew.starter.core.util.SpringUtils;
|
||||
import top.continew.starter.log.http.RecordableHttpRequest;
|
||||
import top.continew.starter.log.model.AccessLogProperties;
|
||||
import top.continew.starter.log.model.LogProperties;
|
||||
import top.continew.starter.core.util.ServletUtils;
|
||||
import top.continew.starter.core.util.SpringWebUtils;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -48,37 +49,42 @@ public class AccessLogUtils {
|
||||
/**
|
||||
* 获取参数信息
|
||||
*
|
||||
* @param request 请求对象
|
||||
* @param properties 属性
|
||||
* @return {@link String }
|
||||
*/
|
||||
public static String getParam(AccessLogProperties properties) {
|
||||
public static String getParam(RecordableHttpRequest request, AccessLogProperties properties) {
|
||||
// 是否需要打印请求参数
|
||||
if (!properties.isPrintRequestParam()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 参数为空返回空
|
||||
Object params;
|
||||
try {
|
||||
params = getAccessLogReqParam();
|
||||
} catch (Exception e) {
|
||||
String params = request.getParams();
|
||||
if (CharSequenceUtil.isBlank(params)) {
|
||||
return null;
|
||||
}
|
||||
if (ObjectUtil.isEmpty(params)) {
|
||||
return null;
|
||||
|
||||
Object paramObj;
|
||||
if (JSONUtil.isTypeJSONArray(params)) {
|
||||
paramObj = JSONUtil.toBean(params, List.class);
|
||||
} else if (JSONUtil.isTypeJSONObject(params)) {
|
||||
paramObj = JSONUtil.toBean(params, Map.class);
|
||||
} else {
|
||||
paramObj = params;
|
||||
}
|
||||
|
||||
// 是否需要对特定入参脱敏
|
||||
if (properties.isParamSensitive()) {
|
||||
params = processSensitiveParams(params, properties.getSensitiveParams());
|
||||
paramObj = processSensitiveParams(paramObj, properties.getSensitiveParams());
|
||||
}
|
||||
|
||||
// 是否自动截断超长参数值
|
||||
if (properties.isLongParamTruncate()) {
|
||||
params = processTruncateLongParams(params, properties.getLongParamThreshold(), properties
|
||||
paramObj = processTruncateLongParams(paramObj, properties.getLongParamThreshold(), properties
|
||||
.getLongParamMaxLength(), properties.getLongParamSuffix());
|
||||
}
|
||||
return JSONUtil.toJsonStr(params);
|
||||
return JSONUtil.toJsonStr(paramObj);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -90,8 +96,8 @@ public class AccessLogUtils {
|
||||
*/
|
||||
public static boolean exclusionPath(LogProperties properties, String path) {
|
||||
// 放行路由配置的排除检查
|
||||
return properties.isMatch(path) || RESOURCE_PATH.stream()
|
||||
.anyMatch(resourcePath -> SpringWebUtils.isMatchAnt(path, resourcePath));
|
||||
return properties.isMatchExcludeUri(path) || RESOURCE_PATH.stream()
|
||||
.anyMatch(resourcePath -> SpringUtils.isMatchAnt(path, resourcePath));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -106,7 +112,7 @@ public class AccessLogUtils {
|
||||
return filterSensitiveParams((Map<String, Object>)params, sensitiveParams);
|
||||
} else if (params instanceof List) {
|
||||
return ((List<?>)params).stream()
|
||||
.filter(item -> item instanceof Map)
|
||||
.filter(Map.class::isInstance)
|
||||
.map(item -> filterSensitiveParams((Map<String, Object>)item, sensitiveParams))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
@@ -183,25 +189,4 @@ public class AccessLogUtils {
|
||||
}
|
||||
return truncatedParams;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取访问日志请求参数
|
||||
*
|
||||
* @return {@link Object }
|
||||
*/
|
||||
private static Object getAccessLogReqParam() {
|
||||
String body = ServletUtils.getRequestBody();
|
||||
if (CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body)) {
|
||||
try {
|
||||
if (JSONUtil.isTypeJSONArray(body)) {
|
||||
return JSONUtil.toBean(body, List.class);
|
||||
} else {
|
||||
return JSONUtil.toBean(body, Map.class);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(ServletUtils.getRequestParams());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,6 @@ public class LogAutoConfiguration implements WebMvcConfigurer {
|
||||
* 日志过滤器
|
||||
*/
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public FilterRegistrationBean<LogFilter> logFilter() {
|
||||
FilterRegistrationBean<LogFilter> registrationBean = new FilterRegistrationBean<>();
|
||||
registrationBean.setFilter(new LogFilter(logProperties));
|
||||
|
||||
@@ -24,10 +24,12 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.web.method.HandlerMethod;
|
||||
import org.springframework.web.servlet.HandlerInterceptor;
|
||||
import top.continew.starter.log.dao.LogDao;
|
||||
import top.continew.starter.log.handler.LogHandler;
|
||||
import top.continew.starter.log.http.servlet.RecordableServletHttpRequest;
|
||||
import top.continew.starter.log.http.servlet.RecordableServletHttpResponse;
|
||||
import top.continew.starter.log.model.AccessLogContext;
|
||||
import top.continew.starter.log.model.LogProperties;
|
||||
import top.continew.starter.log.dao.LogDao;
|
||||
import top.continew.starter.log.handler.LogHandler;
|
||||
import top.continew.starter.log.model.LogRecord;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
@@ -58,10 +60,15 @@ public class LogInterceptor implements HandlerInterceptor {
|
||||
@NonNull HttpServletResponse response,
|
||||
@NonNull Object handler) {
|
||||
Instant startTime = Instant.now();
|
||||
logHandler.accessLogStart(AccessLogContext.builder().startTime(startTime).properties(logProperties).build());
|
||||
// 访问日志
|
||||
logHandler.accessLogStart(AccessLogContext.builder()
|
||||
.startTime(startTime)
|
||||
.request(new RecordableServletHttpRequest(request))
|
||||
.properties(logProperties)
|
||||
.build());
|
||||
// 开始日志记录
|
||||
if (this.isRecord(handler)) {
|
||||
LogRecord.Started startedLogRecord = logHandler.start(startTime);
|
||||
LogRecord.Started startedLogRecord = logHandler.start(startTime, request);
|
||||
logTtl.set(startedLogRecord);
|
||||
}
|
||||
return true;
|
||||
@@ -74,7 +81,11 @@ public class LogInterceptor implements HandlerInterceptor {
|
||||
Exception e) {
|
||||
try {
|
||||
Instant endTime = Instant.now();
|
||||
logHandler.accessLogFinish(AccessLogContext.builder().endTime(endTime).build());
|
||||
// 访问日志
|
||||
logHandler.accessLogFinish(AccessLogContext.builder()
|
||||
.endTime(endTime)
|
||||
.response(new RecordableServletHttpResponse(response))
|
||||
.build());
|
||||
LogRecord.Started startedLogRecord = logTtl.get();
|
||||
if (startedLogRecord == null) {
|
||||
return;
|
||||
@@ -83,7 +94,7 @@ public class LogInterceptor implements HandlerInterceptor {
|
||||
HandlerMethod handlerMethod = (HandlerMethod)handler;
|
||||
Method targetMethod = handlerMethod.getMethod();
|
||||
Class<?> targetClass = handlerMethod.getBeanType();
|
||||
LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, logProperties
|
||||
LogRecord logRecord = logHandler.finish(startedLogRecord, endTime, response, logProperties
|
||||
.getIncludes(), targetMethod, targetClass);
|
||||
logDao.add(logRecord);
|
||||
} catch (Exception ex) {
|
||||
|
||||
@@ -21,8 +21,8 @@ import jakarta.servlet.*;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import top.continew.starter.core.util.SpringUtils;
|
||||
import top.continew.starter.security.xss.autoconfigure.XssProperties;
|
||||
import top.continew.starter.core.util.SpringWebUtils;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
@@ -56,7 +56,7 @@ public class XssFilter implements Filter {
|
||||
if (servletRequest instanceof HttpServletRequest request && xssProperties.isEnabled()) {
|
||||
// 放行路由:忽略 XSS 过滤
|
||||
List<String> excludePatterns = xssProperties.getExcludePatterns();
|
||||
if (CollUtil.isNotEmpty(excludePatterns) && SpringWebUtils.isMatch(request
|
||||
if (CollUtil.isNotEmpty(excludePatterns) && SpringUtils.isMatch(request
|
||||
.getServletPath(), excludePatterns)) {
|
||||
filterChain.doFilter(request, servletResponse);
|
||||
return;
|
||||
@@ -64,7 +64,7 @@ public class XssFilter implements Filter {
|
||||
// 拦截路由:执行 XSS 过滤
|
||||
List<String> includePatterns = xssProperties.getIncludePatterns();
|
||||
if (CollUtil.isNotEmpty(includePatterns)) {
|
||||
if (SpringWebUtils.isMatch(request.getServletPath(), includePatterns)) {
|
||||
if (SpringUtils.isMatch(request.getServletPath(), includePatterns)) {
|
||||
filterChain.doFilter(new XssServletRequestWrapper(request, xssProperties), servletResponse);
|
||||
} else {
|
||||
filterChain.doFilter(request, servletResponse);
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import top.continew.starter.core.constant.StringConstants;
|
||||
import top.continew.starter.core.util.SpringWebUtils;
|
||||
import top.continew.starter.core.util.SpringUtils;
|
||||
import top.continew.starter.storage.autoconfigure.properties.LocalStorageConfig;
|
||||
import top.continew.starter.storage.common.exception.StorageException;
|
||||
import top.continew.starter.storage.domain.model.resp.FileInfo;
|
||||
@@ -70,7 +70,7 @@ public class LocalStorageStrategy implements StorageStrategy {
|
||||
*/
|
||||
public void registerResources(LocalStorageConfig config) {
|
||||
// 注册资源映射
|
||||
SpringWebUtils.registerResourceHandler(MapUtil.of(URLUtil.url(config.getEndpoint()).getPath(), config
|
||||
SpringUtils.registerResourceHandler(MapUtil.of(URLUtil.url(config.getEndpoint()).getPath(), config
|
||||
.getBucketName()));
|
||||
}
|
||||
|
||||
@@ -426,7 +426,7 @@ public class LocalStorageStrategy implements StorageStrategy {
|
||||
public void cleanup() {
|
||||
// 清理静态资源映射
|
||||
if (config != null) {
|
||||
SpringWebUtils.deRegisterResourceHandler(MapUtil.of(URLUtil.url(config.getEndpoint()).getPath(), config
|
||||
SpringUtils.deRegisterResourceHandler(MapUtil.of(URLUtil.url(config.getEndpoint()).getPath(), config
|
||||
.getBucketName()));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user