成人国产在线小视频_日韩寡妇人妻调教在线播放_色成人www永久在线观看_2018国产精品久久_亚洲欧美高清在线30p_亚洲少妇综合一区_黄色在线播放国产_亚洲另类技巧小说校园_国产主播xx日韩_a级毛片在线免费

資訊專欄INFORMATION COLUMN

手把手教你如何優(yōu)雅的使用Aop記錄帶參數(shù)的復(fù)雜Web接口日志

Loong_T / 2222人閱讀

摘要:幾乎每一個(gè)接口被調(diào)用后,都要記錄一條跟這個(gè)參數(shù)掛鉤的特定的日志到數(shù)據(jù)庫。我最終采用了的方式,采取攔截的請求的方式,來記錄日志。所有打上了這個(gè)注解的方法,將會記錄日志。那么如何從眾多可能的參數(shù)中,為當(dāng)前的日志指定對應(yīng)的參數(shù)呢。

前言

不久前,因?yàn)樾枨蟮脑?,需要?shí)現(xiàn)一個(gè)操作日志。幾乎每一個(gè)接口被調(diào)用后,都要記錄一條跟這個(gè)參數(shù)掛鉤的特定的日志到數(shù)據(jù)庫。舉個(gè)例子,就比如禁言操作,日志中需要記錄因?yàn)槭裁唇?,被禁言的人的id和各種信息。方便后期查詢。

這樣的接口有很多個(gè),而且大部分接口的參數(shù)都不一樣??赡艽蠹液苋菀紫氲降囊粋€(gè)思路就是,實(shí)現(xiàn)一個(gè)日志記錄的工具類,然后在需要記錄日志的接口中,添加一行代碼。由這個(gè)日志工具類去判斷此時(shí)應(yīng)該處理哪些參數(shù)。

但是這樣有很大的問題。如果需要記日志的接口數(shù)量非常多,先不討論這個(gè)工具類中需要做多少的類型判斷,僅僅是給所有接口添加這樣一行代碼在我個(gè)人看來都是不能接受的行為。首先,這樣對代碼的侵入性太大。其次,后期萬一有改動(dòng),維護(hù)的人將會十分難受。想象一下,全局搜索相同的代碼,再一一進(jìn)行修改。

所以我放棄了這個(gè)略顯原始的方法。我最終采用了Aop的方式,采取攔截的請求的方式,來記錄日志。但是即使采用這個(gè)方法,仍然面臨一個(gè)問題,那就是如何處理大量的參數(shù)。以及如何對應(yīng)到每一個(gè)接口上。

我最終沒有攔截所有的controller,而是自定義了一個(gè)日志注解。所有打上了這個(gè)注解的方法,將會記錄日志。同時(shí),注解中會帶有類型,來為當(dāng)前的接口指定特定的日志內(nèi)容以及參數(shù)。

那么如何從眾多可能的參數(shù)中,為當(dāng)前的日志指定對應(yīng)的參數(shù)呢。我的解決方案是維護(hù)一個(gè)參數(shù)類,里面列舉了所有需要記錄在日志中的參數(shù)名。然后在攔截請求時(shí),通過反射,獲取到該請求的request和response中的所有參數(shù)和值,如果該參數(shù)存在于我維護(hù)的param類中,則將對應(yīng)的值賦值進(jìn)去。

然后在請求結(jié)束后,將模板中的所有預(yù)留的參數(shù)全部用賦了值的參數(shù)替換掉。這樣一來,在不大量的侵入業(yè)務(wù)的前提下,滿足了需求,同時(shí)也保證了代碼的可維護(hù)性。

下面我將會把詳細(xì)的實(shí)現(xiàn)過程列舉出來。

開始操作前

文章結(jié)尾我會給出這個(gè)demo項(xiàng)目的所有源碼。所以不想看過程的兄臺可移步到末尾,直接看源碼。(聽說和源碼搭配,看文章更美味...)

開始操作 新建項(xiàng)目

大家可以參考我之前寫的另一篇文章,手把手教你從零開始搭建SpringBoot后端項(xiàng)目框架。只要能請求簡單的接口就可以了。本項(xiàng)目的依賴如下。


    org.springframework.boot
    spring-boot-starter-web
    2.1.1.RELEASE




    org.aspectj
    aspectjrt
    1.9.2




    org.aspectj
    aspectjweaver
    1.9.2




    org.projectlombok
    lombok
    1.18.2



    cn.hutool
    hutool-all
    4.1.14

新建Aop類

新建LogAspect類。代碼如下。

package spring.aop.log.demo.api.util;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * LogAspect
 *
 * @author Lunhao Hu
 * @date 2019-01-30 16:21
 **/
@Aspect
@Component
public class LogAspect {
    /**
     * 定義切入點(diǎn)
     */
    @Pointcut("@annotation(spring.aop.log.demo.api.util.Log)")
    public void operationLog() {
    }
    
    /**
     * 新增結(jié)果返回后觸發(fā)
     *
     * @param point
     * @param returnValue
     */
    @AfterReturning(returning = "returnValue", pointcut = "operationLog() && @annotation(log)")
    public void doAfterReturning(JoinPoint point, Object returnValue, Log log) {
        System.out.println("test");
    }
}

Pointcut中傳入了一個(gè)注解,表示凡是打上了這個(gè)注解的方法,都會觸發(fā)由Pointcut修飾的operationLog函數(shù)。而AfterReturning則是在請求返回之后觸發(fā)。

自定義注解

上一步提到了自定義注解,這個(gè)自定義注解將打在controller的每個(gè)方法上。新建一個(gè)annotation的類。代碼如下。

package spring.aop.log.demo.api.util;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Log
 *
 * @author Lunhao Hu
 * @date 2019-01-30 16:19
 **/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface Log {
    String type() default "";
}

TargetRetention都屬于元注解。共有4種,分別是@Retention、@Target、@Document、@Inherited。

Target注解說明了該Annotation所修飾的范圍??梢詡魅牒芏囝愋停瑓?shù)為ElementType。例如TYPE,用于描述類、接口或者枚舉類;FIELD用于描述屬性;METHOD用于描述方法;PARAMETER用于描述參數(shù);CONSTRUCTOR用于描述構(gòu)造函數(shù);LOCAL_VARIABLE用于描述局部變量;ANNOTATION_TYPE用于描述注解;PACKAGE用于描述包等。

Retention注解定義了該Annotation被保留的時(shí)間長短。參數(shù)為RetentionPolicy。例如SOURCE表示只在源碼中存在,不會在編譯后的class文件存在;CLASS是該注解的默認(rèn)選項(xiàng)。 即存在于源碼,也存在于編譯后的class文件,但不會被加載到虛擬機(jī)中去;RUNTIME存在于源碼、class文件以及虛擬機(jī)中,通俗一點(diǎn)講就是可以在運(yùn)行的時(shí)候通過反射獲取到。

加上普通注解

給需要記錄日志的接口加上Log注解。

package spring.aop.log.demo.api.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import spring.aop.log.demo.api.util.Log;

/**
 * HelloController
 *
 * @author Lunhao Hu
 * @date 2019-01-30 15:52
 **/
@RestController
public class HelloController {
    @Log
    @GetMapping("test/{id}")
    public String test(@PathVariable(name = "id") Integer id) {
        return "Hello" + id;
    }
}

加上之后,每一次調(diào)用test/{id}這個(gè)接口,都會觸發(fā)攔截器中的doAfterReturning方法中的代碼。

加上帶類型注解

上面介紹了記錄普通日志的方法,接下來要介紹記錄特定日志的方法。什么特定日志呢,就是每個(gè)接口要記錄的信息不同。為了實(shí)現(xiàn)這個(gè),我們需要實(shí)現(xiàn)一個(gè)操作類型的枚舉類。代碼如下。

操作類型模板枚舉

新建一個(gè)枚舉類Type。代碼如下。

package spring.aop.log.demo.api.util;

/**
 * Type
 *
 * @author Lunhao Hu
 * @date 2019-01-30 17:12
 **/
public enum Type {
    /**
     * 操作類型
     */
    WARNING("警告", "因被其他玩家舉報(bào),警告玩家");

    /**
     * 類型
     */
    private String type;

    /**
     * 執(zhí)行操作
     */
    private String operation;

    Type(String type, String operation) {
        this.type = type;
        this.operation = operation;
    }

    public String getType() { return type; }

    public String getOperation() { return operation; }
}
給注解加上類型

給上面的controller中的注解加上type。代碼如下。

package spring.aop.log.demo.api.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import spring.aop.log.demo.api.util.Log;

/**
 * HelloController
 *
 * @author Lunhao Hu
 * @date 2019-01-30 15:52
 **/
@RestController
public class HelloController {
    @Log(type = "WARNING")
    @GetMapping("test/{id}")
    public String test(@PathVariable(name = "id") Integer id) {
        return "Hello" + id;
    }
}
修改aop類

將aop類中的doAfterReturning為如下。

@AfterReturning(returning = "returnValue", pointcut = "operationLog() && @annotation(log)")
public void doAfterReturning(JoinPoint point, Object returnValue, Log log) {
    // 注解中的類型
    String enumKey = log.type();
    System.out.println(Type.valueOf(enumKey).getOperation());
}

加上之后,每一次調(diào)用加了@Log(type = "WARNING")這個(gè)注解的接口,都會打印這個(gè)接口所指定的日志。例如上述代碼就會打印出如下代碼。

因被其他玩家舉報(bào),警告玩家
獲取aop攔截的請求參數(shù)

為每個(gè)接口指定一個(gè)日志并不困難,只需要為每個(gè)接口指定一個(gè)類型即可。但是大家應(yīng)該也注意到了,一個(gè)接口日志,只記錄因被其他玩家舉報(bào),警告玩家這樣的信息沒有任何意義。

記錄日志的人倒不覺得,而最后去查看日志的人就要吾日三省吾身了,被誰舉報(bào)了?因?yàn)槭裁磁e報(bào)了?我警告的誰?

這樣的日志做了太多的無用功,根本沒有辦法在出現(xiàn)問題之后溯源。所以我們下一步的操作就是給每個(gè)接口加上特定的參數(shù)。那么大家可能會有問題,如果每個(gè)接口的參數(shù)幾乎都不一樣,那這個(gè)工具類豈不是要傳入很多參數(shù),要怎么實(shí)現(xiàn)呢,甚至還要組織參數(shù),這樣會大量的侵入業(yè)務(wù)代碼,并且會大量的增加冗余代碼。

大家可能會想到,實(shí)現(xiàn)一個(gè)記錄日志的方法,在要記日志的接口中調(diào)用,把參數(shù)傳進(jìn)去。如果類型很多的話,參數(shù)也會隨之增多,每個(gè)接口的參數(shù)都不一樣。處理起來十分麻煩,而且對業(yè)務(wù)的侵入性太高。幾乎每個(gè)地方都要嵌入日志相關(guān)代碼。一旦涉及到修改,將會變得十分難維護(hù)。

所以我直接利用反射獲取aop攔截到的請求中的所有參數(shù),如果我的參數(shù)類(所有要記錄的參數(shù))里面有請求中的參數(shù),那么我就將參數(shù)的值寫入?yún)?shù)類中。最后將日志模版中參數(shù)預(yù)留字段替換成請求中的參數(shù)。

流程圖如下所示。

新建參數(shù)類

新建一個(gè)類Param,其中包含所有在操作日志中,可能會出現(xiàn)的參數(shù)。為什么要這么做?因?yàn)槊總€(gè)接口需要的參數(shù)都有可能完全不一樣,與其去維護(hù)大量的判斷邏輯,還不如貪心一點(diǎn),直接傳入所有的可能參數(shù)。當(dāng)然后期如果有新的參數(shù)需要記錄,則需要修改代碼。

package spring.aop.log.demo.api.util;

import lombok.Data;

/**
 * Param
 *
 * @author Lunhao Hu
 * @date 2019-01-30 17:14
 **/
@Data
public class Param {
    /**
     * 所有可能參數(shù)
     */
    private String id;
    private String workOrderNumber;
    private String userId;
}
修改模板

將模板枚舉類中的WARNING修改為如下。

WARNING("警告", "因 工單號 [(%workOrderNumber)] /舉報(bào) ID [(%id)] 警告玩家 [(%userId)]");

其中的參數(shù),就是要在aop攔截階段獲取并且替換掉的參數(shù)。

修改controller

我們給之前的controller加上上述模板中國呢的參數(shù)。部分代碼如下。

@Log(type = "WARNING")
@GetMapping("test/{id}")
public String test(
        @PathVariable(name = "id") Integer id,
        @RequestParam(name = "workOrderNumber") String workOrderNumber,
        @RequestParam(name = "userId") String userId,
        @RequestParam(name = "name") String name
) {
    return "Hello" + id;
}
通過反射獲取請求的參數(shù)

在此處分兩種情況,一種是簡單參數(shù)類型,另外一種是復(fù)雜參數(shù)類型,也就是參數(shù)中帶了請求DTO的情況。

獲取簡單參數(shù)類型

給aop類添加幾個(gè)私有變量。

/**
 * 請求中的所有參數(shù)
 */
private Object[] args;

/**
 * 請求中的所有參數(shù)名
 */
private String[] paramNames;

/**
 * 參數(shù)類
 */
private Param params;

然后將doAfterReturning中的代碼改成如下。

try {
    // 獲取請求詳情
    ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
    HttpServletRequest request = attributes.getRequest();
    HttpServletResponse response = attributes.getResponse();
    // 獲取所有請求參數(shù)
    Signature signature = point.getSignature();
    MethodSignature methodSignature = (MethodSignature) signature;
    this.paramNames = methodSignature.getParameterNames();
    this.args = point.getArgs();

    // 實(shí)例化參數(shù)類
    this.params = new Param();
    // 注解中的類型
    String enumKey = log.type();
    String logDetail = Type.valueOf(enumKey).getOperation();

    // 從請求傳入?yún)?shù)中獲取數(shù)據(jù)
    this.getRequestParam();
} catch (Exception e) {
    System.out.println(e.getMessage());
}

首先要做的就是攔截打上了自定義注解的請求。我們可以獲取到請求的詳情,以及請求中的所有的參數(shù)名,以及參數(shù)。下面我們就來實(shí)現(xiàn)上述代碼中的getRequestParam方法。

getRequestParam
/**
 * 獲取攔截的請求中的參數(shù)
 * @param point
 */
private void getRequestParam() {
    // 獲取簡單參數(shù)類型
    this.getSimpleParam();
}
getSimpleParam
/**
 * 獲取簡單參數(shù)類型的值
 */
private void getSimpleParam() {
    // 遍歷請求中的參數(shù)名
    for (String reqParam : this.paramNames) {
        // 判斷該參數(shù)在參數(shù)類中是否存在
        if (this.isExist(reqParam)) {
            this.setRequestParamValueIntoParam(reqParam);
        }
    }
}

上述代碼中,遍歷請求所傳入的參數(shù)名,然后我們實(shí)現(xiàn)isExist方法, 來判斷這個(gè)參數(shù)在我們的Param類中是否存在,如果存在我們就再調(diào)用setRequestParamValueIntoParam方法,將這個(gè)參數(shù)名所對應(yīng)的參數(shù)值寫入到Param類的實(shí)例中。

isExist

isExist的代碼如下。

/**
 * 判斷該參數(shù)在參數(shù)類中是否存在(是否是需要記錄的參數(shù))
 * @param targetClass
 * @param name
 * @param 
 * @return
 */
private  Boolean isExist(String name) {
    boolean exist = true;
    try {
        String key = this.setFirstLetterUpperCase(name);
        Method targetClassGetMethod = this.params.getClass().getMethod("get" + key);
    } catch (NoSuchMethodException e) {
        exist = false;
    }
    return exist;
}

在上面我們也提到過,在編譯的時(shí)候會加上getter和setter,所以參數(shù)名的首字母都會變成大寫,所以我們需要自己實(shí)現(xiàn)一個(gè)setFirstLetterUpperCase方法,來將我們傳入的參數(shù)名的首字母變成大寫。

setFirstLetterUpperCase

代碼如下。

/**
 * 將字符串的首字母大寫
 *
 * @param str
 * @return
 */
private String setFirstLetterUpperCase(String str) {
    if (str == null) {
        return null;
    }
    return str.substring(0, 1).toUpperCase() + str.substring(1);
}
setRequestParamValueIntoParam

代碼如下。

/**
 * 從參數(shù)中獲取
 * @param paramName
 * @return
 */
private void setRequestParamValueIntoParam(String paramName) {
    int index = ArrayUtil.indexOf(this.paramNames, paramName);
    if (index != -1) {
        String value = String.valueOf(this.args[index]);
        this.setParam(this.params, paramName, value);
    }
}

ArrayUtilhutool中的一個(gè)工具函數(shù)。用來判斷在一個(gè)元素在數(shù)組中的下標(biāo)。

setParam

代碼如下。

/**
 * 將數(shù)據(jù)寫入?yún)?shù)類的實(shí)例中
 * @param targetClass
 * @param key
 * @param value
 * @param 
 */
private  void setParam(T targetClass, String key, String value) {
    try {
        Method targetClassParamSetMethod = targetClass.getClass().getMethod("set" + this.setFirstLetterUpperCase(key), String.class);
        targetClassParamSetMethod.invoke(targetClass, value);
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
}

該函數(shù)使用反射的方法,獲取該參數(shù)的set方法,將Param類中對應(yīng)的參數(shù)設(shè)置成傳入的值。

運(yùn)行

啟動(dòng)項(xiàng)目,并且請求controller中的方法。并且傳入定義好的參數(shù)。

http://localhost:8080/test/8?workOrderNumber=3231732&userId=748327843&name=testName

GET請求總共傳入了4個(gè)參數(shù),分別是id,workOrderNumber,userId, name。大家可以看到,在Param類中并沒有定義name這個(gè)字段。這是特意加了一個(gè)不需要記錄的參數(shù),來驗(yàn)證我們接口的健壯性的。

運(yùn)行之后,可以看到控制臺打印的信息如下。

Param(id=8, workOrderNumber=3231732, userId=748327843)

我們想讓aop記錄的參數(shù)全部記錄到Param類中的實(shí)例中,而傳入了意料之外的參數(shù)也沒有讓程序崩潰。接下里我們只需要將這些參數(shù),將之前定義好的模板的參數(shù)預(yù)留字段替換掉即可。

替換參數(shù)

doAfterReturning中的getRequestParam函數(shù)后,加入以下代碼。

if (!logDetail.isEmpty()) {
    // 將模板中的參數(shù)全部替換掉
    logDetail = this.replaceParam(logDetail);
}
System.out.println(logDetail);

下面我們實(shí)現(xiàn)replaceParam方法。

replaceParam

代碼如下。

/**
 * 將模板中的預(yù)留字段全部替換為攔截到的參數(shù)
 * @param template
 * @return
 */
private String replaceParam(String template) {
    // 將模板中的需要替換的參數(shù)轉(zhuǎn)化成map
    Map paramsMap = this.convertToMap(template);
    for (String key : paramsMap.keySet()) {
        template = template.replace("%" + key, paramsMap.get(key)).replace("(", "").replace(")", "");
    }
    return template;
}

convertToMap方法將模板中的所有預(yù)留字段全部提取出來,當(dāng)作一個(gè)Map的Key。

convertToMap

代碼如下。

/**
 * 將模板中的參數(shù)轉(zhuǎn)換成map的key-value形式
 * @param template
 * @return
 */
private Map convertToMap(String template) {
    Map map = new HashMap<>();
    String[] arr = template.split("(");
    for (String s : arr) {
        if (s.contains("%")) {
            String key = s.substring(s.indexOf("%"), s.indexOf(")")).replace("%", "").replace(")", "").replace("-", "").replace("]", "");
            String value = this.getParam(this.params, key);
            map.put(key, "null".equals(value) ? "(空)" : value);
        }
    }
    return map;
}

其中的getParam方法,類似于setParam,也是利用反射的方法,通過傳入的Class和Key,獲取對應(yīng)的值。

getParam

代碼如下。

/**
 * 通過反射獲取傳入的類中對應(yīng)key的值
 * @param targetClass
 * @param key
 * @param 
 */
private  String getParam(T targetClass, String key) {
    String value = "";
    try {
        Method targetClassParamGetMethod = targetClass.getClass().getMethod("get" + this.setFirstLetterUpperCase(key));
        value = String.valueOf(targetClassParamGetMethod.invoke(targetClass));
    } catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
        e.printStackTrace();
    }
    return value;
}
再次運(yùn)行

再次請求上述的url,則可以看到控制臺的輸出如下。

因 工單號 [3231732] /舉報(bào) ID [8] 警告玩家 [748327843]

可以看到,我們需要記錄的所有的參數(shù),都被正確的替換了。而不需要記錄的參數(shù),同樣也沒有對程序造成影響。

讓我們試試傳入不傳入非必選參數(shù),會是什么樣。修改controller如下,把workOrderNumber改成非必須按參數(shù)。

@Log(type = "WARNING")
@GetMapping("test/{id}")
public String test(
        @PathVariable(name = "id") Integer id,
        @RequestParam(name = "workOrderNumber", required = false) String workOrderNumber,
        @RequestParam(name = "userId") String userId,
        @RequestParam(name = "name") String name
) {
    return "Hello" + id;
}

請求如下url。

http://localhost:8080/test/8?userId=748327843&name=testName

然后可以看到,控制臺的輸出如下。

因 工單號 [空] /舉報(bào) ID [8] 警告玩家 [748327843]

并不會影響程序的正常運(yùn)行。

獲取復(fù)雜參數(shù)類型

接下來要介紹的是如何記錄復(fù)雜參數(shù)類型的日志。其實(shí),大致的思路是不變的。我們看傳入的類中的參數(shù),有沒有需要記錄的。有的話就按照上面記錄簡單參數(shù)的方法來替換記錄參數(shù)。

定義測試復(fù)雜類型

新建TestDTO。代碼如下。

package spring.aop.log.demo.api.util;

import lombok.Data;

/**
 * TestDto
 *
 * @author Lunhao Hu
 * @date 2019-02-01 15:02
 **/
@Data
public class TestDTO {
    private String name;
    
    private Integer age;

    private String email;
}
修改Param

將上面的所有的參數(shù)全部添加到Param類中,全部定義成字符串類型。

package spring.aop.log.demo.api.util;

import lombok.Data;

/**
 * Param
 *
 * @author Lunhao Hu
 * @date 2019-01-30 17:14
 **/
@Data
public class Param {
    /**
     * 所有可能參數(shù)
     */
    private String id;
    private String age;
    private String workOrderNumber;
    private String userId;
    private String name;
    private String email;
}
修改模板

WARNING模板修改如下。

/**
 * 操作類型
 */
WARNING("警告", "因 工單號 [(%workOrderNumber)] /舉報(bào) ID [(%id)] 警告玩家 [(%userId)], 游戲名 [(%name)], 年齡 [(%age)]");
修改controller
@Log(type = "WARNING")
@PostMapping("test/{id}")
public String test(
        @PathVariable(name = "id") Integer id,
        @RequestParam(name = "workOrderNumber", required = false) String workOrderNumber,
        @RequestParam(name = "userId") String userId,
        @RequestBody TestDTO testDTO
) {
    return "Hello" + id;
}
修改getRequestParam
/**
 * 獲取攔截的請求中的參數(shù)
 * @param point
 */
private void getRequestParam() {
    // 獲取簡單參數(shù)類型
    this.getSimpleParam();

    // 獲取復(fù)雜參數(shù)類型
    this.getComplexParam();
}

接下來實(shí)現(xiàn)getComplexParam方法。

getComplexParam
/**
 * 獲取復(fù)雜參數(shù)類型的值
 */
private void getComplexParam() {
    for (Object arg : this.args) {
        // 跳過簡單類型的值
        if (arg != null && !this.isBasicType(arg)) {
           this.getFieldsParam(arg);
        }
    }
}
getFieldsParam
/**
 * 遍歷一個(gè)復(fù)雜類型,獲取值并賦值給param
 * @param target
 * @param 
 */
private  void getFieldsParam(T target) {
    Field[] fields = target.getClass().getDeclaredFields();
    for (Field field : fields) {
        String paramName = field.getName();
        if (this.isExist(paramName)) {
            String value = this.getParam(target, paramName);
            this.setParam(this.params, paramName, value);
        }
    }
}
運(yùn)行

啟動(dòng)項(xiàng)目。使用postman對上面的url發(fā)起POST請求。請求body中帶上TestDTO中的參數(shù)。請求成功返回后就會看到控制臺輸出如下。

因 工單號 [空] /舉報(bào) ID [8] 警告玩家 [748327843], 游戲名 [tom], 年齡 [12]

然后就可以根據(jù)需求,將上面的日志記錄到相應(yīng)的地方。

到這可能有些哥們就覺得行了,萬事具備,只欠東風(fēng)。但其實(shí)這樣的實(shí)現(xiàn)方式,還存在幾個(gè)問題。

比如,如果請求失敗了怎么辦?請求失敗,在需求上將,是根本不需要記錄操作日志的,但是即使請求失敗也會有返回值,就代表日志也會成功的記錄。這就給后期查看日志帶來了很大的困擾。

再比如,如果我需要的參數(shù)在返回值中怎么辦?如果你沒有用統(tǒng)一的生成唯一id的服務(wù),就會遇到這個(gè)問題。就比如我需要往數(shù)據(jù)庫中插入一條新的數(shù)據(jù),我需要得到數(shù)據(jù)庫自增id,而我們的日志攔截只攔截了請求中的參數(shù)。所以這就是我們接下來要解決的問題。

判斷請求是否成功

實(shí)現(xiàn)success函數(shù),代碼如下。

/**
 * 根據(jù)http狀態(tài)碼判斷請求是否成功
 *
 * @param response
 * @return
 */
private Boolean success(HttpServletResponse response) {
    return response.getStatus() == 200;
}

然后將getRequestParam之后的所有操作,包括getRequestParam本身,用success包裹起來。如下。

if (this.success(response)) {
    // 從請求傳入?yún)?shù)中獲取數(shù)據(jù)
    this.getRequestParam();
    if (!logDetail.isEmpty()) {
        // 將模板中的參數(shù)全部替換掉
        logDetail = this.replaceParam(logDetail);
    }
}

這樣一來,就可以保證只有在請求成功的前提下,才會記錄日志。

通過反射獲取返回的參數(shù) 新建Result類

在一個(gè)項(xiàng)目中,我們用一個(gè)類來統(tǒng)一返回值。

package spring.aop.log.demo.api.util;

import lombok.Data;

/**
 * Result
 *
 * @author Lunhao Hu
 * @date 2019-02-01 16:47
 **/
@Data
public class Result {
    private Integer id;

    private String name;

    private Integer age;

    private String email;
}
修改controller
@Log(type = "WARNING")
@PostMapping("test")
public Result test(
        @RequestParam(name = "workOrderNumber", required = false) String workOrderNumber,
        @RequestParam(name = "userId") String userId,
        @RequestBody TestDTO testDTO
) {
    Result result = new Result();
    result.setId(1);
    result.setAge(testDTO.getAge());
    result.setName(testDTO.getName());
    result.setEmail(testDTO.getEmail());
    return result;
}
運(yùn)行

啟動(dòng)項(xiàng)目,發(fā)起POST請求會發(fā)現(xiàn),返回值如下。

{
    "id": 1,
    "name": "tom",
    "age": 12,
    "email": "[email protected]"
}

而控制臺的輸出如下。

因 工單號 [39424] /舉報(bào) ID [空] 警告玩家 [748327843], 游戲名 [tom], 年齡 [12]

可以看到,id沒有被獲取到。所以我們還需要添加一個(gè)函數(shù),從返回值中獲取id的數(shù)據(jù)。

getResponseParam

getRequestParam后,添加方法getResponseParam,直接調(diào)用之前寫好的函數(shù)。代碼如下。

/**
 * 從返回值從獲取數(shù)據(jù)
 */
private void getResponseParam(Object value) {
    this.getFieldsParam(value);
}
運(yùn)行

再次發(fā)起POST請求,可以發(fā)現(xiàn)控制臺的輸出如下。

因 工單號 [39424] /舉報(bào) ID [1] 警告玩家 [748327843], 游戲名 [tom], 年齡 [12]

一旦得到了這條信息,我們就可以把它記錄到任何我們想記錄的地方。

項(xiàng)目源碼地址

想要參考源碼的大佬請戳 ->這里<-

文章版權(quán)歸作者所有,未經(jīng)允許請勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。

轉(zhuǎn)載請注明本文地址:http://systransis.cn/yun/77415.html

相關(guān)文章

  • Java后端

    摘要:,面向切面編程,中最主要的是用于事務(wù)方面的使用。目標(biāo)達(dá)成后還會有去構(gòu)建微服務(wù),希望大家多多支持。原文地址手把手教程優(yōu)雅的應(yīng)用四手把手實(shí)現(xiàn)后端搭建第四期 SpringMVC 干貨系列:從零搭建 SpringMVC+mybatis(四):Spring 兩大核心之 AOP 學(xué)習(xí) | 掘金技術(shù)征文 原本地址:SpringMVC 干貨系列:從零搭建 SpringMVC+mybatis(四):Sp...

    joyvw 評論0 收藏0
  • 把手0基礎(chǔ)項(xiàng)目實(shí)戰(zhàn)(三)——教你開發(fā)一套電商平臺安全框架

    摘要:在領(lǐng)域,有兩大主流的安全框架,和。角色角色是一組權(quán)限的集合。安全框架的實(shí)現(xiàn)注解的實(shí)現(xiàn)本套安全框架一共定義了四個(gè)注解。該注解用來告訴安全框架,本項(xiàng)目中所有類所在的包,從而能夠幫助安全框架快速找到類,避免了所有類的掃描。 寫在最前 本文是《手把手項(xiàng)目實(shí)戰(zhàn)系列》的第三篇文章,預(yù)告一下,整個(gè)系列會介紹如下內(nèi)容: 《手把手0基礎(chǔ)項(xiàng)目實(shí)戰(zhàn)(一)——教你搭建一套可自動(dòng)化構(gòu)建的微服務(wù)框架(Sprin...

    RaoMeng 評論0 收藏0
  • 把手教你登錄NodeJS爬蟲+數(shù)據(jù)展示

    摘要:可以通過傳入待刪除數(shù)組元素組成的數(shù)組進(jìn)行一次性刪除。如果后臺返回的為表示登錄的已失效,需要重新執(zhí)行。等所有的異步執(zhí)行完畢后,再執(zhí)行回調(diào)函數(shù)。回調(diào)函數(shù)的參數(shù)是每個(gè)函數(shù)返回?cái)?shù)據(jù)組成的數(shù)組。 其實(shí)在早之前,就做過立馬理財(cái)?shù)匿N售額統(tǒng)計(jì),只不過是用前端js寫的,需要在首頁的console調(diào)試面板里粘貼一段代碼執(zhí)行,點(diǎn)擊這里。主要是通過定時(shí)爬取https://www.lmlc.com/s/web/...

    cpupro 評論0 收藏0
  • Java深入-框架技巧

    摘要:從使用到原理學(xué)習(xí)線程池關(guān)于線程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實(shí)現(xiàn)在軟件開發(fā)中,分散于應(yīng)用中多出的功能被稱為橫切關(guān)注點(diǎn)如事務(wù)安全緩存等。 Java 程序媛手把手教你設(shè)計(jì)模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經(jīng)風(fēng)雨慢慢變老,回首走過的點(diǎn)點(diǎn)滴滴,依然清楚的記得當(dāng)初愛情萌芽的模樣…… Java 進(jìn)階面試問題列表 -...

    chengtao1633 評論0 收藏0

發(fā)表評論

0條評論

最新活動(dòng)
閱讀需要支付1元查看
<