feat(log/aop): 新增 log-aop 组件模块(基于 AOP 实现日志记录)

This commit is contained in:
liquor
2024-12-11 09:03:09 +00:00
committed by Charles7c
parent 75874171db
commit 7c3f15a6f6
21 changed files with 614 additions and 11 deletions

View File

@@ -14,7 +14,7 @@
* limitations under the License.
*/
package top.continew.starter.log.interceptor.autoconfigure;
package top.continew.starter.log.interceptor.annotation;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import top.continew.starter.core.constant.PropertiesConstants;

View File

@@ -0,0 +1,65 @@
/*
* 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.interceptor.annotation;
import top.continew.starter.log.core.enums.Include;
import java.lang.annotation.*;
/**
* 日志注解
* <p>用于接口方法或类上,辅助 Spring Doc 使用效果最佳</p>
*
* @author Charles7c
* @since 1.1.0
*/
@Documented
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
/**
* 日志描述(仅用于接口方法上)
* <p>
* 优先级:@Log("描述") > @Operation(summary="描述")
* </p>
*/
String value() default "";
/**
* 所属模块(用于接口方法或类上)
* <p>
* 优先级: 接口方法上的 @Log(module = "模块") > 接口类上的 @Log(module = "模块") > @Tag(name = "模块") 内容
* </p>
*/
String module() default "";
/**
* 包含信息(在全局配置基础上扩展包含信息)
*/
Include[] includes() default {};
/**
* 排除信息(在全局配置基础上减少包含信息)
*/
Include[] excludes() default {};
/**
* 是否忽略日志记录(用于接口方法或类上)
*/
boolean ignore() default false;
}

View File

@@ -28,6 +28,7 @@ import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import top.continew.starter.log.core.dao.LogDao;
import top.continew.starter.log.core.dao.impl.LogDaoDefaultImpl;
import top.continew.starter.log.interceptor.annotation.ConditionalOnEnabledLog;
import top.continew.starter.log.interceptor.handler.LogFilter;
import top.continew.starter.log.interceptor.handler.LogInterceptor;
@@ -75,6 +76,6 @@ public class LogAutoConfiguration implements WebMvcConfigurer {
@PostConstruct
public void postConstruct() {
log.debug("[ContiNew Starter] - Auto Configuration 'Log' completed initialization.");
log.debug("[ContiNew Starter] - Auto Configuration 'Log-interceptor' completed initialization.");
}
}

View File

@@ -28,7 +28,9 @@ 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.core.annotation.Log;
import top.continew.starter.log.core.http.recordable.impl.RecordableServletHttpRequest;
import top.continew.starter.log.core.http.recordable.impl.RecordableServletHttpResponse;
import top.continew.starter.log.interceptor.annotation.Log;
import top.continew.starter.log.core.dao.LogDao;
import top.continew.starter.log.core.enums.Include;
import top.continew.starter.log.core.model.LogRecord;

View File

@@ -1,104 +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.log.interceptor.handler;
import cn.hutool.core.text.CharSequenceUtil;
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.continew.starter.core.constant.StringConstants;
import top.continew.starter.log.core.model.RecordableHttpRequest;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Map;
/**
* 可记录的 HTTP 请求信息适配器
*
* @author Andy WilkinsonSpring 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 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 getIp() {
return JakartaServletUtil.getClientIP(request);
}
@Override
public Map<String, String> getHeaders() {
return JakartaServletUtil.getHeaderMap(request);
}
@Override
public String getBody() {
ContentCachingRequestWrapper wrapper = WebUtils.getNativeRequest(request, ContentCachingRequestWrapper.class);
if (null != wrapper) {
String body = StrUtil.utf8Str(wrapper.getContentAsByteArray());
return JSONUtil.isTypeJSON(body) ? body : null;
}
return null;
}
@Override
public Map<String, Object> getParam() {
String body = this.getBody();
return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body)
? JSONUtil.toBean(body, Map.class)
: Collections.unmodifiableMap(request.getParameterMap());
}
private StringBuilder appendQueryString(String queryString) {
return new StringBuilder().append(request.getRequestURL())
.append(StringConstants.QUESTION_MARK)
.append(queryString);
}
}

View File

@@ -1,73 +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.log.interceptor.handler;
import cn.hutool.core.text.CharSequenceUtil;
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.continew.starter.log.core.model.RecordableHttpResponse;
import top.continew.starter.web.util.ServletUtils;
import java.util.Map;
/**
* 可记录的 HTTP 响应信息适配器
*
* @author Andy WilkinsonSpring 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, String> getHeaders() {
return ServletUtils.getHeaderMap(response);
}
@Override
public String getBody() {
ContentCachingResponseWrapper wrapper = WebUtils
.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (null != wrapper) {
String body = StrUtil.utf8Str(wrapper.getContentAsByteArray());
return JSONUtil.isTypeJSON(body) ? body : null;
}
return null;
}
@Override
public Map<String, Object> getParam() {
String body = this.getBody();
return CharSequenceUtil.isNotBlank(body) && JSONUtil.isTypeJSON(body) ? JSONUtil.toBean(body, Map.class) : null;
}
}