减少代码中接口的入参出参打印,减少工作量,保持打印风格一致性。
[2020-07-23 14:50:09 INFO http-nio-9001-exec-2] t.g.gmayaserviceadminimpl.system.aspect.LogAspect - top.gmaya.gmayaserviceadminimpl.system.controller.UserController.add()【新增登录信息】:===================
[2020-07-23 14:50:09 INFO http-nio-9001-exec-2] t.g.gmayaserviceadminimpl.system.aspect.LogAspect - top.gmaya.gmayaserviceadminimpl.system.controller.UserController.add()【方法请求参数为】:{"data":{"id":1,"name":"1212"},"token":"11111111"}
[2020-07-23 14:50:09 INFO http-nio-9001-exec-2] t.g.gmayaserviceadminimpl.system.aspect.LogAspect - top.gmaya.gmayaserviceadminimpl.system.controller.UserController.add()【方法返回结果为】:{"msg":"success","code":0,"data":1}
[2020-07-23 14:50:09 INFO http-nio-9001-exec-2] t.g.gmayaserviceadminimpl.system.aspect.LogAspect - top.gmaya.gmayaserviceadminimpl.system.controller.UserController.add()【方法执行时长为】:208 ms
[2020-07-23 14:50:09 INFO http-nio-9001-exec-2] t.g.gmayaserviceadminimpl.system.aspect.LogAspect - {"method":"POST","createTime":1595487009526,"ip":"192.168.21.1","methodName":"add","className":"top.gmaya.gmayaserviceadminimpl.system.controller.UserController","createUser":"GMaya","time":208,"operation":"新增登录信息","url":"http://localhost:9001/user/add"}
[2020-07-23 14:50:09 INFO http-nio-9001-exec-2] t.g.gmayaserviceadminimpl.system.aspect.LogAspect - top.gmaya.gmayaserviceadminimpl.system.controller.UserController.add()【保存数据库成功!】:===================
思路:
一个注解类, 一个切面类。使用环绕通知,将入参出参打印出来,可以根据实际情况,有些接口只需要打印即可,有些需要打印并保存到数据库。
注解类GmLog
package top.gmaya.gmayaserviceadminimpl.system.annotation;
import java.lang.annotation.*;
/**
* 自定义日志注解
* @author GMaya
* @dateTime 2020/7/23 10:29
* 1.运行时 使用使用注解
* 2.注解作用于方法上
* 3.注解是否将包含在 JavaDoc 中
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.METHOD })
@Documented
public @interface GmLog {
// 打印日志描述信息
String value() default "";
// TODO 是否保存到数据库
boolean isSave() default false;
}
切面类LogAspect
里面有两种方式,一个是拦截添加注解的方法(适合新建项目,写接口的时候加上注解),一个是指定的包名下面所有的接口(适合现有项目,不必要改变其余代码)
package top.gmaya.gmayaserviceadminimpl.system.aspect;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import top.gmaya.gmayaserviceadminimpl.system.annotation.GmLog;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.util.Date;
/**
* 用于记录注解上接口的入参出参,统一规范。
*/
@Aspect
@Component
@Slf4j
public class LogAspect {
/**
* 方法一: 不需要自定义注解, 直接拦截所有controller的请求。全部打印
* 定义切入点表达式
* 第一个*号:表示返回类型, *号表示所有的类型。
* 包名:表示需要拦截的包名,后面的两个句点表示当前包和当前包的所有子包.
* 第二个*号:表示类名,*号表示所有的类。
* *(..):最后这个星号表示方法名,*号表示所有的方法,后面括弧里面表示方法的参数,两个句点表示任何参数
*/
@Pointcut("execution(public * top.gmaya.gmayaserviceadminimpl.system.controller..*.*(..))")
public void privilege() {
}
/**
* 方法二:拦截该注解标识的方法
*/
@Pointcut("@annotation(top.gmaya.gmayaserviceadminimpl.system.annotation.GmLog)")
public void logPointCut() {
}
/**
* 环绕通知
* @param pjd
* @return
* @throws Throwable
*/
// @Around("privilege()") // 第一种方式
@Around("logPointCut()") // 第二种方式
public Object arount(ProceedingJoinPoint pjd) throws Throwable {
long startTime = System.currentTimeMillis();
// 类名
String className = pjd.getTarget().getClass().getName();
// 获取执行的方法名称
String methodName = pjd.getSignature().getName();
// 1. 如果是使用的第二种方式,则判断该方法是否使用了改注解
// 2. 如果是使用的第一种方式,直接注释即可。
GmLog gmLog = this.getAnnotationLog(pjd);
if (gmLog != null) {
String value = gmLog.value();
log.info("{}.{}()【{}】:===================", className, methodName, value);
}
Object[] args = pjd.getArgs();
try {
String params = JSON.toJSONString(args[0]);
//打印请求参数参数
log.info("{}.{}()【方法请求参数为】:{}", className, methodName, params);
} catch (Exception e) {
log.info("{}.{}()【方法请求参数打印失败】:{}", className, methodName, e);
}
// 执行目标方法
Object result = pjd.proceed();
// 打印返回结果
try {
String s = JSON.toJSONString(result);
log.info("{}.{}()【方法返回结果为】:{}", className, methodName, s);
} catch (Exception e) {
log.info("{}.{}()【方法返回结果打印失败】:{}", className, methodName, e);
}
// 获取执行完的时间
long time = System.currentTimeMillis() - startTime;
log.info("{}.{}()【方法执行时长为】:{}{}", className, methodName, time, " ms");
// 如果使用第一种方式,把这里注释掉
// TODO 这里可以考虑新加一个异步方法,保存信息到数据库,入参,出参,请求人,请求时间,ip信息等,如果有异常,还有异常信息。
if (gmLog != null) {
boolean save = gmLog.isSave();
if (save) {
String val = gmLog.value();
// 调用异步保存数据库方法
int i = this.saveLog(pjd, time, val);
if (i > 0) {
// 判断插入条数,大于0,保存成功。
log.info("{}.{}()【{}】:===================", className, methodName, "保存数据库成功!");
}
}
}
return result;
}
/**
* 是否存在注解,如果存在就获取
* @param joinPoint
* @return
*/
private GmLog getAnnotationLog(JoinPoint joinPoint) {
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
Method method = methodSignature.getMethod();
if (method != null) {
return method.getAnnotation(GmLog.class);
}
return null;
}
/**
* 保存到数据库
* @param joinPoint
* @param time 方法执行时间 单位ms
* @param val 方法请求描述
* @return
*/
private int saveLog(JoinPoint joinPoint, long time, String val) {
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder
.getRequestAttributes();
HttpServletRequest request = requestAttributes.getRequest();
JSONObject jsonObject = new JSONObject();
// ip地址
String hostAddress = "";
try {
hostAddress = InetAddress.getLocalHost().getHostAddress();
} catch (Exception e) {
log.error("获取ip失败");
}
// redis.getUserId(); 结合实际情况 获取当前登录人信息
// 类名
String className = joinPoint.getTarget().getClass().getName();
// 获取执行的方法名称
String methodName = joinPoint.getSignature().getName();
String url = request.getRequestURL().toString();
String method = request.getMethod();
jsonObject.put("ip", hostAddress);
jsonObject.put("className", className);
jsonObject.put("methodName", methodName);
jsonObject.put("url", url);
// 执行时间
jsonObject.put("time", time);
jsonObject.put("createTime", new Date());
jsonObject.put("createUser", "GMaya");
// 操作描述
jsonObject.put("operation", val);
jsonObject.put("method", method);
String s = jsonObject.toJSONString();
// 调用日志service的add方法即可!
log.info(s);
return 1;
}
}
controller
层接口展示:
@RequestMapping("add")
@GmLog(value = "新增登录信息" , isSave = true)
public R add(@RequestBody F<UserEntity> f) {
// 登录用户信息
UserEntity user = this.getUser(f.getToken());
return R.data(userService.add(f,user));
}
只需要添加注解即可,以及是否保存到数据库,默认不保存。
@GmLog(value = "新增登录信息" , isSave = true)
写完,测试一下,完美!