背景:
系统的操作日志、审计日志。在日常的管理还是维护中都会起到很大的作用。
解决办法:
可以在需要的方法中对日志进行保存操作,但是对业务代码入侵性大。
或者使用切面针对控制类进行处理,但是灵活度不高。
==》因此决定使用自定义注解 + 切面来针对方法进行日志记录。
目前日志主要记录的有三方面:
- 请求的入参,出参
- 关于业务上的操作
- 异常日常日志的打印
一、自定义注解
创建自定义注解 @AuditLog:
java
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.METHOD)
public @interface AuditLog {
// 1.操作描述
String action() default "";
// 2.操作类型(增删改查)
OperateEnum type() default OperateEnum.MODIFY;
// 3.是否记录参数
boolean isRecord() default true;
}
操作类型枚举类:新增、删除、修改、查询。
java
public enum OperateEnum {
ADD,
DELETE,
MODIFY,
SAVE_OR_MODIFY,
SELECT;
}
二、定义一个切面
创建一个切面类AuditAspect ,用于捕获带有**@AuditLog**注解的方法调用并记录日志:
java
import cn.hutool.json.JSONUtil;
import lombok.extern.slf4j.Slf4j;
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.stereotype.Component;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
/**
* 日志记录切面
*/
@Slf4j
@Aspect
@Component
public class AuditAspect {
@Pointcut(value = "@annotation(com.bocloud.devops.eaas.api.log.AuditLog)")
public void logMethodPointCut() {
}
@Around("logMethodPointCut()")
public Object recordSysLog(ProceedingJoinPoint point) throws Throwable {
UserLogRecordDO userLogRecordDO = bulidRequestParams(point);
//先执行业务
try {
Object result = point.proceed();
if (result != null) {
userLogRecordDO.setResultMsg(JSONUtil.toJsonStr(result));
}
return result;
} catch (BusinessException e) {
userLogRecordDO.setResultCode(e.getCode());
userLogRecordDO.setResultMsg(e.getMessage());
throw e;
} catch (Exception e) {
userLogRecordDO.setResultCode("");
userLogRecordDO.setResultMsg("后端未知异常");
throw e;
} finally {
//操作日志入库
try {
//XXXService.save(userLogRecordDO);
} catch (Exception e) {
log.warn("记录用户操作异常:{}", e.getMessage());
}
}
}
/**
* 记录方法和方法入参
* @param point
* @return
*/
protected UserLogRecordDO bulidRequestParams(ProceedingJoinPoint point) {
MethodSignature methodSignature = (MethodSignature) point.getSignature();
Method method = methodSignature.getMethod();
AuditLog annotation = method.getAnnotation(AuditLog.class);
UserLogRecordDO userLogRecordDO = new UserLogRecordDO();
userLogRecordDO.setAction(annotation.action());
userLogRecordDO.setType(annotation.type().name());
userLogRecordDO.setMethodName(method.getDeclaringClass().getSimpleName() + "." + method.getName());
try {
// 处理入参
Parameter[] parameters = methodSignature.getMethod().getParameters();
HashMap<String, Object> paramMap = new HashMap<>();
Object[] args = point.getArgs();
for (int i = 0; i < parameters.length; i++) {
AuditLog auditLog = parameters[i].getAnnotation(AuditLog.class);
if (auditLog != null) {
continue;
}
Class<?> type = parameters[i].getType();
if (ServletResponse.class.isAssignableFrom(type) || ServletRequest.class.isAssignableFrom(type)) {
continue;
}
String name = parameters[i].getName();
paramMap.put(name, args[i]);
}
userLogRecordDO.setMethodParams(JSONUtil.toJsonStr(paramMap));
return userLogRecordDO;
} catch (Exception e) {
log.warn("构建入参异常:{}", e.getMessage());
}
return userLogRecordDO;
}
}
logMethodPointCut()
: 这个方法定义了一个切入点(pointcut),它使用@annotation
注解来匹配带有com.bocloud.devops.eaas.api.log.AuditLog
注解的方法。这意味着切面将在这些被标记为@AuditLog
的方法执行前后生效。
recordSysLog(ProceedingJoinPoint point)
: 这是一个环绕通知方法,用于包围切入点方法的执行。它负责记录系统日志和审计信息。具体的操作包括:
- 构建请求参数并创建一个
UserLogRecordDO
对象,其中包括操作描述、操作类型、方法名等信息。- 调用
point.proceed()
来执行切入点方法,捕获方法的执行结果,将结果信息记录到UserLogRecordDO
中。- 处理可能抛出的
BusinessException
异常,记录异常信息。- 在
finally
块中尝试将操作日志入库,但在此示例中,入库的部分被注释掉。
bulidRequestParams(ProceedingJoinPoint point)
: 这个方法用于构建方法的参数信息,并将其记录到UserLogRecordDO
对象中。它包括方法名、操作描述、操作类型以及方法的入参信息。此方法通过反射分析方法参数和参数上的AuditLog
注解,以构建参数信息的JSON表示。此切面的主要目的是在标记了
@AuditLog
注解的方法执行前后记录操作日志信息。在实际应用中,您需要确保数据库操作以及记录日志的部分(在finally
块中)按照实际需求进行配置。此示例代码中的入库部分被注释掉,您需要根据自己的需求实现相应的数据持久化逻辑。
三、记录日志的实体类
java
import io.swagger.annotations.ApiModelProperty;
import io.swagger.annotations.ApiOperation;
import lombok.Data;
/**
* @program: eaas-center
* @description:
* @author: yanchao
* @create: 2023-10-10 23:09
**/
@Data
@ApiOperation("操作日志记录表")
public class UserLogRecordDO {
@ApiModelProperty("主键")
private String id;
@ApiModelProperty("行为描述")
private String action;
@ApiModelProperty("执行方法")
private String methodName;
@ApiModelProperty("执行入参")
private String methodParams;
@ApiModelProperty("操作类型")
private String type;
@ApiModelProperty("响应编码")
private String resultCode;
@ApiModelProperty("结果描述")
private String resultMsg;
}
最后:使用该注解
java
/**
* 通过id查询中心维护表
*
* @param id 主键
* @return 单条数据
*/
@GetMapping("{id}")
@AuditLog(action = "通过id查询中心维护表", type = OperateEnum.SELECT)
public Result<EaasCentralMaintainQueryDetailRes> detail(@PathVariable("id") Integer id{
return Result.success(this.eaasCentralMaintainService.detail(id));
}
这样即可在数据库查看相应的操作日志记录。