一、引入配置文件
<!--AOP-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- guava工具包 -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>30.0-jre</version>
</dependency>
二、编写对应的枚举类
package com.baizhi.enus;
/**
* @author xxs
* @date 2022年08月10日 16:44
*/
public interface OpType {
/**
* 新增
*/
public static final int ADD = 1;
/**
* 修改
*/
public static final int UPDATE = 2;
/**
* 删除
*/
public static final int DELETE = 3;
/**
* 查询-分页
*/
public static final int QUERY_PAGE = 4;
/**
* 其他
*/
public static final int OTHER = 5;
/**
* 登录
*/
public static final int LOGIN = 6;
/**
* 登出
*/
public static final int LOGOUT = 7;
/**
* 导出
*/
public static final int EXPORT = 8;
/**
* 导入
*/
public static final int IMPORT = 9;
/**
* 保存
*/
public static final int SAVE = 10;
/**
* 发送邮件
*/
public static final int SEND_EMAIL = 11;
/**
* 发送短信
*/
public static final int SMS = 12;
/**
* 查询-不分页
*/
public static final int QUERY_NO_PAGE = 13;
/**
* 查询明细
*/
public static final int QUERY_DETAIL = 14;
/**
* 审核
*/
public static final int AUDIT = 15;
/**
* 反审核
*/
public static final int UN_AUDIT = 16;
/**
* 下载
*/
public static final int DOWNLOAD = 17;
/**
* 上传
*/
public static final int UPLOAD = 18;
/**
* 校验
*/
public static final int CHECK = 19;
/**
* 安装
*/
public static final int INSTALL = 20;
/**
* 卸载
*/
public static final int UN_INSTALL = 21;
/**
* 启动
*/
public static final int START = 22;
/**
* 停止
*/
public static final int STOP = 23;
/**
* 重启
*/
public static final int RESTART = 24;
/**
* 暂停
*/
public static final int PAUSE = 25;
/**
* 恢复
*/
public static final int RESUME = 26;
/**
* 刷新
*/
public static final int REFRESH = 27;
/**
* 生成
*/
public static final int GENERATE = 28;
/**
* 统计
*/
public static final int STATISTICS = 29;
/**
* 同步
*/
public static final int SYNC = 30;
/**
* 定时调度
*/
public static final int SCHEDULE = 31;
}
package com.baizhi.enus;
import java.util.ArrayList;
import java.util.List;
/**
* @author hz
*/
public enum OpTypeEnum {
/*
1. 增加
2. 删除
3. 修改
4. 查询
5. 其他
*/
ADD(OpType.ADD, "新增"),
UPDATE(OpType.UPDATE, "修改"),
DELETE(OpType.DELETE, "删除"),
QUERY_PAGE(OpType.QUERY_PAGE, "查询-分页", true),
OTHER(OpType.OTHER, "其他"),
LOGIN(OpType.LOGIN, "登录"),
LOGOUT(OpType.LOGOUT, "登出"),
EXPORT(OpType.EXPORT, "导出"),
IMPORT(OpType.IMPORT, "导入"),
SAVE(OpType.SAVE, "保存"),
SEND_EMAIL(OpType.SEND_EMAIL, "发送邮件"),
SMS(OpType.SMS, "发送短信"),
QUERY_NO_PAGE(OpType.QUERY_NO_PAGE, "查询-不分页", true),
QUERY_DETAIL(OpType.QUERY_DETAIL, "查询明细", true),
AUDIT(OpType.AUDIT, "审核"),
UN_AUDIT(OpType.UN_AUDIT, "反审核"),
DOWNLOAD(OpType.DOWNLOAD, "下载"),
UPLOAD(OpType.UPLOAD, "上传"),
CHECK(OpType.CHECK, "校验"),
INSTALL(OpType.INSTALL, "安装"),
UN_INSTALL(OpType.UN_INSTALL, "卸载"),
START(OpType.START, "启动"),
STOP(OpType.STOP, "停止"),
RESTART(OpType.RESTART, "重启"),
PAUSE(OpType.PAUSE, "暂停"),
RESUME(OpType.RESUME, "恢复"),
REFRESH(OpType.REFRESH, "刷新"),
GENERATE(OpType.GENERATE, "生成"),
STATISTICS(OpType.STATISTICS, "统计"),
SYNC(OpType.SYNC, "同步"),
SCHEDULE(OpType.SCHEDULE, "定时调度"),
last(0, "最后一个");
final int key;
final String desc;
//是否忽略返回结果
final boolean ignoreResult;
OpTypeEnum(int key, String desc) {
this.key = key;
this.desc = desc;
this.ignoreResult = false;
}
OpTypeEnum(int key, String desc, boolean ignoreResult) {
this.key = key;
this.desc = desc;
this.ignoreResult = ignoreResult;
}
/**
* 根据key获取desc
*/
public static String getDesc(Integer key) {
for (OpTypeEnum opTypeEnum : OpTypeEnum.values()) {
if (opTypeEnum.getKey() == key) {
return opTypeEnum.getDesc();
}
}
return "";
}
/**
* 获取所有忽略返回结果的Key集合
*/
public static List<Integer> getIgnoreResultKeys() {
List<Integer> keys = new ArrayList<>();
for (OpTypeEnum opTypeEnum : OpTypeEnum.values()) {
if (opTypeEnum.isIgnoreResult()) {
keys.add(opTypeEnum.getKey());
}
}
return keys;
}
public int getKey() {
return key;
}
public String getDesc() {
return desc;
}
public boolean isIgnoreResult() {
return ignoreResult;
}
}
三、这里我使用的是注解的形式进行的切面
package com.baizhi.annotation;
import com.baizhi.enus.OpType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER,ElementType.METHOD})
public @interface OptLog {
/**
* 业务类型,如新增、删除、修改
* 默认 其他
* @return
*/
int opType() default OpType.OTHER;
/**
* 业务对象名称,如订单、库存、价格
*
* @return
*/
String opItem();
/**
* 业务对象编号表达式,描述了如何获取订单号的表达式
*
* @return
*/
String opItemIdExpression();
/**
*是否使用注解切面默认是使用的注解切面
**/
boolean isSaveRequestData() default true;
}
四、编写AOP
package com.baizhi.aop;
import com.baizhi.annotation.OptLog;
import com.baizhi.enus.OpTypeEnum;
import io.micrometer.core.instrument.util.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.expression.EvaluationContext;
import org.springframework.expression.Expression;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import com.google.common.base.CaseFormat;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Optional;
@Aspect
@Component
public class LogMethodAspect {
private static final Logger log = LoggerFactory.getLogger(LogMethodAspect.class);
/**
* 指定切面类的位置*/
@Pointcut("@annotation(com.baizhi.annotation.OptLog)")
public void myAnnotationAspect(){}
@Autowired
HttpServletRequest request;
@Around("myAnnotationAspect()")
public Object log(ProceedingJoinPoint pjp) throws Exception {
Method method = ((MethodSignature)pjp.getSignature()).getMethod();
OptLog opLog = method.getAnnotation(OptLog.class);
Object response = null;
try {
// 目标方法执行
response = pjp.proceed();
} catch (Throwable throwable) {
throw new Exception(throwable);
}
if (StringUtils.isNotEmpty(opLog.opItemIdExpression()) && opLog.isSaveRequestData()) {
SpelExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parseExpression(opLog.opItemIdExpression());
EvaluationContext context = new StandardEvaluationContext();
// 获取参数值
Object[] args = pjp.getArgs();
// 获取运行时参数的名称
LocalVariableTableParameterNameDiscoverer discoverer
= new LocalVariableTableParameterNameDiscoverer();
String[] parameterNames = discoverer.getParameterNames(method);
// 将参数绑定到context中
if (parameterNames != null) {
for (int i = 0; i < parameterNames.length; i++) {
context.setVariable(parameterNames[i], args[i]);
}
}
// 将方法的resp当做变量放到context中,变量名称为该类名转化为小写字母开头的驼峰形式
if (response != null) {
context.setVariable(
CaseFormat.UPPER_CAMEL.to(CaseFormat.LOWER_CAMEL, response.getClass().getSimpleName()),
response);
}
// 解析表达式,获取结果
String itemId = String.valueOf(expression.getValue(context));
//TODO 缺少登录用户id
// 执行日志记录
handle(opLog.opType(), opLog.opItem(), itemId);
}
return response;
}
//这里模拟的是数据库的操作插入对应的log表执行插入操作。我这里的插入人还没有如果大写写的话建议在新增一个插入人员的id方便我们后期对log表查询的时候知道插入的人的,人员信息以便我们项目对日志有着很好的管理
private void handle(Integer opType, String opItem, String opItemId) {
//
String orElse= Optional.ofNullable(OpTypeEnum.getDesc(opType)).orElse("");
// 通过日志打印输出
log.info("opType = " + opItem +",opItem = " +orElse + ",opItemId = " +opItemId);
}
}
五、编写controller测试
/**
* 编辑数据
*
* @param log 实体
* @return 编辑结果
*/
//opItemIdExpression = "#log.id" 这里接收的值是根据前台传入的值确定的 相当于记录的管理员或者其//他人员操作的唯一id方便我们后期对日志可以进行有效的排查。
//如果为get请求?或者是/ 接收的后台参数直接拿 opItemIdExpression = "#id" 进行接收
@OptLog(opType = OpType.UPDATE, opItem = "log查询", opItemIdExpression = "#log.id", isSaveRequestData = true)
@PutMapping
public ResponseEntity<Log> edit(@RequestBody Log log) {
return ResponseEntity.ok(this.logService.update(log));
}