springboot利用AOP完成日志統(tǒng)計的詳細步驟_第1頁
springboot利用AOP完成日志統(tǒng)計的詳細步驟_第2頁
springboot利用AOP完成日志統(tǒng)計的詳細步驟_第3頁
springboot利用AOP完成日志統(tǒng)計的詳細步驟_第4頁
springboot利用AOP完成日志統(tǒng)計的詳細步驟_第5頁
已閱讀5頁,還剩15頁未讀 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

第springboot利用AOP完成日志統(tǒng)計的詳細步驟目錄1、創(chuàng)建日志表2、創(chuàng)建實體類3、創(chuàng)建枚舉類4、創(chuàng)建自定義注解5、獲取ip的util6、線程池util7、HttpServletRequest實現(xiàn)類8、添加過濾器9、添加AOP核心類10、接口測試步驟寫的很詳細,可以直接復制拿來用的,其中用到了過濾器、自定義注解以及AOP切面,來完成日志記錄統(tǒng)計,感興趣的收藏起來,以后遇到了可以直接用。

可能步驟會比較多,但是整體跟著思路下來,應該沒什么大問題的。

項目用到了過濾器,可能有的人會不理解,之所以用過濾器是因為想要在日志記錄post請求的json數(shù)據(jù)。

請求的時候,是通過request的body來傳輸?shù)?。在AOP后置方法中獲取request里面的body,是取不到,直接為空。

原因很簡單:因為是流。想想看,java中的流也是只能讀一次,因為我是在AOP后置方法獲取的,控制器實際上已經(jīng)讀過了一次,后置方法再讀自然為空了。所以用過濾器來進行解決了這個問題。

1、創(chuàng)建日志表

這里我用的是mysql,假如您用的別的數(shù)據(jù)庫,可以自行根據(jù)數(shù)據(jù)庫類型進行修改。

CREATETABLE`log`(

`id`varchar(64)CHARACTERSETutf8COLLATEutf8_general_ciNOTNULLCOMMENT'主鍵',

`create_by`varchar(64)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULLCOMMENT'創(chuàng)建人',

`create_time`datetimeNULLDEFAULTNULLCOMMENT'創(chuàng)建時間',

`update_by`varchar(64)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULLCOMMENT'最近更新時間',

`update_time`datetimeNULLDEFAULTNULLCOMMENT'最近更新人',

`update_count`int(11)NULLDEFAULTNULLCOMMENT'更新次數(shù)',

`delete_flag`char(1)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULLCOMMENT'刪除標志',

`delete_time`datetimeNULLDEFAULTNULLCOMMENT'刪除日期',

`delete_by`varchar(64)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULLCOMMENT'刪除人',

`cost_time`int(11)NULLDEFAULTNULLCOMMENT'花費時間',

`ip`varchar(64)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULLCOMMENT'ip',

`description`varchar(255)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULLCOMMENT'日志描述',

`request_param`longtextCHARACTERSETutf8COLLATEutf8_general_ciNULLCOMMENT'請求參數(shù)',

`request_json`longtextCHARACTERSETutf8COLLATEutf8_general_ciNULLCOMMENT'請求json數(shù)據(jù)',

`request_type`varchar(16)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULLCOMMENT'請求類型',

`request_url`varchar(36)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULLCOMMENT'請求路徑',

`username`varchar(64)CHARACTERSETutf8COLLATEutf8_general_ciNULLDEFAULTNULLCOMMENT'請求用戶',

`operation_type`int(3)NULLDEFAULTNULLCOMMENT'操作類型',

PRIMARYKEY(`id`)USINGBTREE

)ENGINE=InnoDBCHARACTERSET=utf8COLLATE=utf8_general_ciROW_FORMAT=Dynamic;

2、創(chuàng)建實體類

我的項目運用到了mybatisplus、swagger、lombok,你們可以根據(jù)自己項目框架寫對應的實體類。BaseModel是我們封裝了一個基礎實體類,專門存放關于操作人的信息,然后實體類直接繼承。

importcom.baomidou.mybatisplus.annotation.TableField;

importcom.baomidou.mybatisplus.annotation.TableName;

importio.swagger.annotations.ApiModelProperty;

import.xaas.mybatis.model.BaseModel;

importlombok.Data;

importlombok.ToString;

@TableName(value="log")

@Data

@ToString(callSuper=true)

publicclassLogextendsBaseModel{

@ApiModelProperty(value="花費時間")

@TableField(value="cost_time")

privateIntegercostTime;

@ApiModelProperty(value="ip")

@TableField(value="ip")

privateStringip;

@ApiModelProperty(value="日志描述")

@TableField(value="description")

privateStringdescription;

@ApiModelProperty(value="請求參數(shù)")

@TableField(value="request_param")

privateStringrequestParam;

@ApiModelProperty(value="請求json數(shù)據(jù)")

@TableField(value="request_json")

privateStringrequestJson;

@ApiModelProperty(value="請求類型")

@TableField(value="request_type")

privateStringrequestType;

@ApiModelProperty(value="請求路徑")

@TableField(value="request_url")

privateStringrequestUrl;

@ApiModelProperty(value="請求用戶")

@TableField(value="username")

privateStringusername;

@ApiModelProperty(value="操作類型")

@TableField(value="operation_type")

privateIntegeroperationType;

3、創(chuàng)建枚舉類

用來記錄日志操作類型

publicenumOperationType{

*操作類型

UNKNOWN("unknown"),

DELETE("delete"),

SELECT("select"),

UPDATE("update"),

INSERT("insert");

OperationType(Strings){

this.value=s;

privateStringvalue;

publicStringgetValue(){

returnvalue;

publicvoidsetValue(Stringvalue){

this.value=value;

4、創(chuàng)建自定義注解

importjava.lang.annotation.*;

@Target({ElementType.PARAMETER,ElementType.METHOD})//作用于參數(shù)或方法上

@Retention(RetentionPolicy.RUNTIME)

@Documented

public@interfaceSystemLog{

*日志名稱

*@return

Stringdescription()default"";

*操作類型

*@return

OperationTypetype()defaultOperationType.UNKNOWN;

5、獲取ip的util

importlombok.extern.slf4j.Slf4j;

importorg.springframework.stereotype.Component;

importjavax.servlet.http.HttpServletRequest;

import.InetAddress;

import.UnknownHostException;

@Slf4j

@Component

publicclassIpInfoUtil{

*獲取客戶端IP地址

*@paramrequest請求

*@return

publicStringgetIpAddr(HttpServletRequestrequest){

Stringip=request.getHeader("x-forwarded-for");

if(ip==null||ip.length()==0||"unknown".equalsIgnoreCase(ip)){

ip=request.getHeader("Proxy-Client-IP");

if(ip==null||ip.length()==0||"unknown".equalsIgnoreCase(ip)){

ip=request.getHeader("WL-Proxy-Client-IP");

if(ip==null||ip.length()==0||"unknown".equalsIgnoreCase(ip)){

ip=request.getRemoteAddr();

if("".equals(ip)){

//根據(jù)網(wǎng)卡取本機配置的IP

InetAddressinet=null;

try{

inet=InetAddress.getLocalHost();

}catch(UnknownHostExceptione){

e.printStackTrace();

ip=inet.getHostAddress();

//對于通過多個代理的情況,第一個IP為客戶端真實IP,多個IP按照','分割

if(ip!=nullip.length()15){

if(ip.indexOf(",")0){

ip=ip.substring(0,ip.indexOf(","));

if("0:0:0:0:0:0:0:1".equals(ip)){

ip="";

returnip;

6、線程池util

利用線程異步記錄日志。所以直接用了一個util維護線程池。

importjava.util.concurrent.ArrayBlockingQueue;

importjava.util.concurrent.BlockingQueue;

importjava.util.concurrent.ThreadPoolExecutor;

importjava.util.concurrent.TimeUnit;

publicclassThreadPoolUtil{

*線程緩沖隊列

privatestaticBlockingQueueRunnablebqueue=newArrayBlockingQueueRunnable(100);

*核心線程數(shù),會一直存活,即使沒有任務,線程池也會維護線程的最少數(shù)量

privatestaticfinalintSIZE_CORE_POOL=5;

*線程池維護線程的最大數(shù)量

privatestaticfinalintSIZE_MAX_POOL=10;

*線程池維護線程所允許的空閑時間

privatestaticfinallongALIVE_TIME=2000;

privatestaticThreadPoolExecutorpool=newThreadPoolExecutor(SIZE_CORE_POOL,SIZE_MAX_POOL,ALIVE_TIME,TimeUnit.MILLISECONDS,bqueue,newThreadPoolExecutor.CallerRunsPolicy());

static{

pool.prestartAllCoreThreads();

publicstaticThreadPoolExecutorgetPool(){

returnpool;

publicstaticvoidmain(String[]args){

System.out.println(pool.getPoolSize());

7、HttpServletRequest實現(xiàn)類

這個就是重寫的一個HttpServletRequest類。

importjavax.servlet.ReadListener;

importjavax.servlet.ServletInputStream;

importjavax.servlet.http.HttpServletRequest;

importjavax.servlet.http.HttpServletRequestWrapper;

importjava.io.*;

publicclassBodyReaderRequestWrapperextendsHttpServletRequestWrapper{

privatefinalStringbody;

*@paramrequest

publicBodyReaderRequestWrapper(HttpServletRequestrequest){

super(request);

StringBuildersb=newStringBuilder();

InputStreamins=null;

BufferedReaderisr=null;

try{

ins=request.getInputStream();

if(ins!=null){

isr=newBufferedReader(newInputStreamReader(ins));

char[]charBuffer=newchar[128];

intreadCount=0;

while((readCount=isr.read(charBuffer))!=-1){

sb.append(charBuffer,0,readCount);

}else{

sb.append("");

}catch(IOExceptione){

e.printStackTrace();

}finally{

try{

if(isr!=null){

isr.close();

}catch(IOExceptione){

e.printStackTrace();

try{

if(ins!=null){

ins.close();

}catch(IOExceptione){

e.printStackTrace();

body=sb.toString();

@Override

publicBufferedReadergetReader()throwsIOException{

returnnewBufferedReader(newInputStreamReader(this.getInputStream()));

@Override

publicServletInputStreamgetInputStream()throwsIOException{

finalByteArrayInputStreambyteArrayIns=newByteArrayInputStream(body.getBytes());

ServletInputStreamservletIns=newServletInputStream(){

@Override

publicbooleanisFinished(){

returnfalse;

@Override

publicbooleanisReady(){

returnfalse;

@Override

publicvoidsetReadListener(ReadListenerreadListener){

@Override

publicintread()throwsIOException{

returnbyteArrayIns.read();

returnservletIns;

8、添加過濾器

這個過濾器我添加了一個路徑,就是代表需要json日志的接口,可以在list當中添加路徑,不需要取request當中json數(shù)據(jù)的可以不配置。

importjavax.servlet.*;

importjavax.servlet.http.HttpServletRequest;

importjavax.servlet.http.HttpServletResponse;

importjava.io.IOException;

importjava.util.ArrayList;

importjava.util.List;

importjava.util.regex.Pattern;

publicclassBodyReaderRequestFilterimplementsFilter{

privatestaticfinalPatternSHOULD_NOT_FILTER_URL_PATTERN;

static{

ListStringurlList=newArrayList();

//想要通過aop記錄request當中body數(shù)據(jù)的,就需要進行配置路徑

urlList.add("(socket/.*)");

urlList.add("(test/test1)");

urlList.add("(test/test2)");

StringBuildersb=newStringBuilder();

for(Stringurl:urlList){

sb.append(url);

sb.append("|");

sb.setLength(sb.length()-1);

SHOULD_NOT_FILTER_URL_PATTERN=Ppile(sb.toString());

@Override

publicvoidinit(FilterConfigfilterConfig){

@Override

publicvoiddoFilter(ServletRequestreq,ServletResponseres,FilterChainfilterChain)throwsIOException,ServletException{

HttpServletRequestrequest=(HttpServletRequest)req;

HttpServletResponseresponse=(HttpServletResponse)res;

//獲取訪問的url

StringservletPath=request.getServletPath();

if(SHOULD_NOT_FILTER_URL_PATTERN.matcher(servletPath).find()){

BodyReaderRequestWrapperrequestWrapper=newBodyReaderRequestWrapper(request);

if(requestWrapper==null){

filterChain.doFilter(request,response);

}else{

filterChain.doFilter(requestWrapper,response);

}else{

filterChain.doFilter(request,response);

@Override

publicvoiddestroy(){

想要讓過濾器生效需要注入到容器當中。

import.bjca.szyx.xaas.equipment.filter.BodyReaderRequestFilter;

importorg.springframework.boot.web.servlet.FilterRegistrationBean;

importorg.springframework.context.annotation.Bean;

importorg.springframework.context.annotation.Configuration;

@Configuration

publicclassMyServerConfig{

@Bean

publicFilterRegistrationBeanmyFilter(){

FilterRegistrationBeanregistrationBean=newFilterRegistrationBean();

registrationBean.setFilter(newBodyReaderRequestFilter());

returnregistrationBean;

9、添加AOP核心類

對于切面,我們可以通過指定包名,進行日志統(tǒng)計,也可以選擇根據(jù)自定義的注解在方法上添加,然后進行統(tǒng)計,根據(jù)自己的實際情況,在切點進行配置即可。

LogDao我是沒有提供的,每個項目框架不一樣,自行根據(jù)情況進行編寫,就是保存數(shù)據(jù)庫就可以了。

importcn.hutool.core.util.IdUtil;

importcn.hutool.json.JSONUtil;

import.xaas.core.util.HeaderSecurityUtils;

import.xaas.equipment.annotation.SystemLog;

import.xaas.equipment.dao.LogDao;

import.xaas.equipment.model.base.Log;

import.xaas.equipment.utils.IpInfoUtil;

import.xaas.equipment.utils.ThreadPoolUtil;

importlombok.extern.slf4j.Slf4j;

importorg.aspectj.lang.JoinPoint;

importorg.aspectj.lang.annotation.AfterReturning;

importorg.aspectj.lang.annotation.Aspect;

importorg.aspectj.lang.annotation.Before;

importorg.aspectj.lang.annotation.Pointcut;

importorg.springframework.beans.factory.annotation.Autowired;

importorg.springframework.core.NamedThreadLocal;

importorg.springframework.stereotype.Component;

importjavax.servlet.ServletInputStream;

importjavax.servlet.http.HttpServletRequest;

importjava.io.BufferedReader;

importjava.io.IOException;

importjava.io.InputStreamReader;

importjava.lang.reflect.Method;

importjava.util.Date;

importjava.util.HashMap;

importjava.util.Map;

@Aspect

@Component

@Slf4j

publicclassSystemLogAspect{

privatestaticfinalThreadLocalDatebeginTimeThreadLocal=newNamedThreadLocalDate("ThreadLocalbeginTime");

@Autowired

privateLogDaologDao;

@Autowired

privateIpInfoUtilipInfoUtil;

@Autowired(required=false)

privateHttpServletRequestrequest;

*Controller層切點,注解方式

//@Pointcut("execution(**..controller..*Controller*.*(..))")

@Pointcut("@annotation(.xaas.equipment.annotation.SystemLog)")

publicvoidcontrollerAspect(){

*前置通知(在方法執(zhí)行之前返回)用于攔截Controller層記錄用戶的操作的開始時間

*@paramjoinPoint切點

*@throwsInterruptedException

@Before("controllerAspect()")

publicvoiddoBefore(JoinPointjoinPoint)throwsInterruptedException{

//線程綁定變量(該數(shù)據(jù)只有當前請求的線程可見)

DatebeginTime=newDate();

beginTimeThreadLocal.set(beginTime);

*后置通知(在方法執(zhí)行之后并返回數(shù)據(jù))用于攔截Controller層無異常的操作

*@paramjoinPoint切點

@AfterReturning("controllerAspect()")

publicvoidafter(JoinPointjoinPoint){

try{

//獲取操作人,每個系統(tǒng)不一樣,一般存儲與session,此處就不展示了

Stringusername=HeaderSecurityUtils.getUserName();

//讀取json數(shù)據(jù)

StringopenApiRequestData=getJSON(request);

MapString,String[]requestParams=request.getParameterMap();

Loglog=newLog();

if(openApiRequestData!=null){

log.setRequestJson(JSONUtil.toJsonStr(openApiRequestData));

log.setId(IdUtil.simpleUUID());

log.setUsername(username);

//日志標題

Stringdescription=getControllerMethodInfo(joinPoint).get("description").toString();

log.setDescription(description);

//日志類型

log.setOperationType((int)getControllerMethodInfo(joinPoint).get("type"));

//日志請求url

log.setRequestUrl(request.getRequestURI());

//請求方式

log.setRequestType(request.getMethod());

//請求參數(shù)

log.setRequestParam(JSONUtil.toJsonStr(requestParams));

//其他屬性

log.setIp(ipInfoUtil.getIpAddr(request));

log.setCreateBy(username);

log.setUpdateBy(username);

log.setCreateTime(newDate());

log.setUpdateTime(newDate());

log.setDeleteFlag("0");

//請求開始時間

longbeginTime=beginTimeThreadLocal.get().getTime();

longendTime=System.currentTimeMillis();

//請求耗時

LonglogElapsedTime=endTime-beginTime;

log.setCostTime(logElapsedTValue());

//持久化(存儲到數(shù)據(jù)或者ES,可以考慮用線程池)

ThreadPoolUtil.getPool().execute(newSaveSystemLogThread(log,logDao));

}catch(Exceptione){

log.error("AOP后置通知異常",e);

*獲取request的body

*@paramrequest

*@return

publicStringgetJSON(HttpServletRequestrequest){

ServletInputStreaminputStream=null;

InputStreamReaderinputStreamReader=null;

BufferedReaderstreamReader=null;

StringBuilderresponseStrBuilder=newStringBuilder();

try{

inputStream=request.getInputStream();

inputStreamReader=newInputStreamReader(inputStream,"UTF-8");

streamReader=newBufferedReader(inputStreamReader);

StringinputStr;

while((inputStr=streamReader.readLine())!=null){

responseStrBuilder.append(inputStr);

}catch(IOExceptionioException){

ioException.printStackTrace();

}finally{

try{

if(inputStream!=null){

inputStream.close();

}catch(IOExceptione){

e.printStackTrace();

try{

if(inputStreamReader!=null){

inputStreamReader.close();

}catch(IOExceptione){

e.printStackTrace();

try{

if(streamReader!=null){

streamReader.close();

}catch(IOExceptione){

e.printStackTrace();

returnresponseStrBuilder.toString();

*保存日志至數(shù)據(jù)庫

privatestaticclassSaveSystemLogThreadimplementsRunnable{

privateLoglog;

pri

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

評論

0/150

提交評論