摘要:幾乎每一個(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)目的依賴如下。
新建Aop類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
新建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 ""; }
Target和Retention都屬于元注解。共有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í)例中。
isExistisExist的代碼如下。
/** * 判斷該參數(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); } }
ArrayUtil是hutool中的一個(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 MapparamsMap = 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 MapconvertToMap(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再次運(yùn)行*/ 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; }
再次請求上述的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運(yùn)行*/ 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); } } }
啟動(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
摘要:在領(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...
摘要:可以通過傳入待刪除數(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/...
摘要:從使用到原理學(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)階面試問題列表 -...
閱讀 1964·2021-11-19 09:40
閱讀 2148·2021-10-09 09:43
閱讀 3304·2021-09-06 15:00
閱讀 2821·2019-08-29 13:04
閱讀 2776·2019-08-26 11:53
閱讀 3539·2019-08-26 11:46
閱讀 2330·2019-08-26 11:38
閱讀 398·2019-08-26 11:27