Springboot 自定义注解 AOP切面获取操作日志

编码思想:

新增和修改数据,记录用户操作信息(创建人,修改人) ,然后每个模块打算根据操作数据的主键id关联日志表查询操作人信息;需要声明每个模块名称及操作方法(constant包中便是声明的模块和操作方法枚举)

文件目录:





1. build.gradle引入jar包

compile('org.springframework.boot:spring-boot-starter-aop')

2.application.yml加入声明

spring:
  aop:
    proxy-target-class: true
    auto: true

3.自定义注解@ControllerLog

package com.wdletu.log.annotation;

import com.wdletu.log.constant.OperateModule;
import com.wdletu.log.constant.OperateType;

import java.lang.annotation.*;

/**
 * Created by zhangmy on 2017/7/24.
 */
@Target({ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface ControllerLog {
    /**
     * 操作描述 业务名称business
     *
     * @return
     */
    String description() default "";

    /**
     * 操作模块
     *
     * @return
     */
    OperateModule module();

    /**
     * 操作类型 create modify delete
     *
     * @return
     */
    OperateType opType();

    /**
     * 主键入参参数名称,入参中的哪个参数为主键
     *
     * @return
     */
    String primaryKeyName() default "";

    /**
     * 主键在参数中的顺序,从0开始,默认0
     */
    int primaryKeySort() default 0;

    /**
     * 主键所属类
     *
     * @return
     */
    Class<?> primaryKeyBelongClass();

}

4. 定义模块名称枚举

package com.wdletu.log.constant;

/**
 * Created by zhangmy on 2017/7/26.
 */
public enum OperateModule {
    SightMerchant("商家管理"), AdminUser("用户管理"), Tour("行程管理"), UserTour("用户行程");
    private String text;

    OperateModule(String text) {
        this.text = text;
    }

    public String getText() {
        return text;
    }
}


5.定义操作类型枚举
package com.wdletu.log.constant;

/**
 * Created by zhangmy on 2017/7/26.
 */
public enum OperateType {
    /**
     * 当操作类型为created时,要求方法返回格式为:return ok("具体操作信息", new MapBean("此处为实体主键属性名称", primaryKeyValue));
     */
    create, modify, delete
}

6. 切面

package com.wdletu.log.aspect;

import com.wdletu.core.exception.ServiceException;
import com.wdletu.core.util.MapBean;
import com.wdletu.core.util.StringUtil;
import com.wdletu.log.annotation.ControllerLog;
import com.wdletu.log.constant.OperateType;
import com.wdletu.log.entity.OperateLog;
import com.wdletu.log.service.OperateLogService;
import com.wdletu.security.JwtService;
import com.wdletu.travel.admin.entity.AdminUser;
import com.wdletu.travel.admin.service.AdminUserService;
import net.sf.json.JSONObject;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.CodeSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.persistence.Id;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;

/**
 * Created by zhangmy on 2017/7/25.
 */
@Aspect
@Component
public class WebLogAspect {
    @Autowired
    private OperateLogService operateLogService;
    @Autowired
    private JwtService jwtService;
    @Autowired
    private AdminUserService adminUserService;

    @Value("${jwt.header}")
    private String tokenHeader;

    private Logger logger = LoggerFactory.getLogger(this.getClass());

    /**
     * 获取class的主键字段名 主键值
     */
    public static Object getPrimaryKeyName(Class<?> clazz) throws Exception {
        Object param = null;
        //递归获取父子类所有的field
        Class tempClass = clazz;
        //当父类为null的时候说明到达了最上层的父类(Object类
        while (tempClass != null && !StringUtils.equals(tempClass.getName().toLowerCase(), "java.lang.object")) {
            Field[] fields = tempClass.getDeclaredFields();
            for (Field field : fields) {
                String fieldName = field.getName();
                //boolean类型不必判断,因实体里包含boolean类型的属性,getter方法是以is开头,不是get开头
                if (field.getType().equals(Boolean.class) || field.getType().getName().equals("boolean")) {
                    continue;
                }
                if ((field.getModifiers() & Modifier.FINAL) == Modifier.FINAL) {
                    continue;
                }
                String getterMethod = "get" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
                Method method = tempClass.getDeclaredMethod(getterMethod);

                //字段上是否存在@Id注解
                Id primaryAnnotation = field.getAnnotation(Id.class);
                //getter方法上是否存在@Id注解
                if (primaryAnnotation == null) {
                    primaryAnnotation = method.getAnnotation(Id.class);
                }
                //存在@Id注解,则说明该字段为主键
                if (primaryAnnotation != null) {
                    /*String primaryKeyName = field.getName();*/
                    param = field.getName();
                    break;
                }
            }
            if (param != null && StringUtil.isNotBlank(param.toString())) {
                break;
            }
            //得到父类赋值给tempClass
            tempClass = tempClass.getSuperclass();
        }
        if (param == null) {
            throw new ServiceException(clazz.getName() + "实体,未设置主键");
        }
        return param;
    }

    /**
     * 获取@controllerLog 注解上信息
     *
     * @param joinPoint
     * @return map
     * @throws Exception
     */
    public static Map<String, Object> getControllerAnnotationValue(JoinPoint joinPoint) throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        Object[] arguments = joinPoint.getArgs();
        Class targetClass = Class.forName(targetName);
        Method[] methods = targetClass.getMethods();
        Map<String, Object> map = new HashMap<>();
        for (Method method : methods) {
            if (method.getName().equals(methodName)) {
                Class[] classes = method.getParameterTypes();
                if (classes.length == arguments.length) {
                    //取入参数据
                    String description = method.getAnnotation(ControllerLog.class).description();
                    String module = method.getAnnotation(ControllerLog.class).module().name();
                    String opType = method.getAnnotation(ControllerLog.class).opType().name();
                    String primaryKeyName = method.getAnnotation(ControllerLog.class).primaryKeyName();
                    int primaryKeySort = method.getAnnotation(ControllerLog.class).primaryKeySort();
                    Class<?> clazz = method.getAnnotation(ControllerLog.class).primaryKeyBelongClass();
                    map.put("module", module);
                    map.put("opType", opType);
                    map.put("business", description);
                    map.put("primaryKeyName", primaryKeyName);
                    map.put("primaryKeySort", primaryKeySort);
                    map.put("primaryKeyBelongClass", clazz);
                    break;
                }
            }
        }
        return map;
    }

    /**
     * 定义一个切入点.
     * ("execution(public * com.kfit.*.web..*.*(..))")
     * 解释下:
     * 第一个 * 代表任意修饰符及任意返回值.
     * 第二个 * 任意包名
     * 第三个 * 代表任意方法.
     * 第四个 * 定义在web包或者子包
     * 第五个 * 任意方法
     * .. 匹配任意数量的参数.
     */
    @Pointcut("execution(public * com.wdletu..*.controller..*.*(..))  && @annotation(com.wdletu.log.annotation.ControllerLog)")
    public void webLog() {
    }

    @Around("webLog()")
    public Object round(ProceedingJoinPoint joinPoint) throws Throwable {
        logger.info("环绕通知开始........");
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        /*try {*/
        //读取用户
        String token = request.getHeader(tokenHeader);
        Long userId = jwtService.getUserIdFromToken(token);
        AdminUser adminUser = adminUserService.findByUserId(userId);
        String username = adminUser.getName();
        //入参  value
        Object[] args = joinPoint.getArgs();
        //入参名称
        String[] paramNames = ((CodeSignature) joinPoint.getSignature()).getParameterNames();
        Map<String, Object> params = new HashMap<>();
        //获取所有参数对象
        for (int i = 0; i < args.length; i++) {
            if (null != args[i]) {
                if (args[i] instanceof BindingResult) {
                    params.put(paramNames[i], "bindingResult");
                } else {
                    params.put(paramNames[i], args[i]);
                }
            } else {
                params.put(paramNames[i], "无");
            }
        }
        Map<String, Object> values = getControllerAnnotationValue(joinPoint);
        String opType = values.get("opType").toString();
        String module = values.get("module").toString();
        String business = values.get("business").toString();
        String primaryKeyName = values.get("primaryKeyName").toString();
        int primaryKeySort = Integer.parseInt(values.get("primaryKeySort").toString());
        Class<?> primaryKeyBelongClass = (Class<?>) values.get("primaryKeyBelongClass");

        Object primaryKeyValue = null;
        if (StringUtils.isNotBlank(primaryKeyName) && OperateType.valueOf(opType) != OperateType.create) {
            primaryKeyValue = args[primaryKeySort];
        }
        //切面返回值
        Object returnValue = joinPoint.proceed();
        if (OperateType.valueOf(opType) == OperateType.create) {
            // 主要目的是为了获取方法保存成功的返回数据格式,以获取保存成功的数据id
            // 此处要限制新增返回成功的格式,return ok("具体操作信息", new MapBean("此处为实体主键属性名称", primaryKeyValue));
            // 不然自己也可定义格式,进行拆分获取主键
            primaryKeyName = getPrimaryKeyName(primaryKeyBelongClass).toString();
            if (returnValue instanceof ResponseEntity<?>) {
                Object entity = ((ResponseEntity<?>) returnValue).getBody();
                if (entity instanceof MapBean) {
                    MapBean mapBean = (MapBean) entity;
                    Boolean success = mapBean.get("success");
                    if (success) {
                        MapBean content = mapBean.get("content");
                        if (content != null) {
                            primaryKeyValue = content.get(primaryKeyName);
                        }
                    }
                }
            }
        }

        OperateLog operateLog = new OperateLog();
        operateLog.setParams(JSONObject.fromObject(params).toString());
        operateLog.setUserId(userId);
        operateLog.setUserName(username);
        operateLog.setModule(module);
        operateLog.setOpType(opType);
        operateLog.setBusiness(business);
        operateLog.setRecordId(primaryKeyValue == null ? null : Long.parseLong(primaryKeyValue.toString()));
        operateLogService.save(operateLog);
        /*} catch (Throwable throwable) {
            throwable.printStackTrace();
            logger.error("日志切面异常", throwable.getMessage());

        }*/
        return returnValue;
    }

    @AfterReturning("webLog()")
    public void doAfterReturning(JoinPoint joinPoint) {
        // 处理完请求,返回内容
        logger.info("WebLogAspect.doAfterReturning()");
    }

}

7.Cotroller中引用自定义注解,从而对该方法进行切面处理

   /**
     * 新增
     */
    @ControllerLog(description = "新增商家信息", module = OperateModule.SightMerchant, opType = OperateType.create, primaryKeyBelongClass = SightMerchant.class)
    @RequestMapping(method = RequestMethod.POST)
    @ResponseBody
    public ResponseEntity save(@RequestBody @Valid SightMerchantInputVO sightMerchantInputVO, BindingResult bindingResult) {
        if (bindingResult.hasErrors()) {
            return error(bindingResult.getFieldError().getDefaultMessage());
        }
        Boolean primaryAccount = sightMerchantInputVO.getPrimaryAccount();
        String leaderPhone = sightMerchantInputVO.getLeaderPhone();
        if (primaryAccount != null && primaryAccount == true && StringUtils.isBlank(leaderPhone)) {
            return error("负责人电话不能为空");
        }
        SightMerchant sightMerchant = SightMerchantInputVO.from(sightMerchantInputVO);
        Long id = sightMerchantService.saveMerchantAndAccount(sightMerchant, primaryAccount);
        return ok("保存成功", new MapBean("id", id));
    }

 /**
     * 修改状态
     */
    @ControllerLog(description = "修改商家状态", module = OperateModule.SightMerchant, opType = OperateType.modify,
            primaryKeyName = "id", primaryKeySort = 1, primaryKeyBelongClass = SightMerchant.class)
    @RequestMapping(value = "/{id}/enabled", method = RequestMethod.PUT)
    @ResponseBody
    public ResponseEntity updateEnabled(Boolean enabled, @PathVariable Long id) {
        sightMerchantService.updateEnabled(id, enabled);
        return ok("操作成功");
    }

8.日志表

package com.wdletu.log.entity;


import com.wdletu.core.persistence.BaseEntity;

import javax.persistence.Entity;
import javax.persistence.Table;

/**
 * Created by zhangmy on 2017/6/29.
 */
@Entity
@Table(name = "operate_log")
public class OperateLog extends BaseEntity {
    private Long recordId; //操作数据id
    private String module;//模块名称
    private String business;//业务方法描述
    private String opType;//操作类型
    private Long userId;//操作人
    private String userName;//操作人姓名
    private String params;//操作数据

    public OperateLog() {
    }


    public Long getRecordId() {
        return recordId;
    }

    public void setRecordId(Long recordId) {
        this.recordId = recordId;
    }

    public String getModule() {
        return module;
    }

    public void setModule(String module) {
        this.module = module;
    }

    public String getBusiness() {
        return business;
    }

    public void setBusiness(String business) {
        this.business = business;
    }

    public String getOpType() {
        return opType;
    }

    public void setOpType(String opType) {
        this.opType = opType;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getParams() {
        return params;
    }

    public void setParams(String params) {
        this.params = params;
    }
    

}

参考链接:

http://www.cnblogs.com/shipengzhi/articles/2716004.html

http://412887952-qq-com.iteye.com/blog/2305333

http://blog.csdn.net/paincupid/article/details/51176149

http://www.cnblogs.com/chihirotan/p/6228337.html

http://blog.csdn.net/gengqiquan/article/details/70185731


评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值