AOP:使用自定義註解記錄指定方法的操作,實現日誌功能

項目情況:

        pmdb項目開發過程中需要實現一個操作日誌的功能,即將對數據庫指定表的增刪改查操作進行記錄。我採用的是面向切面編程+自定義註解的方式實現的。這種方式的優點是,可以指定哪些方法的操作被記錄。

        我在開發中遇到的問題是:

             1.我們的數據庫表是有業務主鍵的,無法根據數據庫命名規則來獲取到。(日誌記錄中需要存入業務主鍵的值);

             2.有的表沒有業務主鍵,可以通過命名規則根據表名來獲得Id字段名。但是有的表有多個業務主鍵,這就需要進行特殊的處理。

解決方案:

第一步:添加依賴

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

第二步:編寫自定義註解類



import java.lang.annotation.*;


// 自定義註釋

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface MethodRemark {

    // 操作描述
    String remark() default "";

    // 0:查 1:增  2:刪  3:改內容  4: archive  5: unarchive'
    String opType() default "0";

    //所操作表名
    String tableName() default "";

    //主鍵字段名稱
    String keyName() default "";

    //業務主鍵名稱
    String[] businessKeyName() default {""};
}

第三步:編寫切面類(這裏面如何過去請求參數和響應參數並進行處理是關鍵)


import com.handlecar.basf_pmdb_service.common.CommonConstant;
import com.handlecar.basf_pmdb_service.dto.AbstractInputDto;
import com.handlecar.basf_pmdb_service.dto.AbstractOutputDto;
import com.handlecar.basf_pmdb_service.entity.log.BusinessCostDataLog;
import com.handlecar.basf_pmdb_service.service.log.LogService;
import com.handlecar.basf_pmdb_service.tools.JsonUtils;
import com.handlecar.basf_pmdb_service.tools.Util;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.*;


@Component
@Aspect
public class OperationLogs {
    private static Logger logger = LoggerFactory.getLogger(OperationLogs.class);

    @Value("${publicconfig.openlog}")
    private int isOpen;

    @Pointcut("execution(public * com.*.*.controller..*(..))")
    public void pointcut1(){}

    @Autowired
    private LogService logService;

    private BusinessCostDataLog businessCostDataLog;

    private String keyName;

    private String keyValue;


    @Before(value = "pointcut1()")
    public void beforMethod(JoinPoint point) throws Exception {
        MethodRemark methodRemark = getMethodRemark(point);
        if(methodRemark !=null && isOpen == 1){
            String opType = methodRemark.opType();  //操作類型  0:查 1:增  2:刪  3:改內容  4: archive  5: unarchive'
            String tableName = methodRemark.tableName(); //操作表名
            String username = getCurrentUser();
            String contentorg=""; //原內容
            String contentnow=""; //現內容
            this.keyName = getKeyName(tableName);
            //如果通過表名獲取不到主鍵名稱,就通過註解指定
            keyName=keyName==null? methodRemark.keyName():getKeyName(tableName);//主鍵名稱
            this.keyValue = getInputParamValue(point,keyName);//主鍵值
            String[] businessNames = methodRemark.businessKeyName(); //業務主鍵名稱



            //封裝日誌參數
            BusinessCostDataLog log=new BusinessCostDataLog();
            log.setBcdlopdate(new Date()); // datetime NOT NULL COMMENT '操作時間',
            log.setBcdlopuser(username); // varchar(256) NOT NULL COMMENT '操作用戶',
            log.setBcdloptype(Integer.parseInt(opType)); // int(11) NOT NULL COMMENT '操作類型 0:查 1:增  2:刪  3:改內容  4: archive  5: unarchive',
            log.setBcdloptable(tableName); // varchar(64) DEFAULT NULL COMMENT '操作的表名',


            Map<String,Object> mapToJson=null;
            StringBuilder businessValue = new StringBuilder(); //業務主鍵的值
            for (String businessName : businessNames){
                //刪除或者修改需要查詢原內容
                if(opType.equals("2") || opType.equals("3")){
                      //如果該表是business_data_keyword_values,特別處理
                    if(tableName.equals(CommonConstant.KEYWORDS_TABLE_NAME)){
                         //從business_data_keywords表中查詢
                        mapToJson = logService.getBcdlcontentorg(log,keyName,keyValue);
                        String bdkkeyword=mapToJson.get("bdkkeyword").toString();  //關鍵字bdkkeyword的值---業務主鍵的值
                        //根據bdkkeyword的值到數據庫中查business_data_keyword_values表的記錄
                        log.setBcdloptable(CommonConstant.KEY_WORD_VALUES_TABLE_NAME);
                        contentorg=logService.getKeywordValuesLog(CommonConstant.KEY_WORD_VALUES_TABLE_NAME,businessName,bdkkeyword);
                        businessValue.append(bdkkeyword).append(","); //業務主鍵的值
                    }else{
                        //查詢原內容
                        mapToJson = logService.getBcdlcontentorg(log,keyName,keyValue);
                        businessValue.append(mapToJson.get(businessName).toString()).append(",") ;
                        contentorg= JsonUtils.map2JsonString(mapToJson);
                    }
                }else if(opType.equals("4") || opType.equals("5")){
                    //增加,歸檔,解檔需要存關鍵字段值
                    mapToJson=logService.getBcdlcontentorg(log,keyName,keyValue);
                    businessValue.append(mapToJson.get(businessName).toString()).append(",") ;
                }

            }
            String keyname=businessValue.toString();
            //當opType=1時暫時不處理
            if(!Util.isEmpty(keyname)){
                log.setBcdlkeyname(keyname.substring(0,businessValue.length()-1)); // varchar(64) DEFAULT NULL COMMENT '操作的記錄的關鍵字段值',
                log.setBcdlcontentorg(contentorg); // varchar(1024) DEFAULT NULL COMMENT '原內容',
                log.setBcdlcontentnow(contentnow); // varchar(1024) DEFAULT NULL COMMENT '現內容',
            }
            this.businessCostDataLog = log;
        }
    }


    @AfterReturning(returning = "result",pointcut = "pointcut1()")
    public void doAfterReturning(JoinPoint point, Object result) throws Throwable {
        //取註解中設置的值
        MethodRemark methodRemark = getMethodRemark(point);
        if(methodRemark!=null && isOpen == 1) {   //如果添加了註解,被掃描到
            //用戶操作類型  0:查 1:增  2:刪  3:改內容  4: archive  5: unarchive'
            String opType = methodRemark.opType();
            BusinessCostDataLog log = this.businessCostDataLog;

            //添加的情況需要獲取添加的關鍵字段值(添加的話可以直接從輸入參數中找到對應的值)
            if (opType.equals("1")) {
                String[] businessKeyNames =methodRemark.businessKeyName();
                StringBuilder businessValue = new StringBuilder();
                for(String businessKeyName : businessKeyNames){
                    businessValue.append(getInputParamValue(point,businessKeyName)).append(",");
                }
                log.setBcdlkeyname(businessValue.toString().substring(0,businessValue.length()-1)); // varchar(64) DEFAULT NULL COMMENT '操作的記錄的關鍵字段值',
            }

            //判斷當前操作是否成功
            AbstractOutputDto abstractOutputDto=(AbstractOutputDto) result;
            int status=abstractOutputDto.getStatus();
            if(status==1){ //操作成功
                logService.insertBcdlcontentorg(log);
            }
        }
    }



    //根據表名獲取關鍵字段名稱
    private  String getKeyName(String tableName){
        String[] split = tableName.split("_");
        StringBuilder keyName = new StringBuilder();
        for(String str:split){
            keyName.append(str.substring(0,1));
        }
        keyName.append("id");
        return keyName.toString();
    }

    //獲取輸入參數中某個屬性的值
    private String getInputParamValue(JoinPoint point,String keyName)  {
        Object[] args = point.getArgs();  //獲取切點參數
        Object inputParamData;
        if(args[0] instanceof AbstractInputDto){
            inputParamData = ((AbstractInputDto)args[0]).getData();
            Field field = null;
            try {
                field = inputParamData.getClass().getDeclaredField(keyName);
                //設置對象的訪問權限,保證對private的屬性的訪問
                field.setAccessible(true);
                return String.valueOf(field.get(inputParamData));
            } catch (NoSuchFieldException e) {
                 logger.error(e.getMessage());
            } catch (IllegalAccessException e) {
                logger.error(e.getMessage());
            }
        }
        return null;
    }

    //獲取參數中某個屬性的值
    private String getOutputParamValue(Object result,String keyName)  {
        Object inputParamData;
        if(result instanceof AbstractOutputDto){
            inputParamData = ((AbstractOutputDto)result).getData();
            Field field = null;
            try {
                field = inputParamData.getClass().getDeclaredField(keyName);
                //設置對象的訪問權限,保證對private的屬性的訪問
                field.setAccessible(true);
                return String.valueOf(field.get(inputParamData));
            } catch (NoSuchFieldException e) {
                logger.error(e.getMessage());
            } catch (IllegalAccessException e) {
                logger.error(e.getMessage());
            }
        }
        return null;
    }


    private MethodRemark getMethodRemark(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[] method = targetClass.getMethods();
        for (Method m : method) {
            if (m.getName().equals(methodName)) {
                Class[] tmpCs = m.getParameterTypes();
                if (tmpCs.length == arguments.length) {
                    MethodRemark methodCache = m.getAnnotation(MethodRemark.class);
                    if (methodCache != null && !("").equals(methodCache.remark())) {
                        return methodCache;
                    }
                    break;
                }
            }
        }
        return null;
    }

    private HttpServletRequest getRequest(JoinPoint point) {
        Object[] args = point.getArgs();
        for (Object obj : args) {
            if (obj instanceof HttpServletRequest)
                return (HttpServletRequest) obj;
        }
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletRequest request = sra.getRequest();
        return request;
    }

    private String getRequestIP(HttpServletRequest request) {
        String ip = null;
        if (request.getHeader("x-forwarded-for") == null) {
            ip = request.getRemoteAddr();
        }else{
            ip = request.getHeader("x-forwarded-for");
        }
        return ip;
    }

    private String getParam(HttpServletRequest request) {
        Map properties = request.getParameterMap();
        Map returnMap = new HashMap();
        Iterator entries = properties.entrySet().iterator();
        Map.Entry entry;
        String name = "";
        String value = "";
        while (entries.hasNext()) {
            entry = (Map.Entry) entries.next();
            name = (String) entry.getKey();
            Object valueObj = entry.getValue();
            value = null;
            if (null == valueObj) {
                value = "";
            } else if (valueObj instanceof String[]) {
                String[] values = (String[]) valueObj;
                for (int i = 0; i < values.length; i++) {
                    if (value == null)
                        value = (values[i] == null ? "" : values[i]);
                    else
                        value += "," + (values[i] == null ? "" : values[i]);
                }
            } else {
                value = valueObj.toString();
            }
            returnMap.put(name, value);
        }
        return JsonUtils.map2JsonString(returnMap);
    }

    private String getCurrentUser(){
        return SecurityContextHolder.getContext().getAuthentication().getName();
    }
}

第四步:在Controller層的指定方法上添加註解(這裏的表和業務主鍵名稱我都使用公共類封裝起來了)

@MethodRemark(remark = "歸檔canner數據",opType = "4",tableName = CommonConstant.CANNER_TABLE_NAME,
        businessKeyName = CommonConstant.CANNER_BUSINESS_KEY_NAME)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章