一、 自定义 注解 的 参数
/**
* AroundLog注解
*
* @author 华中强
* @date 2024-09-20
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface AuditLog {
/**
* 操作描述
* @return
*/
String opBusinessName() default "";
/**
* 操作类型(增删改查)
* @return
*/
OperateEnum type() default OperateEnum.MODIFY;
/**
* 业务唯一标识,如订单id
* @return
*/
String opBusinessId() default "";
/**
* @author: Huazq
* @description: 成功字段
* @date: 2025/2/10 13:42
* @Params
*/
String successField() default "";
/**
* @author: Huazq
* @description: 成功值
* @date: 2025/2/10 13:42
* @Params
*/
String successValue() default "";
}
2 翻看源码得出 @Retention(RetentionPolicy.RUNTIME) 是控制注解什么时候执行,
/*
* Copyright (c) 2003, 2004, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*/
package java.lang.annotation;
/**
* Annotation retention policy. The constants of this enumerated type
* describe the various policies for retaining annotations. They are used
* in conjunction with the {@link Retention} meta-annotation type to specify
* how long annotations are to be retained.
*
* @author Joshua Bloch
* @since 1.5
*/
public enum RetentionPolicy {
/**
* Annotations are to be discarded by the compiler.
*/
SOURCE,
/**
* Annotations are to be recorded in the class file by the compiler
* but need not be retained by the VM at run time. This is the default
* behavior.
*/
CLASS,
/**
* Annotations are to be recorded in the class file by the compiler and
* retained by the VM at run time, so they may be read reflectively.
*
* @see java.lang.reflect.AnnotatedElement
*/
RUNTIME
}
2 翻看源码得出
@Target(ElementType.METHOD)
: 这个元注解表示AuditLog
注解只能用于方法上。
/*
* Copyright (c) 2003, 2013, Oracle and/or its affiliates. All rights reserved.
* ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
*
*/
package java.lang.annotation;
/**
* The constants of this enumerated type provide a simple classification of the
* syntactic locations where annotations may appear in a Java program. These
* constants are used in {@link Target java.lang.annotation.Target}
* meta-annotations to specify where it is legal to write annotations of a
* given type.
*
* <p>The syntactic locations where annotations may appear are split into
* <em>declaration contexts</em> , where annotations apply to declarations, and
* <em>type contexts</em> , where annotations apply to types used in
* declarations and expressions.
*
* <p>The constants {@link #ANNOTATION_TYPE} , {@link #CONSTRUCTOR} , {@link
* #FIELD} , {@link #LOCAL_VARIABLE} , {@link #METHOD} , {@link #PACKAGE} ,
* {@link #PARAMETER} , {@link #TYPE} , and {@link #TYPE_PARAMETER} correspond
* to the declaration contexts in JLS 9.6.4.1.
*
* <p>For example, an annotation whose type is meta-annotated with
* {@code @Target(ElementType.FIELD)} may only be written as a modifier for a
* field declaration.
*
* <p>The constant {@link #TYPE_USE} corresponds to the 15 type contexts in JLS
* 4.11, as well as to two declaration contexts: type declarations (including
* annotation type declarations) and type parameter declarations.
*
* <p>For example, an annotation whose type is meta-annotated with
* {@code @Target(ElementType.TYPE_USE)} may be written on the type of a field
* (or within the type of the field, if it is a nested, parameterized, or array
* type), and may also appear as a modifier for, say, a class declaration.
*
* <p>The {@code TYPE_USE} constant includes type declarations and type
* parameter declarations as a convenience for designers of type checkers which
* give semantics to annotation types. For example, if the annotation type
* {@code NonNull} is meta-annotated with
* {@code @Target(ElementType.TYPE_USE)}, then {@code @NonNull}
* {@code class C {...}} could be treated by a type checker as indicating that
* all variables of class {@code C} are non-null, while still allowing
* variables of other classes to be non-null or not non-null based on whether
* {@code @NonNull} appears at the variable's declaration.
*
* @author Joshua Bloch
* @since 1.5
* @jls 9.6.4.1 @Target
* @jls 4.1 The Kinds of Types and Values
*/
public enum ElementType {
/** Class, interface (including annotation type), or enum declaration */
TYPE,
/** Field declaration (includes enum constants) */
FIELD,
/** Method declaration */
METHOD,
/** Formal parameter declaration */
PARAMETER,
/** Constructor declaration */
CONSTRUCTOR,
/** Local variable declaration */
LOCAL_VARIABLE,
/** Annotation type declaration */
ANNOTATION_TYPE,
/** Package declaration */
PACKAGE,
/**
* Type parameter declaration
*
* @since 1.8
*/
TYPE_PARAMETER,
/**
* Use of a type
*
* @since 1.8
*/
TYPE_USE
}
切面实现,表示
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.RequiredArgsConstructor;
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.annotation.Resource;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.HashMap;
/**
* @BelongsProject: tms-server-workspace
* @BelongsPackage: com.auxgroup.tms.portal.aspect
* @Author: 华中强
* @CreateTime: 2024-09-23 14:21
* @Description: TODO
* @Version: 1.0
*/
@Aspect
@Slf4j
@Component
@RequiredArgsConstructor
public class AuditLogAspect extends AuxBaseController {
private final ObjectMapper objectMapper;
/**
* 注入数据源
*/
@Resource
private final ITOuterLogService outerLogService;
private TOuterLogDto buildRequestParams(ProceedingJoinPoint joinPoint) throws Throwable{
Class<?> targetCls=joinPoint.getTarget().getClass();
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
//获取目标方法上的注解指定的操作名称
Method targetMethod=
targetCls.getDeclaredMethod(
methodSignature.getName(),
methodSignature.getParameterTypes());
AuditLog auditLog=targetMethod.getAnnotation(AuditLog.class);
TOuterLogDto operationLogDO =TOuterLogDto.builder().id(TMSIDUtil.getSnowFlake()).opBadge(getBadgeNo()).oaUserName(getOaUsername()).opMobile(getMobile()).unitCode(getTenantCode())
.opBusinessName(auditLog.opBusinessName())
.opType(auditLog.type().name())
.successField(auditLog.successField())
.successValue(auditLog.successValue())
.methodName(method.getDeclaringClass().getSimpleName() + "." + method.getName()).build();
// 处理入参数
try {
Parameter[] parameters = methodSignature.getMethod().getParameters();
HashMap<String, Object> paramMap = new HashMap<>();
Object[] args = joinPoint.getArgs();
for (int i = 0; i < parameters.length; i++) {
String name = parameters[i].getName();
paramMap.put(name, args[i]);
operationLogDO.setReqMsg(objectMapper.writeValueAsString(paramMap));
}
// 获取返回值
operationLogDO.setResMsg(AuxJsonUtil.toString(joinPoint.proceed()));
} catch (JsonProcessingException e) {
log.warn("构建入参异常:{}", e.getMessage());
}
//操作日志入库
try {
outerLogService.insertByDto(operationLogDO);
} catch (Exception e) {
log.error("记录用户操作异常:", e);
}
return operationLogDO;
}
/**
* 只拦截带AuditLog注解的方法
*/
@Pointcut(value ="@annotation(com.auxgroup.tms.api.log.annotation.AuditLog)")
private void logPoint() {
log.warn("AuditLog切入点:");
}
public void cutAll(){
log.warn("切入点: ");
}
/**
* 环绕通知,用于记录方法执行前后日志,并处理方法执行结果
* @param joinPoint 连接点
* @return 方法执行结果
* @throws Throwable 异常
*/
@Around(value ="logPoint()")
public Object aroundLog(ProceedingJoinPoint joinPoint) throws Throwable {
// 执行连接点前,记录信息
TOuterLogDto userLogRecordDO = buildRequestParams(joinPoint);
try {
Object result = joinPoint.proceed();
handleResult(userLogRecordDO, result, joinPoint);
return result;
} catch (Exception e) {
userLogRecordDO.setRetCode("E");
userLogRecordDO.setRetMsg("后端未知异常,错误消息:" + e.getMessage());
throw e;
} finally {
// 操作日志入库
try {
if (userLogRecordDO.getId() != null) {
outerLogService.updateByDto(userLogRecordDO);
}
} catch (Exception e) {
log.error("更新用户操作异常", e);
}
}
}
/**
* 处理方法执行结果,设置返回码和返回消息
* @param userLogRecordDO 操作日志对象
* @param result 方法执行结果
* @param joinPoint 连接点
*/
private void handleResult(TOuterLogDto userLogRecordDO, Object result, ProceedingJoinPoint joinPoint) {
if (result != null) {
try {
String resultJson = objectMapper.writeValueAsString(result);
userLogRecordDO.setRetCode("S");
userLogRecordDO.setRetMsg(resultJson);
HashMap resultMap = AuxJsonUtil.parse(resultJson, HashMap.class);
// 获取注解中的 successField 和 successValue
MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
Method method = methodSignature.getMethod();
AuditLog auditLog = method.getAnnotation(AuditLog.class);
String successField = auditLog.successField();
String successValue = auditLog.successValue();
if (!isSuccess(resultMap, successField, successValue)) {
userLogRecordDO.setRetCode("E");
userLogRecordDO.setIsSuccess(false);
userLogRecordDO.setRetMsg("业务处理失败,错误消息:" + resultMap.getOrDefault("message", "未知错误"));
}
} catch (JsonProcessingException e) {
log.warn("处理返回值异常:{}", e.getMessage());
}
}
}
/**
* 递归检查对象中是否包含指定的成功字段和成功值
* @param obj 要检查的对象
* @param successField 成功字段名称
* @param successValue 成功字段的预期值
* @return 如果找到成功字段且其值等于预期值,则返回 true;否则返回 false
*/
private boolean isSuccess(Object obj, String successField, String successValue) {
if (obj instanceof HashMap) {
HashMap<?, ?> map = (HashMap<?, ?>) obj;
if (map.containsKey(successField)) {
if (map.get(successField) instanceof Boolean){
return successValue.equals(map.get(successField).toString());
}else {
return successValue.equals(map.get(successField));
}
} else {
for (Object value : map.values()) {
if (isSuccess(value, successField, successValue)) {
return true;
}
}
}
}
return false;
}
}