1.需求:
由于需要开放系统中的接口,给第三方调用所以我们需要有一个可以记录第三方调用的通用方案。此时AOP就给我们提供了一个很好的解决方案,既不用修改原有的 filter(过滤器)逻辑,又可以通用。
AOP是什么
AOP(面像切面编程)将系统中重复使用到的代码抽离出来,执行时使用动态代理的方式,在不改变原有的业务逻辑上,对我们的方法进行增强。是Spring中的一个重要内容。采用代理机制,Spring底层实现AOP 基于JDK的动态代理 (对实现了接口的类进行代理) 和 Cglib的动态代理两种方式实现(对无实现接口的类进行代理)。
2.如何实现
2.1:定义一个注解@ApiLogTest
2.2: 创建一个切面 ApiLogAspect
2.3:定义我们的切点。@Pointcut(value ="XXX")
2.4:定义前置增强方法 @Before("XXX")
3.具体实现
创建自定义注解 ApiLotTest
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
//接口请求日志记录
public @interface ApiLogTest {
}
创建一个切面 ApiLotAspect提供给容器读取
此处需要注意 :
使用 request.getParameterMap() 获取参数时,POST 请求发送端content Type必须设置为application/x-www-form-urlencoded;否则会接收不到。
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.fastjson.JSON;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
@Order(1) //设置 Spring IOC容器中Bean的执行顺序的优先级 数字越小优先级越高
@Aspect //定义一个切面 提供给容器读取
@Component //表示为Spring Bean
@Slf4j //日志
public class ApiLogAspect {
@Autowired
private SystemParameterService systemParameterService;
/**
* 添加一个切入点 ,以ApiLog注解匹配
*/
@Pointcut(value = "@annotation(com.XXX.annotation.ApiLogTest)")
public void apiLog(){}
/**
* 前置增强,在执行方法前做处理
* @param joinPoint
* @throws Throwable
*/
@Before("apiLog()")
public void doBefore(JoinPoint joinPoint) throws Throwable{
log.info("");
log.info("-----------接口请求日志开始-------------");
//获取请求信息
// 获取RequestAttributes
RequestAttributes requestAttributes =RequestContextHolder.getRequestAttributes();
// 从获取RequestAttributes中获取HttpServletRequest的信息
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
try {
if (ObjectUtil.isEmpty(request)) {
// 请求来源IP
String clientIp = request.getRemoteAddr();
log.info("请求来源IP:{}", clientIp);
// 接口请求地址
String requestURL = request.getRequestURL().toString();
log.info("接口请求地址:{}", requestURL);
// 请求方法
String requestMethod = request.getMethod();
log.info("请求方法:{}", requestMethod);
// 接口路径
String path = request.getServletPath();
log.info("接口路径:{}", path);
}
// 获取请求的参数
Map<String, String> rtnMap = converMap(request.getParameterMap());
// 将参数所在的数组转换成json
String params = JSON.toJSONString(rtnMap);
log.info("接口请求参数:{}", JSONUtil.toJsonStr(params));
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取请求的类名
String className = joinPoint.getTarget().getClass().getName();
// 接口方法参数名称
String[] parameterNames = signature.getParameterNames();
log.info("接口方法参数名称:{}", JSONUtil.toJsonStr(parameterNames));
// 获取接口上使用的注解 一般时一些接口的描述 例如Swagger中的一些注解
ApiOperation apiLog =signature.getMethod().getAnnotation(ApiOperation.class);
if (apiLog != null) {
// 接口概述
String summary = apiLog.value();
log.info("接口描述:{}", summary);
}
}catch (Exception e){
e.printStackTrace();
}finally {
log.info("-----------接口请求日志结束-------------");
log.info("");
}
}
public Map<String, String> converMap(Map<String, String[]> paramMap) {
Map<String, String> rtnMap = new HashMap<String, String>();
for (String key : paramMap.keySet()) {
rtnMap.put(key, paramMap.get(key)[0]);
}
return rtnMap;
}
}
4.测试:
在接口上使用我们自定义注解@ApiLogTest
@RestController
@RequestMapping("/admin/wechatMessage")
public class testController{
@ApiLogTest
@GetMapping("/LxdsendTextMesg")
public Result<Object> LxdsendTextMesg(@RequestParam("str") String s){
System.out.println("----------------------测试-----------"+s);
return ResultUtil.ok();
}
}