SpringBoot,AOP切面实现日志记录,自定义注解,注解属性动态传参
项目需求:
需要记录用户的操作具体的方法,记录当前具体操作的是哪一条数据信息,作为参数也一并保存起来。
如:更新数据,IP,电脑号,调用方法,类型为修改,及这条更新的数据具体信息。
用SpringAOP + Annotation来实现
- 自定义aop注解类,通用日志文件
import java.lang.annotation.*;
/******************************
* 用途说明: 通用日志文件
* 作者姓名: wangqiubo
* 创建时间: 2021/03/24
******************************/
@Target(ElementType.METHOD) //注解放置的目标位置,METHOD是可注解在方法级别上
@Retention(RetentionPolicy.RUNTIME) //注解在哪个阶段执行
@Documented //生成文档
public @interface MyLog {
/**
* 业务类型,例如:"新增"
*/
String type() default "";
/**
* 业务的操作,例如:"用户信息: 新增XXX用户"
*/
String name() default "";
}
- controller方法上,加入我们自定义的MyLog注解
/**
* 分页查询
*/
@PostMapping("/{pageNum}/{pageSize}")
@ApiOperation(value = "operateLog-ofPage", notes = "分页查询操作日志列表")
/**
* 注意:就是下面这一行
* @MyLog 是自定义注解, type,name 是我们在自定义注解里面的
* type本文是引用的常量,name是前端参过来的实体类
* 当然type,name也可以写死 如:
* @MyLog(type = "查询", name = "查询所有数据")
*/
@MyLog(type = ConstantList.SELECT, name = "#{sysOperateLogDto.operateType}")
//-----------------------上面这一行--------------------------------------
public CallResult ofPage(@ApiParam(value = "页号", required = true) @PathVariable("pageNum") int pageNum,
@ApiParam(value = "页大小", required = true) @PathVariable("pageSize") int pageSize,
@ApiParam(value = "条件", required = true) @RequestBody SysOperateLogDto sysOperateLogDto) {
log.info("分页查询操作日志参数:pageNum: "+ pageNum + " pageSize: " + pageSize + " sysOperateLogDto: " +sysOperateLogDto);
QueryWrapper<SysOperateLog> queryWrapper = new QueryWrapper();
queryWrapper.eq(!StringUtils.isEmpty(sysOperateLogDto.getOperatorId()),"operator_id",sysOperateLogDto.getOperatorId())
.eq(!StringUtils.isEmpty(sysOperateLogDto.getOperateType()),"operate_type",sysOperateLogDto.getOperateType())
.ge(!StringUtils.isEmpty(sysOperateLogDto.getStartDate()),"operate_time",sysOperateLogDto.getStartDate())
.le(!StringUtils.isEmpty(sysOperateLogDto.getEndDate()),"operate_time",sysOperateLogDto.getEndDate());
queryWrapper.orderByDesc("operate_time");
IPage<SysOperateLog> page = new Page<>();
page.setCurrent(pageNum);
page.setSize(pageSize);
page = operateLogService.page(page, queryWrapper);
log.info("返回操作日志列表:page: "+ page.getRecords().toString());
return CallResult.success("操作日志列表", page);
}
- 定义springAOP切面,来获取动态参数,及调用方法的信息
import com.cgnpc.psc.manage.controller.SysOperateLogController;
import com.cgnpc.psc.manage.domain.SysOperateLog;
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.beans.factory.annotation.Autowired;
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.net.InetAddress;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
/******************************
* 用途说明: 日志AOP切入
* 作者姓名: wangqiubo
* 创建时间: 2021/03/24
******************************/
@Aspect
@Component
public class LogAspect {
@Autowired
SysOperateLogController sysOperateLogController;
//定义切点 定义切入点MyLog(扫描切入点路径)
@Pointcut(value ="@annotation(com.cgnpc.psc.manage.config.sysPperateLog.MyLog)")
public void logPointCut() {
}
//定义切面 日志切面
@Around("logPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); //获取RequestAttributes
HttpServletRequest request = (HttpServletRequest) requestAttributes
.resolveReference(RequestAttributes.REFERENCE_REQUEST); //从RequestAttributes中获取HttpServletRequest的信息
System.out.println("==============================begin==============================");
Object result = point.proceed(); //去执行被访问的切面方法
MethodSignature signature = (MethodSignature) point.getSignature(); //从切面织入点处通过反射机制获取织入点处的方法
Method method = signature.getMethod(); //获取切入点所在的方法
// String methodName = method.getName(); //获取方法名
// System.out.println("Method Name:" + methodName); //输出方法名
// Map<String, String> rtnMap = converMap(request.getParameterMap()); //请求的参数
// String className = point.getTarget().getClass().getName(); //获取请求的类名
/**
* 获取动态参数值
*/
MyLog log = method.getAnnotation(MyLog.class);
System.out.println("type:" + log.name() + "name:" + log.name()); //输出注解里面的值
Object type = AnnotationResolver.newInstance().resolver(point, log.type());
Object name = AnnotationResolver.newInstance().resolver(point, log.name());
InetAddress addr = InetAddress.getLocalHost(); //获取主机信息
System.out.println("Local HostAddress: "+addr.getHostAddress()); //本地主机ip地址
String hostname = addr.getHostName(); //本地主机名称
System.out.println("Local host name: "+hostname);
System.out.println("++++++++++++++++++++++++++++++end++++++++++++++++++++++++++++++");
//定义操作日志实体类
SysOperateLog sysOperateLog = new SysOperateLog();
sysOperateLog.setOperateType(type.toString()); //操作类型
sysOperateLog.setMessage("操作信息:" + name.toString()); //操作信息
sysOperateLog.setOperateTime(new Date()); //操作时间
sysOperateLog.setHostName(addr.getHostAddress()); //主机名(ip)
//调用操作日志新增方法
sysOperateLogController.addOperateLog(sysOperateLog);
return result;
}
/**
* 转换request 请求参数
* @param paramMap request获取的参数数组
*/
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;
}
}
- AnnotationResolver 是解析注解类语法的解析器,可以把注解类中的语法直接解析#{方法变量名}
import java.lang.reflect.Method;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
/**
* 该类的作用可以把方法上的参数绑定到注解的变量中,注解的语法#{变量名}
* 能解析类似 "#{sysOperateLogDto.operateType}"
*/
public class AnnotationResolver {
private static AnnotationResolver resolver ;
public static AnnotationResolver newInstance(){
if (resolver == null) {
return resolver = new AnnotationResolver();
}else{
return resolver;
}
}
/**
* 解析注解上的值
* @param joinPoint
* @param str 需要解析的字符串
* @return
*/
public Object resolver(JoinPoint joinPoint, String str) {
if (str == null) return null ;
Object value = null;
if (str.matches("#\\{\\D*\\}")) {// 如果name匹配上了#{},则把内容当作变量
String newStr = str.replaceAll("#\\{", "").replaceAll("\\}", "");
if (newStr.contains(".")) { // 复杂类型
try {
value = complexResolver(joinPoint, newStr);
} catch (Exception e) {
e.printStackTrace();
}
} else {
value = simpleResolver(joinPoint, newStr);
}
} else { //非变量
value = str;
}
return value;
}
private Object complexResolver(JoinPoint joinPoint, String str) throws Exception {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] names = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
String[] strs = str.split("\\.");
for (int i = 0; i < names.length; i++) {
if (strs[0].equals(names[i])) {
Object obj = args[i];
Method dmethod = obj.getClass().getDeclaredMethod(getMethodName(strs[1]), null);
Object value = dmethod.invoke(args[i]);
return getValue(value, 1, strs);
}
}
return null;
}
private Object getValue(Object obj, int index, String[] strs) {
try {
if (obj != null && index < strs.length - 1) {
Method method = obj.getClass().getDeclaredMethod(getMethodName(strs[index + 1]), null);
obj = method.invoke(obj);
getValue(obj, index + 1, strs);
}
return obj;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private String getMethodName(String name) {
return "get" + name.replaceFirst(name.substring(0, 1), name.substring(0, 1).toUpperCase());
}
private Object simpleResolver(JoinPoint joinPoint, String str) {
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
String[] names = methodSignature.getParameterNames();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < names.length; i++) {
if (str.equals(names[i])) {
return args[i];
}
}
return null;
}
}
- ConstantList 常量列表,供AOP切面使用,操作日志调用
/**
* 常量列表,供AOP切面使用,操作日志调用
*/
public class ConstantList {
public static final String SELECT = "查询" ;
public static final String ADD = "新增" ;
public static final String UPDATE = "修改" ;
public static final String DELETE = "删除" ;
public static final String BATCH_DELETE = "批量删除" ;
}
到这里就全结束了,希望对你有所帮助!!!