一、主要坐标
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.41</version>
</dependency>
完整pom.xml文件(自行参考)
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.xpf</groupId>
<artifactId>springboot_test</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>springboot_test</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.1-jre</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.41</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
二、aop类
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.PathMatcher;
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.multipart.MultipartFile;
import org.springframework.web.util.ContentCachingResponseWrapper;
import org.springframework.web.util.WebUtils;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.List;
import java.util.Set;
import java.util.stream.Collectors;
/**
* @Author xpf
* @Date 2023/8/2 10:30
* @Version 1.0
*/
@Slf4j
@Aspect
@Component
@Order(99999)
public class ControllerAop {
private static final String ACTIONAUTH = "actionAuth_";
private static final String ACTION = "mddaction_";
private static final String SUFFIX_LSIT = "List";
private Set<String> excludeUris = Sets.newHashSet();
private static final PathMatcher URI_PATH_MATCHER = new AntPathMatcher();
private static final List<String> DEFAULT_DOWNLOAD_CONTENT_TYPE = Lists.newArrayList(
"application/vnd.ms-excel",//.xls
"application/msexcel",//.xls
"application/cvs",//.cvs
MediaType.APPLICATION_OCTET_STREAM_VALUE,//.*( 二进制流,不知道下载文件类型)
"application/x-xls",//.xls
"application/msword",//.doc
MediaType.TEXT_PLAIN_VALUE,//.txt
"application/x-gzip"//.gz
);
@Pointcut("execution (* com..*Controller.*(..)) && !execution (* *..ExtendController.testConnect(..))")
public void serviceApi() {
}
@Around("serviceApi()")
public Object processLog(ProceedingJoinPoint jp) throws Throwable {
Object resObj = null;
try{
Method method = ((MethodSignature) jp.getSignature()).getMethod();
//获取方法名称
String methodName = method.getName();
//获取参数名称
LocalVariableTableParameterNameDiscoverer paramNames = new LocalVariableTableParameterNameDiscoverer();
String[] params = paramNames.getParameterNames(method);
Class<?> classTarget=jp.getTarget().getClass();
Class<?>[] par=((MethodSignature) jp.getSignature()).getParameterTypes();
HttpServletResponse response=null;
RequestAttributes ra;
ServletRequestAttributes sra;
HttpServletRequest request=null;
String url=null;
String _contextPath=null;
ContentCachingResponseWrapper wrapper = null;
ra = RequestContextHolder.getRequestAttributes();
sra = (ServletRequestAttributes) ra;
if (null != sra) {
request = sra.getRequest();
// 如果是被排除的uri,不记录log
if (matchExclude(request.getRequestURI())) {
return jp.proceed();
}
url = request.getRequestURI();
if (null != request.getServletContext()) {
_contextPath = request.getServletContext().getContextPath();
}
response = sra.getResponse();
}
if(StringUtils.isNotBlank(_contextPath)){
url=url.replaceFirst(_contextPath, "");
}
long start = System.currentTimeMillis();
String finalUrl = url;
String ip = getRemortIP(request);
//获取参数
Object[] args = jp.getArgs();
//替换response为包装类型ContentCachingResponseWrapper
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof HttpServletResponse){
wrapper = new ContentCachingResponseWrapper((HttpServletResponse) args[i]);
args[i] = wrapper;
break;
}
}
//过滤掉request和response,以及文件类型,不能序列化
List<Object> filteredArgs = Arrays.stream(args)
.filter(arg -> (!(arg instanceof HttpServletRequest) && !(arg instanceof HttpServletResponse)
&& !(arg instanceof MultipartFile)))
.collect(Collectors.toList());
JSONObject rqsJson = new JSONObject();
rqsJson.put("rqsMethod", methodName);
if (filteredArgs == null || filteredArgs.isEmpty()) {
rqsJson.put("rqsParams", null);
} else {
//拼接请求参数
// Map<String, Object> rqsParams = IntStream.range(0, filteredArgs.size())
// .boxed()
// .collect(Collectors.toMap(j -> params[j], j -> filteredArgs.get(j)));
rqsJson.put("rqsParams", JSON.toJSONString(filteredArgs));
}
try {
resObj = jp.proceed(args);
} catch (Exception e) {
log.error(methodName + "方法执行异常!", e);
// //可以使用线程池将日志信息存入表中
// threadPool.submit(() -> {
// operationLogService.insert(JSON.toJSONString(rqsJson), null,1, finalUrl, ip, e.getMessage(), System.currentTimeMillis() - start);
// });
log.info("ip地址为:{} \n,url为:{} \n,耗时:{}毫秒 \n,入参:{} \n, 异常信息为:{} \n",
ip, finalUrl, System.currentTimeMillis() - start, JSON.toJSONString(rqsJson), e.getMessage());
throw e;
}
if (isDownload(response)) {
return resObj;
}
String outPrams = null;
if (resObj == null) {
if (wrapper != null){
byte[] content = wrapper.getContentAsByteArray();
if (content.length > 0) {
outPrams = new String(content, wrapper.getCharacterEncoding());
copyResponse(wrapper);
}
}
}else {
//排除文件类型,无法序列化为 JSON 字符串
if (!(resObj instanceof MultipartFile)){
outPrams = JSON.toJSONString(resObj);
}
}
String finalOutPrams = outPrams;
// threadPool.submit(() ->
// operationLogService.insert(JSON.toJSONString(rqsJson), finalOutPrams,0, finalUrl, ip, null, System.currentTimeMillis() - start)
// );
log.info("ip地址为:{} \n,url为:{} \n,耗时:{}毫秒 \n,入参:{} \n, 出参:{} \n",
ip, finalUrl, System.currentTimeMillis() - start, JSON.toJSONString(rqsJson), finalOutPrams);
} catch(Exception e){
log.error("AopAddLogError" + e.getMessage() ,e);
throw e;
}
return resObj;
}
private void copyResponse(final HttpServletResponse response) {
final ContentCachingResponseWrapper wrapper = WebUtils.getNativeResponse(response, ContentCachingResponseWrapper.class);
if (wrapper != null) {
try {
wrapper.copyBodyToResponse();
} catch (IOException ignored) {
}
}
}
private boolean isDownload(final HttpServletResponse response) {
final String contentType = response.getContentType();
if (StringUtils.isBlank(contentType)) {
return false;
}
return DEFAULT_DOWNLOAD_CONTENT_TYPE.stream().anyMatch(it -> StringUtils.equalsIgnoreCase(it, contentType));
}
private static String getRemortIP(HttpServletRequest request) {
String ip = getXForwardedFor(request);
return ip.indexOf(",") > 0 ? ip.split(",")[0].trim() : ip;
}
private static String getXForwardedFor(HttpServletRequest request) {
String ip = request.getHeader("x-original-forwarded-for");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forwarded-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
ip = request.getHeader("X-Forward-For");
if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
return request.getRemoteAddr();
}
}
}
return ip;
}
private boolean matchExclude(final String uri) {
if (CollectionUtils.isEmpty(excludeUris)) {
return false;
}
for (final String excludeUri : excludeUris) {
if (URI_PATH_MATCHER.match(excludeUri, uri)) {
return true;
}
}
return false;
}
}
三、测试
1、写一个有返回值得方法 2、写一个没有返回值,即void类型,但是有response输出
response.getWriter().write();
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
/*@Controller
@ResponseBody*/
@RestController //上面两个注释的注解等于此注解
@RequestMapping("/test")
public class HelloController {
private static final Logger logger = LoggerFactory.getLogger(HelloController.class);
@GetMapping("/hello")
public String sayHello(@RequestParam String testParam){
return "hello world";
}
/**
* 模拟捕获返回类型为void的,response.getWriter().write();中的参数(储藏)
* @param testParam
* @param response
*/
@GetMapping("/sayVoid")
public void sayVoid(@RequestParam String testParam, HttpServletResponse response){
response.setCharacterEncoding("utf-8");
response.setContentType("application/json;charset=utf-8");
try{
response.getWriter().write(testParam);
}catch (IOException e){
logger.error("HelloController.sayVoid error", e);
}
}
}
分别调用
1、有返回值类型
查看控制台输出
2、无返回类型void
查看控制台输出