自定义注解
自定义注解首先要知道元注解,也就是注解的注解,是jdk内置的。元注解有四种:
- @Retention 注解保留策略
@Retention(RetentionPolicy.SOURCE) 仅存在于源码中
@Retention(RetentionPolicy.CLASS) 存在于class字节码中,但运行时无法获取
@Retention(RetentionPolicy.RUNTIME) 存在于class字节码中,运行时可以通过反射获取
- @Target 注解的作用范围
@Target(ElementType.TYPE) 接口、类等
@Target(ElementType.FIELD) 字段
@Target(ElementType.METHOD) 方法
@Target(ElementType.PARAMETER) 方法参数
@Target(ElementType.CONSTRUCTOR) 构造函数
@Target(ElementType.LOCAL_VARIABLE) 局部变量
@Target(ElementType.ANNOTATION_TYPE) 注解
@Target(ElementType.PACKAGE) 包 - @Document 注解包含在javadoc中
- @Inherited 注解可以被继承
实现方法
引入jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
自定义注解
import java.lang.annotation.*;
/**
* @author 晓风残月Lx
* @date 2023/5/28 11:32
* 前台自定义操作日志注解
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD}) //注解放置的目标位置即方法级别
@Documented
public @interface BusinessLogger {
String value() default ""; // 操作模块
String type() default ""; // 操作类型
String desc() default ""; // 操作说明
boolean save() default true; // 是否将当前日志记录到数据库中
}
编写切面类
切面类根据功能需求不同,进行修改,这里是将操作存入日志。
import cn.dev33.satoken.stp.StpUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.xfcy.blog.annotation.BusinessLogger;
import com.xfcy.blog.common.R;
import com.xfcy.blog.common.constant.Constant;
import com.xfcy.blog.entity.ExceptionLog;
import com.xfcy.blog.entity.UserLog;
import com.xfcy.blog.mapper.ExceptionLogMapper;
import com.xfcy.blog.mapper.UserLogMapper;
import com.xfcy.blog.utils.AspectUtils;
import com.xfcy.blog.utils.IpUtils;
import com.xfcy.blog.vo.SystemUserVO;
import eu.bitwalker.useragentutils.UserAgent;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.AfterThrowing;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
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.time.LocalDateTime;
import java.util.HashMap;
import java.util.Objects;
/**
* @author 晓风残月Lx
* @date 2023/5/28 11:38
*/
@Aspect
@Component
@RequiredArgsConstructor
public class BusinessLoggerAspect {
private static final Logger logger = LoggerFactory.getLogger(BusinessLoggerAspect.class);
private final UserLogMapper userLogMapper;
private final ExceptionLogMapper exceptionLogMapper;
/**
* 设置操作日志切入点 在注解的位置切入代码
*
* @param businessLogger
*/
// execution(public * com.xfcy.blog.controller.*.*(..)) 表示所有controller
@Pointcut(value = "@annotation(businessLogger)")
public void pointAspect(BusinessLogger businessLogger) {
}
/**
* Around注解可以用来在调用一个具体方法前和调用后来完成一些具体的任务。
*
* @param joinPoint 方法的执行点
* @param businessLogger 方法返回值
* @return
*/
@Around(value = "pointAspect(businessLogger)")
public Object doAround(ProceedingJoinPoint joinPoint, BusinessLogger businessLogger) throws Throwable {
System.out.println("--------Around方法开始执行");
//先执行业务
Object result = joinPoint.proceed();
try {
// 日志收集
handle(joinPoint, (R) result);
} catch (Exception e) {
logger.error("日志记录出错!", e);
}
return result;
}
@AfterThrowing(value = "pointAspect(businessLogger)", throwing = "e")
public void afterThrowing(JoinPoint joinPoint, BusinessLogger businessLogger, Throwable e) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从requestAttributes中获取HttpServletRequest的信息
assert requestAttributes != null;
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
String ip = IpUtils.getIp(request);
String paramsJson = getParamsJson((ProceedingJoinPoint) joinPoint);
String operationName = AspectUtils.INSTANCE.parseParams(joinPoint.getArgs(), businessLogger.value());
ExceptionLog exceptionLog = ExceptionLog.builder().ip(ip)
.ipSource(IpUtils.getIp2region(ip)).params(paramsJson)
.method(joinPoint.getSignature().getName())
.username("游客")
.operation(operationName).exceptionJson(JSON.toJSONString(e))
.exceptionMessage(e.getMessage()).createTime(LocalDateTime.now()).build();
exceptionLogMapper.insert(exceptionLog);
}
/**
* 记录操作日志
*
* @param joinPoint 方法的执行点
* @param result 方法返回值
*/
private void handle(ProceedingJoinPoint joinPoint, R result) {
// 获取RequestAttributes
RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
// 从requestAttributes中获取HttpServletRequest的信息
assert requestAttributes != null;
HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
// 从切面织入点处通过反射机制获取织入点处的方法
MethodSignature signature = (MethodSignature) joinPoint.getSignature();
// 获取切入点所在的方法
Method method = signature.getMethod();
// 获取操作
BusinessLogger annotation = method.getAnnotation(BusinessLogger.class);
if (!annotation.save()) {
return;
}
assert request != null;
String ip = IpUtils.getIp(request);
UserAgent userAgent = UserAgent.parseUserAgentString(request.getHeader("user-agent"));
String clientType = userAgent.getOperatingSystem().getDeviceType().toString();
String os = userAgent.getOperatingSystem().getName();
String browser = userAgent.getBrowser().toString();
UserLog userLog = UserLog.builder().model(annotation.value()).type(annotation.type())
.description(annotation.desc()).ip(ip).address(IpUtils.getIp2region(ip))
.clientType(clientType).accessOs(os).browser(browser).result(result.getMsg()).
createTime(LocalDateTime.now()).build();
if (StpUtil.isLogin()) {
userLog.setUserId(StpUtil.getLoginIdAsLong());
}
userLogMapper.insert(userLog);
}
private String getParamsJson(ProceedingJoinPoint joinPoint) {
// 参数值
Object[] args = joinPoint.getArgs();
Signature signature = joinPoint.getSignature();
MethodSignature methodSignature = (MethodSignature) signature;
String[] parameterNames = methodSignature.getParameterNames();
// 通过map封装参数和参数值
HashMap<String, Object> paramMap = new HashMap<>();
for (int i = 0; i < parameterNames.length; i++) {
paramMap.put(parameterNames[i], args[i]);
}
boolean isContains = paramMap.containsKey("request");
if (isContains) paramMap.remove("request");
return JSONObject.toJSONString(paramMap);
}
}
测试类
/**
* 前台文章管理器
*
* @author 晓风残月Lx
* @date 2023/4/9 22:07
*/
@RestController
@RequestMapping("/web/article")
@Api(tags = "前台文章管理")
public class WebArticleController {
@Resource
private ArticleService articleService;
@BusinessLogger(value = "首页-用户访问首页",type = "查询",desc = "查询所有文章")
@GetMapping("/list")
@ApiOperation(value = "文章列表", httpMethod = "GET", response = R.class, notes = "文章列表")
public R list(@RequestParam Integer pageNo, Integer pageSize) {
return articleService.listWebArticle(pageNo, pageSize);
}
}
通过以上方式,就可以在需要记录日志的方法上使用注解,在注解处理器中通过切面编程的方式记录日志,实现了自定义注解记录操作日志的功能。