1. 程式人生 > >Springboot 自定義註解 AOP切面獲取操作日誌

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;
    }
    

}

參考連結: