AOP日志高级写法

场景:在日程开发过程中,通常需要记录某接口操作的日志,但是需要的参数需要通过查询数据库后才有,有的数据需要当前接口操作完成之后才有的,或者更加复杂的自定义规则。那么普通方式的aop切面日志就不太够用了
先看使用后的案例

@RestController
@RequestMapping("/job")
@Api(tags = "职位模块")
@OperationLog(logClass = JobLogService.class) //标记使用的logService类
public class JobController {
    @OperationLogHandler(isAfterMethod = true) //标记需要保存日志的方法 isAfterMethod 表示需要方法执行后再保存日志
    @PostMapping("/operate/status")
    @ApiOperation("职位状态操作(审核职位/分配负责人/拒绝/停止招聘)")
    public void operateStatus(@RequestBody @Valid JobOperateDTO jobOperateDto) {
        jobService.operate(userDto, jobOperateDto);
    }
}
//日志服务类
@Service
public class JobLogService {
     @Resource
    private JobService jobService;
    @Resource
    private SysUserMapper sysUserMapper;
    //对应的方法  需要方法名称 参数完全一致  返回类型为自定义的类(Content)
    public Content operateStatus(JobOperateDTO jobOperateDto) {
        Optional<Status> statusOptional = Arrays.stream(Status.values()).filter(status -> status.getItem().equals(jobOperateDto.getStatus())).findFirst();
        if (statusOptional.isPresent()) {
            JobModelVO jobModelVO = jobService.selectJobModelVOById(jobOperateDto.getJobId());
            Content content = new Content();
            switch (statusOptional.get()) {
                case AUDITING:
                    content.setBehavior(BehaviorEnum.JOB_AUDIT);
                    content.setContent(StrUtil.format("【通过】【{companyName}】发布的【{jobName}】", BeanUtil.beanToMap(jobModelVO)));
                    break;
                case ALLOCATED:
                    content.setBehavior(BehaviorEnum.JOB_ALLOCATED);
                    Map<String, Object> map = BeanUtil.beanToMap(jobModelVO);
                    map.put("sysUserNameMobile", "无");
                    if (jobOperateDto.getSysUserIdList() != null && !jobOperateDto.getSysUserIdList().isEmpty()) {
                        List<SysUserEntity> sysUserEntityList = sysUserMapper.selectBatchIds(jobOperateDto.getSysUserIdList());
                        if (CollUtil.isNotEmpty(sysUserEntityList)) {
                            String sysUserNameMobile = sysUserEntityList.stream().map(x -> StrUtil.format("{}/{}", x.getRealName(), x.getMobile())).collect(Collectors.joining(","));
                            map.put("sysUserNameMobile", sysUserNameMobile);
                        }
                    }
                    content.setContent(StrUtil.format("【分配】【{companyName}】发布的【{jobName}】给【{sysUserNameMobile}】", map));
                    break;
                case REJECTED:
                    content.setBehavior(BehaviorEnum.JOB_AUDIT);
                    content.setContent(StrUtil.format("【不通过】【{companyName}】发布的【{jobName}】【{sysUserFailRemark}】", BeanUtil.beanToMap(jobModelVO)));
                    break;
                case STOP:
                    content.setBehavior(BehaviorEnum.JOB_STOP);
                    content.setContent(StrUtil.format("【停止招聘】【{companyName}】发布的【{jobName}】", BeanUtil.beanToMap(jobModelVO)));
                    break;
                case RESTORE_HIRING:
                    content.setBehavior(BehaviorEnum.JOB_RESTORE_HIRING);
                    content.setContent(StrUtil.format("【{companyName}】【{jobName}】恢复招聘", BeanUtil.beanToMap(jobModelVO)));
                    break;
                default:
                    break;
            }
            return content;
        }
        return null;
    }
}

需要使用的类以及注解

/**
 * 系统日志注解 作用在类上
 * 作用在复杂情况下的日志场景下<br/>
 * 1、新建一个spring bean  命名为 xxxLogService<br/>
 * 2、选择要记录日志的一个方法(比如void save(Dto dto)),复制到 xxxLogService 类,返回值必须为Content 或者 List< Content ><br/>
 * 3、在方法 save 上加上注解@OperationLogHandler
 *
 * @author
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
public @interface OperationLog {
    /**
     * 日志代理类 必填
     */
    Class logClass() default void.class;
}


import cn.xx.common.constant.BehaviorEnum;

import java.lang.annotation.*;

/**
 * 日志记录核心注解,作用在具体的方法上
 * 复杂情况下搭配注解 @OperationLog 在类上一起使用
 *
 * @author
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
@Documented
public @interface OperationLogHandler {


    /**
     * 操作 日志枚举类  简单使用时必填
     */
    BehaviorEnum behavior() default BehaviorEnum.NULL;

    /**
     * 操作对象 一般值参数对象 简单使用时必填
     */
    String object() default "";

    /**
     * 操作详情 在简单的情况下使用,比如参数已经有日志需要的数据了
     */
    String content() default "";

    /**
     * 默认为false
     * false 注解在Controller,需要自己重写操作日志逻辑,返回Content
     * true 注解在操作记录实现类,针对已经写过操作记录的方法,直接修改操作记录返回值为Content类型,切面直接获取返回值。
     */
    boolean isReturn() default false;

    /**
     * 是否在方法执行完后调用
     *
     * @return boolean
     */
    boolean isAfterMethod() default false;

}



import cn.xx.common.annotation.OperationLog;
import cn.xx.common.annotation.OperationLogHandler;
import cn.xx.common.constant.Constant;
import cn.xx.common.convert.UserOperationLogConverter;
import cn.xx.common.dto.log.Content;
import cn.xx.common.dto.log.OperationLogEntity;
import cn.xx.common.dto.user.UserDto;
import cn.xx.common.entity.SysUserEntity;
import cn.xx.common.entity.UserOperationLog;
import cn.xx.common.mapper.UserOperationLogMapper;
import cn.xx.common.utils.ApplicationUtils;
import cn.xx.common.utils.SpringContextUtils;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.PropertyFilter;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Scope;
import org.springframework.core.DefaultParameterNameDiscoverer;
import org.springframework.core.annotation.Order;
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 org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;

/**
 * 日志切面
 */
@Slf4j
@Aspect
@Component
@Order(2)
@Scope("prototype")
public class OperationLogAspect {

    /**
     * 用于SpEL表达式解析.
     */
    private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
    /**
     * 用于获取方法参数定义名字.
     */
    private DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();

    @Pointcut("@within(operationLogHandler) || @annotation(operationLogHandler)")
    public void point(OperationLogHandler operationLogHandler) {
    }

    private List<OperationLogEntity> logEntityList;

    @SuppressWarnings("unchecked")
    @Before(value = "point(operationLogHandler)", argNames = "joinPoint,operationLogHandler")
    public void beforeMethod(JoinPoint joinPoint, OperationLogHandler operationLogHandler) {
        OperationLog sysLog = joinPoint.getTarget().getClass().getDeclaredAnnotation(OperationLog.class);
        if (operationLogHandler == null) {
            return;
        }
        logEntityList = new ArrayList<>();
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        OperationLogEntity operationLogEntity = getLog(joinPoint);

        try {
            if (operationLogHandler.isReturn()) {
                logEntityList.add(operationLogEntity);
            } else if (!operationLogHandler.isAfterMethod()) {
                if (StrUtil.isNotEmpty(operationLogHandler.object()) && StrUtil.isNotEmpty(operationLogHandler.content())) {
                    String object = operationLogHandler.object();
                    String content = operationLogHandler.content();
                    if (object.contains("#")) {
                        //获取方法参数值
                        Object[] args = joinPoint.getArgs();
                        object = getValBySpEL(object, methodSignature, args);
                    }
                    if (content.contains("#")) {
                        //获取方法参数值
                        Object[] args = joinPoint.getArgs();
                        content = getValBySpEL(content, methodSignature, args);
                    }
                    operationLogEntity.setContent(content);
                    operationLogEntity.setObject(object);
                    operationLogEntity.setBehavior(operationLogHandler.behavior().getTitle());
                    logEntityList.add(operationLogEntity);
                } else {
                    Method method = methodSignature.getMethod();
                    Class proxyClass = sysLog.logClass();
                    Method logMethod = proxyClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
                    Object object = SpringContextUtils.getBean(proxyClass);
                    Object logInvoke = logMethod.invoke(object, joinPoint.getArgs());
                    logEntityList.addAll(transferLogEntity(logInvoke, operationLogEntity, operationLogHandler));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("日志记录异常:{}", e.getMessage());
        }
    }

    @AfterReturning(value = "point(operationLogHandler)", argNames = "joinPoint,operationLogHandler,val", returning = "val")
    public void afterMethodReturning(JoinPoint joinPoint, OperationLogHandler operationLogHandler, Object val) {
        if (operationLogHandler == null) {
            return;
        }
        try {
            if (operationLogHandler.isReturn()) {
                OperationLogEntity operationLogEntity = logEntityList.get(0);
                logEntityList = new ArrayList<>();
                logEntityList.addAll(transferLogEntity(val, operationLogEntity, operationLogHandler));
            } else if (operationLogHandler.isAfterMethod()) {
                OperationLog sysLog = joinPoint.getTarget().getClass().getDeclaredAnnotation(OperationLog.class);
                logEntityList = new ArrayList<>();
                MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
                OperationLogEntity operationLogEntity = getLog(joinPoint);

                if (StrUtil.isNotEmpty(operationLogHandler.object()) && StrUtil.isNotEmpty(operationLogHandler.content())) {
                    String object = operationLogHandler.object();
                    String content = operationLogHandler.content();
                    if (object.contains("#")) {
                        //获取方法参数值
                        Object[] args = joinPoint.getArgs();
                        object = getValBySpEL(object, methodSignature, args);
                    }
                    if (content.contains("#")) {
                        //获取方法参数值
                        Object[] args = joinPoint.getArgs();
                        content = getValBySpEL(content, methodSignature, args);
                    }
                    operationLogEntity.setContent(content);
                    operationLogEntity.setObject(object);
                    operationLogEntity.setBehavior(operationLogHandler.behavior().getTitle());
                    logEntityList.add(operationLogEntity);
                } else {
                    Method method = methodSignature.getMethod();
                    Class proxyClass = sysLog.logClass();
                    Method logMethod = proxyClass.getDeclaredMethod(method.getName(), method.getParameterTypes());
                    Object object = SpringContextUtils.getBean(proxyClass);
                    Object logInvoke = logMethod.invoke(object, joinPoint.getArgs());
                    logEntityList.addAll(transferLogEntity(logInvoke, operationLogEntity, operationLogHandler));
                }
            }

            for (OperationLogEntity operationLogEntity : logEntityList) {
                //封禁状态不记录日志
                if (operationLogEntity.isBan()) {
                    break;
                }
                UserOperationLog userOperationLog = SpringContextUtils.getBean(UserOperationLogConverter.class).toEntity(operationLogEntity);
                if (userOperationLog != null) {
                    if (operationLogEntity.getTargetUser() != null) {
                        UserDto targetUser = operationLogEntity.getTargetUser();
                        userOperationLog.setTargetUserId(Integer.parseInt(targetUser.getUserId().toString()));
                        userOperationLog.setTargetUserName(targetUser.getUserName());
                        userOperationLog.setTargetUserMobile(targetUser.getUserMobile());
                        userOperationLog.setTargetUserType(targetUser.getUserType());
                    }
                    SpringContextUtils.getBean(UserOperationLogMapper.class).insert(userOperationLog);
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("日志记录异常:{}", e.getMessage());
        }
    }

    @SuppressWarnings("unchecked")
    private List<OperationLogEntity> transferLogEntity(Object obj, OperationLogEntity operationLogEntity, OperationLogHandler operationLogHandler) {
        List<OperationLogEntity> operationLogEntityList = new ArrayList<>();
        if (obj instanceof Content) {
            Content content = (Content) obj;
            operationLogEntity.setContent(content.getContent());
            operationLogEntity.setObject(content.getObject());
            operationLogEntity.setTargetUser(content.getTargetUser());
            if (content.getBehavior() == null) {
                operationLogEntity.setBehavior(operationLogHandler.behavior().getTitle());
            } else {
                operationLogEntity.setBehavior(content.getBehavior().getTitle());
            }
            operationLogEntityList.add(operationLogEntity);
        } else if (obj instanceof Collection) {
            List<Content> contentList = (List) obj;
            for (Content content : contentList) {
                OperationLogEntity operationLog = ObjectUtil.clone(operationLogEntity);
                operationLog.setContent(content.getContent());
                operationLog.setObject(content.getObject());
                operationLog.setTargetUser(content.getTargetUser());
                if (content.getBehavior() == null) {
                    operationLog.setBehavior(operationLogHandler.behavior().getTitle());
                } else {
                    operationLog.setBehavior(content.getBehavior().getTitle());
                }
                operationLogEntityList.add(operationLog);
            }
        }
        return operationLogEntityList;
    }

    /**
     * 构建日志对象
     */
    private OperationLogEntity getLog(JoinPoint joinPoint) {
        OperationLogEntity operationLogEntity = new OperationLogEntity();
        Object[] args = joinPoint.getArgs();
        List<Object> objectList = new ArrayList<>(args.length);
        for (Object arg : args) {
            boolean parse = parse(arg);
            if (parse) {
                objectList.add(arg);
            }
        }
        operationLogEntity.setArgs(JSON.toJSONString(objectList, new JsonFilter()));
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        operationLogEntity.setClassName(methodSignature.getDeclaringTypeName());
        operationLogEntity.setMethodName(methodSignature.getName());
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        if (attributes != null) {
            HttpServletRequest request = attributes.getRequest();
            String ipAddress = ServletUtil.getClientIP(request);
            operationLogEntity.setCreatorIp(ipAddress);
        }
        UserDto userInfo = ApplicationUtils.getUserInfo();
        if (userInfo != null) {
            operationLogEntity.setCreatorId(Integer.parseInt(String.valueOf(userInfo.getUserId())));
            operationLogEntity.setCreatorName(userInfo.getUserName());
            operationLogEntity.setCreatorMobile(userInfo.getUserMobile());
            operationLogEntity.setCreatorType(userInfo.getUserType());
            if (userInfo.getUserInfo() != null && userInfo.getUserInfo() instanceof SysUserEntity) {
                SysUserEntity sysUserEntity = (SysUserEntity) userInfo.getUserInfo();
                if (Objects.equals(sysUserEntity.getIsBan(), Constant.YES)) {
                    operationLogEntity.setBan(true);
                }
                operationLogEntity.setCreatorName(sysUserEntity.getRealName());
            }
        }
        return operationLogEntity;
    }

    class JsonFilter implements PropertyFilter {

        /**
         * @param object the owner of the property
         * @param name   the name of the property
         * @param value  the value of the property
         * @return true if the property will be included, false if to be filtered out
         */
        @Override
        public boolean apply(Object object, String name, Object value) {
            return parse(object) && parse(value);
        }
    }

    /**
     * 数据是否可以序列化
     *
     * @param object
     * @return true
     */
    private boolean parse(Object object) {
        if (object instanceof HttpServletRequest) {
            return false;
        } else if (object instanceof HttpServletResponse) {
            return false;
        } else if (object instanceof MultipartFile) {
            return false;
        } else if (object instanceof OutputStream) {
            return false;
        }
        return true;
    }

    /**
     * 解析spEL表达式
     */
    private String getValBySpEL(String spEL, MethodSignature methodSignature, Object[] args) {
        //获取方法形参名数组
        String[] paramNames = nameDiscoverer.getParameterNames(methodSignature.getMethod());
        if (paramNames != null && paramNames.length > 0) {
            Expression expression = spelExpressionParser.parseExpression(spEL);
            // spring的表达式上下文对象
            EvaluationContext context = new StandardEvaluationContext();
            // 给上下文赋值
            for (int i = 0; i < args.length; i++) {
                context.setVariable(paramNames[i], args[i]);
            }
            Object value = expression.getValue(context);
            return value != null ? value.toString() : null;
        }
        return null;
    }
}

OperationLogAspect类没有采用单例模式,原因:使用了局部变量临时保存数据,如果使用单例模式会有问题



import lombok.AllArgsConstructor;
import lombok.Getter;

@AllArgsConstructor
@Getter
public enum BehaviorEnum {
    /**
     * 日志操作类型枚举类
     */
    LOGOUT("登出"),
    LOGIN("登录"),
    NULL(""),
    ;

    private String title;
}

import cn.xx.common.constant.BehaviorEnum;
import cn.xx.common.dto.user.UserDto;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Content {
	
    //可能需要用到的
    private String object;
    //内容
    private String content;
	//枚举类 
    private BehaviorEnum behavior;

    @ApiModelProperty(value = "目标用户")
    private UserDto targetUser;


}

尽管涉及到的组件较多,但是一旦理解,就会明白这种方式非常便捷,可以处理更复杂的日志情况。
这套AOP日志的源头是悟空CRM,我对原始AOP日志逻辑进行了优化,而非简单搬运。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值