欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

使用SpringAOP獲取用戶操作日志入庫(kù)

 更新時(shí)間:2021年11月08日 14:38:48   作者:SenKnight  
這篇文章主要介紹了使用SpringAOP獲取用戶操作日志入庫(kù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教

SpringAOP獲取用戶操作日志入庫(kù)

切service層中所有的方法,將有自定義注解的方法的操作日志入庫(kù),其中需要注意的幾點(diǎn):

  • 注意aspectjweaver.jar包的版本,一般要1.6以上版本,否則會(huì)報(bào)錯(cuò)
  • 注意是否使用了雙重代理,spring.xml中不需要配置切面類的<bean>,否則會(huì)出現(xiàn)切兩次的情況
  • 注意返回的數(shù)據(jù)類型,如果是實(shí)體類需要獲取實(shí)體類中每個(gè)屬性的值,若該實(shí)體類中的某個(gè)屬性也是實(shí)體類,需要再次循環(huán)獲取該屬性的實(shí)體類屬性
  • 用遞歸的方法獲得參數(shù)及參數(shù)內(nèi)容
package awb.aweb_soa.service.userOperationLog; 
import java.io.IOException;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.List; 
import javax.servlet.http.HttpServletRequest;
import javax.sql.rowset.serial.SerialBlob;
 
import org.apache.commons.lang.WordUtils;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.AfterThrowing;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
 
import cn.com.agree.aweb.asapi.ASAPI;
import edm.aweb_soa.aweb_soa.base.user.UserOperationLogDO;
import awb.aweb_soa.aservice.app.DefaultUser;
import awb.aweb_soa.global.annotation.UserOperationType;
  
@Service
@Aspect
public class UserOperationLogAspect {
	@Autowired
	UserOperationLog userOperationLog;
	/**
	 * 業(yè)務(wù)邏輯方法切入點(diǎn),切所有service層的方法
	 */
	@Pointcut("execution(* awb.aweb_soa.service..*(..))")
	public void serviceCall() {
 
	}
	/**
	 * 用戶登錄
	 */
	@Pointcut("execution(* awb.aweb_soa.aservice.app.LoginController.signIn(..))")
	public void logInCall() {
 
	}
	/**
	 * 退出登出切入點(diǎn)
	 */
	@Pointcut("execution(* awb.aweb_soa.aservice.app.DefaultUser.logout(..))")
	public void logOutCall() {
 
	}
 
	/**
	 * 操作日志(后置通知)
	 * 
	 * @param joinPoint
	 * @param rtv
	 * @throws Throwable
	 */
	@AfterReturning(value = "serviceCall()", argNames = "rtv", returning = "rtv")
	public void doAfterReturning(JoinPoint joinPoint, Object rtv) throws Throwable {
		operationCall(joinPoint, rtv,"S");
	}
	/**
	 * 用戶登錄(后置通知)
	 * 
	 * @param joinPoint
	 * @param rtv
	 * @throws Throwable
	 */
	@AfterReturning(value = "logInCall()", argNames = "rtv", returning = "rtv")
	public void doLoginReturning(JoinPoint joinPoint, Object rtv) throws Throwable {
		operationCall(joinPoint, rtv,"S");
	}
	
	@Before(value = "logOutCall()")
	public void logoutCalls(JoinPoint joinPoint) throws Throwable {
		operationCall(joinPoint, null,"S");
	}
	/**
	 * 操作日志(異常通知)
	 * 
	 * @param joinPoint
	 * @param e
	 * @throws Throwable
	 */
	@AfterThrowing(value = "serviceCall()", throwing="e")
	public void doAfterThrowing(JoinPoint joinPoint, Object e) throws Throwable {
		operationCall(joinPoint, e,"F");
	}	
	/**
	 * 獲取用戶操作日志詳細(xì)信息
	 * 
	 * @param joinPoint
	 * @param rtv
	 * @param status
	 * @throws Throwable
	 */
	private void operationCall(JoinPoint joinPoint, Object rtv,String status)
			throws Throwable {
		//獲取當(dāng)前用戶
		DefaultUser currentUser = (DefaultUser) ASAPI.authenticator().getCurrentUser();
		String userName = null;
		if (currentUser != null) {
			//獲取用戶名
			userName = currentUser.getUsername();
			//獲取用戶ip地址
			HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder
					.getRequestAttributes()).getRequest();
			String userIp = getIpAddress(request);
 
			// 拼接操作內(nèi)容的字符串
			StringBuffer rs = new StringBuffer();
 
			// 獲取類名
			String className = joinPoint.getTarget().getClass()
					.getCanonicalName();
			rs.append("類名:" + className + "; </br>");
 
			// 獲取方法名
			String methodName = joinPoint.getSignature().getName();
			rs.append("方法名:" + methodName + "; </br>");
 
			// 獲取類的所有方法
			Method[] methods = joinPoint.getTarget().getClass()
					.getDeclaredMethods();
			//創(chuàng)建變量用于存儲(chǔ)注解返回的value值
			String operationType = "";
			for (Method method:methods) {
				String mName = method.getName();
				// 當(dāng)切的方法和類中的方法相同時(shí)
				if (methodName.equals(mName)) {
					//獲取方法的UserOperationType注解
					UserOperationType userOperationType = 
							method.getAnnotation(UserOperationType.class);
					//如果方法存在UserOperationType注解時(shí)
					if (userOperationType!=null) {
						//獲取注解的value值
						operationType = userOperationType.value();
						
						// 獲取操作內(nèi)容
						Object[] args = joinPoint.getArgs();
						int i = 1;
						if (args!=null&&args.length>0) {
							for (Object arg :args) {
								rs.append("[參數(shù)" + i + "======");
								userOptionContent(arg, rs);
								rs.append("]</br>");
							}
						}
						// 創(chuàng)建日志對(duì)象
						UserOperationLogDO log = new UserOperationLogDO();
						log.setLogId(ASAPI.randomizer().getRandomGUID());
						log.setUserCode(userName);
						log.setUserIP(userIp);
						log.setOperationDesc(new SerialBlob(rs.toString().getBytes("UTF-8")));
						log.setOperationType(operationType);
						log.setOperationTime(new Timestamp(System.currentTimeMillis()));
						log.setStatus(status);
						//日志對(duì)象入庫(kù)
						userOperationLog.insertLog(log);						
					}
				}
			}
		} 			
	}	
	/**
	 * 獲取請(qǐng)求主機(jī)IP地址,如果通過(guò)代理進(jìn)來(lái),則透過(guò)防火墻獲取真實(shí)IP地址;
	 * 
	 * @param request
	 * @return
	 * @throws IOException
	 */
	public final static String getIpAddress(HttpServletRequest request)
			throws IOException {
		// 獲取請(qǐng)求主機(jī)IP地址,如果通過(guò)代理進(jìn)來(lái),則透過(guò)防火墻獲取真實(shí)IP地址
 
		String ip = request.getHeader("X-Forwarded-For"); 
		if (ip == null || ip.length() == 0 || "unknown".equalsIgnoreCase(ip)) {
			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.getHeader("HTTP_CLIENT_IP");
			}
			if (ip == null || ip.length() == 0
					|| "unknown".equalsIgnoreCase(ip)) {
				ip = request.getHeader("HTTP_X_FORWARDED_FOR");
			}
			if (ip == null || ip.length() == 0
					|| "unknown".equalsIgnoreCase(ip)) {
				ip = request.getRemoteAddr();
			}
		} else if (ip.length() > 15) {
			String[] ips = ip.split(",");
			for (int index = 0; index < ips.length; index++) {
				String strIp = (String) ips[index];
				if (!("unknown".equalsIgnoreCase(strIp))) {
					ip = strIp;
					break;
				}
			}
		}
		return ip;
	}
	/**
	 * 使用Java反射來(lái)獲取被攔截方法(insert、update, delete)的參數(shù)值, 將參數(shù)值拼接為操作內(nèi)容
	 */
	@SuppressWarnings("unchecked")
	public StringBuffer userOptionContent(Object info, StringBuffer rs){
		String className = null;
		// 獲取參數(shù)對(duì)象類型
		className = info.getClass().getName();
		className = className.substring(className.lastIndexOf(".") + 1);
		rs.append("類型:"+className+",");
		
		//參數(shù)對(duì)象類型不是實(shí)體類或者集合時(shí),直接顯示參數(shù)值
		if (className.equals("String")||className.equals("int")||className.equals("Date")
				||className.equals("Timestamp")||className.equals("Integer")
				||className.equals("B")||className.equals("Long")) {
			rs.append("值:(" + info + ")");
		}
		
		//參數(shù)類型是ArrayList集合,迭代里面的對(duì)象,并且遞歸
		if(className.equals("ArrayList")){
			int i = 1;
			//將參數(shù)對(duì)象轉(zhuǎn)換成List集合
			List<Object> list = (List<Object>) info;
			for (Object obj: list) {
				rs.append("</br>&nbsp;集合內(nèi)容" + i + "————");
				//遞歸
				userOptionContent(obj, rs);
				rs.append("</br>");
				i++;
			}
		//參數(shù)對(duì)象是實(shí)體類
		}else{
			// 獲取對(duì)象的所有方法
			Method[] methods = info.getClass().getDeclaredMethods();
			//遍歷對(duì)象中的所有方法是否是get方法
			for (Method method : methods) {
				//獲取方法名字
				String methodName = method.getName();
				if (methodName.indexOf("get") == -1 || methodName.equals("getPassword")
						|| methodName.equals("getBytes")|| methodName.equals("getChars")
						|| methodName.equals("getLong") || methodName.equals("getInteger")
						|| methodName.equals("getTime") || methodName.equals("getCalendarDate")
						|| methodName.equals("getDay")  || methodName.equals("getMinutes")
						|| methodName.equals("getHours")|| methodName.equals("getSeconds")
						|| methodName.equals("getYear") || methodName.equals("getTimezoneOffset")
						|| methodName.equals("getDate") || methodName.equals("getJulianCalendar")
						|| methodName.equals("getMillisOf") || methodName.equals("getCalendarSystem")
						|| methodName.equals("getMonth")|| methodName.equals("getTimeImpl")
						|| methodName.equals("getNanos")) {  
					continue;
				}
				rs.append("</br>&nbsp;" + className + "——" + changeString(methodName) + ":");
				
				Object rsValue = null;
				try {
					// 調(diào)用get方法,獲取返回值
					rsValue = method.invoke(info);
					userOptionContent(rsValue, rs);
				} catch (Exception e) {
					continue;
				}
			}
		}
		return rs;
	}
	//有g(shù)et方法獲得屬性名
	public String changeString(String name){
		name = name.substring(3);
		name = WordUtils.uncapitalize(name);//首字符小寫
		return name;
	}
}

記錄操作日志的一般套路

記錄操作日志是web系統(tǒng)做安全審計(jì)和系統(tǒng)維護(hù)的重要手段,這里總結(jié)筆者在用java和python開發(fā)web系統(tǒng)過(guò)程中總結(jié)出來(lái)的、具有普遍意義的方法。

在java體系下,網(wǎng)絡(luò)上搜索了一下,幾乎一邊倒的做法是用AOP,通過(guò)注解的方式記錄操作日志,在此,筆者并不是很認(rèn)同這種做法,原因如下:

  • AOP的應(yīng)用場(chǎng)景是各種接口中可以抽象出普遍的行為,且切入點(diǎn)選擇需要在各接口中比較統(tǒng)一。
  • 記錄審計(jì)日志除了ip、用戶等共同的信息外,還需要記錄很多個(gè)性化的東西,比如一次修改操作,一般來(lái)講需要記錄對(duì)象標(biāo)識(shí)、修改前后的值等等。有的值甚至并不能從request參數(shù)中直接獲取,有可能需要一定的邏輯判斷或者運(yùn)算,使用AOP并不合適。
  • 當(dāng)然,有人說(shuō)AOP中也可以傳遞參數(shù),這里且不說(shuō)有些日志信息需要從request參數(shù)計(jì)算而來(lái)的問(wèn)題,就是是可以直接獲取,在注解中傳遞一大堆的參數(shù)也失去了AOP簡(jiǎn)單的好處。

當(dāng)然這主要還是看需求,如果你的操作日志僅僅是需要記錄ip、用戶等與具體接口無(wú)關(guān)的信息,那就無(wú)所謂。

接下來(lái)記錄操作日志就比較簡(jiǎn)單了,無(wú)非就是在接口返回之前記錄一些操作信息,這些信息可能從request參數(shù)中獲取,也可能用request參數(shù)經(jīng)過(guò)一些運(yùn)算獲取,都無(wú)所謂,但是有一點(diǎn)需要注意,你得確保成功或者失敗場(chǎng)景都有記錄。

那么問(wèn)題來(lái)了,現(xiàn)在的web框架,REST接口調(diào)用失敗普遍的做法是業(yè)務(wù)往外拋異常,由一個(gè)“統(tǒng)一異常處理”模塊來(lái)處理異常并構(gòu)造返回體,Java的String Boot(ExceptionHandler)、Python的flask(裝飾器里make_response)、pecan(hook)等莫不是如此。那么接口調(diào)用失敗的時(shí)候如何記錄審計(jì)日志呢?肯定不可能在業(yè)務(wù)每個(gè)拋異常的地方去記錄,這太麻煩,解決方法當(dāng)然是在前面說(shuō)的這個(gè)“統(tǒng)一異常處理”模塊去處理,那么記錄的參數(shù)如何傳遞給這個(gè)模塊呢?方法就是放在本地線程相關(guān)的變量里,java接口可以在入口處整理操作日志信息存放在ThreadLocal變量里,成功或者失敗的時(shí)候設(shè)置一個(gè)status然后記錄入庫(kù)即可;python下,flask接口可以放在app_context的g里,pecan可以放在session里。另外如果是異步任務(wù),還需要給任務(wù)寫個(gè)回調(diào)來(lái)更新狀態(tài)。

可見,不管是用java還是python開發(fā)操作日志,都是相同的套路,總結(jié)如下圖:

還有一點(diǎn)要注意,如果java接口是用的@Valid注解來(lái)進(jìn)行參數(shù)校驗(yàn),那么在校驗(yàn)失敗時(shí)會(huì)拋出MethodArgumentNotValidException,問(wèn)題在于,這個(gè)Valid發(fā)生在請(qǐng)求進(jìn)入接口之前,也就是說(shuō),出現(xiàn)參數(shù)校驗(yàn)失敗拋出MethodArgumentNotValidException的時(shí)候還沒有進(jìn)入接口里面的代碼,自然也就沒有往本地線程中記錄操作日志需要的信息,那怎么辦呢?方法就是在接口的請(qǐng)求入?yún)⒅屑右粋€(gè)BindingResult binding類型的參數(shù),這個(gè)參數(shù)會(huì)截獲參數(shù)校驗(yàn)的接口而不是拋出異常,然后在代碼中(已經(jīng)往線程上下文中寫入了操作日志需要的信息以后的代碼中)判斷當(dāng)binding中有錯(cuò)誤,就拋出MethodArgumentNotValidException,此時(shí)就可以獲取到操作日志需要的信息了,代碼如下:

// 先往threadlocal變量中存入操作日志需要的信息

...

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • 利用Jackson解決Json序列化和反序列化問(wèn)題

    利用Jackson解決Json序列化和反序列化問(wèn)題

    Jackson是一個(gè)用于處理Json數(shù)據(jù)的Java庫(kù),它提供了一系列功能,包括Json序列化和反序列化,所以本文就來(lái)講講如何利用利用Jackson解決Json序列化和反序列化的問(wèn)題吧
    2023-05-05
  • Java zookeeper服務(wù)的使用詳解

    Java zookeeper服務(wù)的使用詳解

    ZooKeeper是一個(gè)分布式的,開放源碼的分布式應(yīng)用程序協(xié)調(diào)服務(wù),是Google的Chubby一個(gè)開源的實(shí)現(xiàn),是Hadoop和Hbase的重要組件。它是一個(gè)為分布式應(yīng)用提供一致性服務(wù)的軟件,提供的功能包括:配置維護(hù)、域名服務(wù)、分布式同步、組服務(wù)等
    2022-08-08
  • 詳談cxf和axis兩種框架下的webservice客戶端開發(fā)

    詳談cxf和axis兩種框架下的webservice客戶端開發(fā)

    這篇文章主要介紹了詳談cxf和axis兩種框架下的webservice客戶端開發(fā),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-08-08
  • java實(shí)現(xiàn)簡(jiǎn)單圖片上傳下載功能

    java實(shí)現(xiàn)簡(jiǎn)單圖片上傳下載功能

    這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)簡(jiǎn)單圖片上傳下載功能,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-10-10
  • JavaStream將List轉(zhuǎn)為Map示例

    JavaStream將List轉(zhuǎn)為Map示例

    這篇文章主要為大家介紹了JavaStream將List轉(zhuǎn)為Map示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-09-09
  • java獲取網(wǎng)絡(luò)類型的方法

    java獲取網(wǎng)絡(luò)類型的方法

    這篇文章主要介紹了java獲取網(wǎng)絡(luò)類型的方法,涉及java針對(duì)網(wǎng)絡(luò)類型的參數(shù)獲取及判定技巧,具有一定參考借鑒價(jià)值,需要的朋友可以參考下
    2015-10-10
  • JavaWeb項(xiàng)目中classpath路徑詳解

    JavaWeb項(xiàng)目中classpath路徑詳解

    今天小編就為大家分享一篇關(guān)于JavaWeb項(xiàng)目中classpath路徑詳解,小編覺得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧
    2018-12-12
  • Maven導(dǎo)入本地jar包的實(shí)現(xiàn)步驟

    Maven導(dǎo)入本地jar包的實(shí)現(xiàn)步驟

    本文主要介紹了Maven導(dǎo)入本地jar包的實(shí)現(xiàn)步驟,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-07-07
  • Java中的Callable實(shí)現(xiàn)多線程詳解

    Java中的Callable實(shí)現(xiàn)多線程詳解

    這篇文章主要介紹了Java中的Callable實(shí)現(xiàn)多線程詳解,接口Callable中有一個(gè)call方法,其返回值類型為V,這是一個(gè)泛型,值得關(guān)注的是這個(gè)call方法有返回值,這意味著線程執(zhí)行完畢后可以將處理結(jié)果返回,需要的朋友可以參考下
    2023-08-08
  • springboot項(xiàng)目啟動(dòng)指定對(duì)應(yīng)環(huán)境的方法

    springboot項(xiàng)目啟動(dòng)指定對(duì)應(yīng)環(huán)境的方法

    這篇文章主要介紹了springboot項(xiàng)目啟動(dòng)指定對(duì)應(yīng)環(huán)境的方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-08-08

最新評(píng)論