自定义注解初始--切面log记录交互日志

一、 自定义 注解 的 参数

​
/**
 * 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;
    }
}
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值