springAOP實現操作日志記錄,並記錄請求參數與編輯前后字段的具體改變


 本文為博主原創,未經允許不得轉載:

   在項目開發已經完成多半的情況下,需要開發進行操作日志功能的開發,由於操作的重要性,需要記錄下操作前的參數和請求時的參數,

在網上找了很多,沒找到可行的方法.由於操作日志用注解方式的AOP記錄操作日志比較便捷,所以想到了在注解中定義操作前查詢數據

詳情的bean,查詢方法及參數,參數類型,在aop進行方法執行前,對指定的bean,方法,參數進行調用,獲得修改前的參數,並進行保存.

此處需要注意:

1.在前面中調用指定bean的方法時,不可用反射進行調用,反射不能加載spring容器,無法獲取指定的spring bean,下面方法中封裝的獲取spring bean的

  工具類也需要配置為bean,而且被spring加載,才可以;

2.@Aspect注解的類一定要配置成bean,而且被spring加載,才可以,即同時配置@Component和@Aspect,或在spring的配置文件進行bean的配置

3.如果配置了bean,要檢索component-scan掃描范圍是否包括Aspect類;

一.定義切面執行的注解(該注解可根據自己實現的內容進行自定義)

 1 import java.lang.annotation.Documented;
 2 import java.lang.annotation.Retention; 3 import java.lang.annotation.Target; 4 5 import java.lang.annotation.ElementType; 6 import java.lang.annotation.RetentionPolicy; 7 8 @Target({ElementType.PARAMETER, ElementType.METHOD}) 9 @Retention(RetentionPolicy.RUNTIME) 10 @Documented 11 public @interface SystemControllerLog { 12 13 /**查詢模塊*/ 14 String module() default ""; 15 16 /**查詢模塊名稱*/ 17 String methods() default ""; 18 19 /**查詢的bean名稱*/ 20 String serviceClass() default ""; 21 22 /**查詢單個詳情的bean的方法*/ 23 String queryMethod() default ""; 24 25 /**查詢詳情的參數類型*/ 26 String parameterType() default ""; 27 28 /**從頁面參數中解析出要查詢的id, 29 * 如域名修改中要從參數中獲取customerDomainId的值進行查詢 30 */ 31 String parameterKey() default ""; 32 33 /**是否為批量類型操作*/ 34 boolean paramIsArray() default false; 35 36 }

 

 二.切面執行的方法

  1  
  2  import java.lang.reflect.Method;
  3  import java.text.SimpleDateFormat;
  4  import java.util.Date;
  5  
  6  import javax.servlet.http.HttpServletRequest;
  7  import javax.servlet.http.HttpSession;
  8  
  9  import org.apache.commons.lang3.StringUtils;
 10  import org.aspectj.lang.ProceedingJoinPoint;
 11  import org.aspectj.lang.Signature;
 12  import org.aspectj.lang.annotation.Around;
 13  import org.aspectj.lang.annotation.Aspect;
 14  import org.aspectj.lang.annotation.Pointcut;
 15  import org.aspectj.lang.reflect.MethodSignature;
 16  import org.slf4j.Logger;
 17  import org.slf4j.LoggerFactory;
 18  import org.springframework.beans.factory.annotation.Autowired;
 19  import org.springframework.stereotype.Component;
 20  import org.springframework.util.ReflectionUtils;
 21  import org.springframework.web.context.request.RequestContextHolder;
 22  import org.springframework.web.context.request.ServletRequestAttributes;
 23  
 24  import com.alibaba.fastjson.JSON;
 25  import com.alibaba.fastjson.JSONArray;
 26  import com.alibaba.fastjson.JSONObject;
 27  import com.suning.fucdn.common.RequestResult;
 28  import com.suning.fucdn.common.enums.FucdnStrConstant;
 29  import com.suning.fucdn.entity.log.SystemControllerLogInfo;
 30  import com.suning.fucdn.impl.service.log.LogServiceImpl;
 31  import com.suning.fucdn.vo.AdminUserVO;
 32  
 33  /**
 34   * 
 35   * 〈一句話功能簡述:操作日志切面記錄操作〉<br> 
 36   * 〈功能詳細描述〉
 37   *
 38   * @author xiang
 39   * @see [相關類/方法](可選)
 40   * @since [產品/模塊版本] (可選)
 41   */
 42  @Component
 43  @Aspect
 44  public class ControllerLogAopAspect {
 45      
 46      private static final Logger LOGGER = LoggerFactory.getLogger(ControllerLogAopAspect.class);
 47      
 48      //注入service,用來將日志信息保存在數據庫
 49      @Autowired
 50      private LogServiceImpl logservice;
 51      
 52       //配置接入點,如果不知道怎么配置,可以百度一下規則
      //指定controller的類進行切面  @Pointcut("execution(* com.controller..CustomerController.*(..))||execution(* com.controller.ManageController.*(..))")  
53 @Pointcut("execution(* com.controller..*.*(..))") 54 private void controllerAspect(){ 55 System.out.println("point cut start"); 56 }//定義一個切入點 57 58 @SuppressWarnings({ "rawtypes", "unused" }) 59 @Around("controllerAspect()") 60 public Object around(ProceedingJoinPoint pjp) throws Throwable { 61 //常見日志實體對象 62 SystemControllerLogInfo log = new SystemControllerLogInfo(); 63 //獲取登錄用戶賬戶 64 HttpServletRequest httpRequest = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest(); 65 66 //方法通知前獲取時間,為什么要記錄這個時間呢?當然是用來計算模塊執行時間的 67 //獲取系統時間 68 String time = new SimpleDateFormat(FucdnStrConstant.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.getConstant()).format(new Date()); 69 log.setStartTime(time); 70 71 //獲取系統ip,這里用的是我自己的工具類,可自行網上查詢獲取ip方法 72 //String ip = GetLocalIp.localIp(); 73 //log.setIP(ip); 74 75 // 攔截的實體類,就是當前正在執行的controller 76 Object target = pjp.getTarget(); 77 // 攔截的方法名稱。當前正在執行的方法 78 String methodName = pjp.getSignature().getName(); 79 // 攔截的方法參數 80 Object[] args = pjp.getArgs(); 81 //String params = Arrays.toString(pjp.getArgs()); 82 JSONArray operateParamArray = new JSONArray(); 83 for (int i = 0; i < args.length; i++) { 84 Object paramsObj = args[i]; 85 //通過該方法可查詢對應的object屬於什么類型:String type = paramsObj.getClass().getName(); 86 if(paramsObj instanceof String || paramsObj instanceof JSONObject){ 87 String str = (String) paramsObj; 88 //將其轉為jsonobject 89 JSONObject dataJson = JSONObject.parseObject(str); 90 if(dataJson == null || dataJson.isEmpty() || "null".equals(dataJson)){ 91 break; 92 }else{ 93 operateParamArray.add(dataJson); 94 } 95 }else if(paramsObj instanceof Map){ 96 //get請求,以map類型傳參 97 //1.將object的map類型轉為jsonobject類型 98 Map<String, Object> map = (Map<String, Object>) paramsObj; 99 JSONObject json =new JSONObject(map); 100 operateParamArray.add(json); 101 } 102 } 103 //設置請求參數 104 log.setOperateParams(operateParamArray.toJSONString()); 105 // 攔截的放參數類型 106 Signature sig = pjp.getSignature(); 107 MethodSignature msig = null; 108 if (!(sig instanceof MethodSignature)) { 109 throw new IllegalArgumentException("該注解只能用於方法"); 110 } 111 msig = (MethodSignature) sig; 112 113 Class[] parameterTypes = msig.getMethod().getParameterTypes(); 114 Object object = null; 115 // 獲得被攔截的方法 116 Method method = null; 117 try { 118 method = target.getClass().getMethod(methodName, parameterTypes); 119 } catch (NoSuchMethodException e1) { 120 LOGGER.error("ControllerLogAopAspect around error",e1); 121 } catch (SecurityException e1) { 122 LOGGER.error("ControllerLogAopAspect around error",e1); 123 } 124 if (null != method) { 125 // 判斷是否包含自定義的注解,說明一下這里的SystemLog就是我自己自定義的注解 126 if (method.isAnnotationPresent(SystemControllerLog.class)) { 127 128 //此處需要對用戶進行區分:1為admin user 2為customer user 129 // get session 130 HttpSession httpSession = httpRequest.getSession(true); 131 // 從session獲取登錄用戶 132 AdminUserVO adminUserVO = (AdminUserVO) httpSession 133 .getAttribute(FucdnStrConstant.SESSION_KEY_ADMIN.getConstant()); 134 long adminUserId = adminUserVO.getAdminUserId(); 135 log.setUserId(String.valueOf(adminUserId)); 136 137 SystemControllerLog systemlog = method.getAnnotation(SystemControllerLog.class); 138 139 log.setModule(systemlog.module()); 140 log.setMethod(systemlog.methods()); 141 //請求查詢操作前數據的spring bean 142 String serviceClass = systemlog.serviceClass(); 143 //請求查詢數據的方法 144 String queryMethod = systemlog.queryMethod(); 145 //判斷是否需要進行操作前的對象參數查詢 146 if(StringUtils.isNotBlank(systemlog.parameterKey()) 147 &&StringUtils.isNotBlank(systemlog.parameterType()) 148 &&StringUtils.isNotBlank(systemlog.queryMethod()) 149 &&StringUtils.isNotBlank(systemlog.serviceClass())){ 150 boolean isArrayResult = systemlog.paramIsArray(); 151 //參數類型 152 String paramType = systemlog.parameterType(); 153 String key = systemlog.parameterKey(); 154 155 if(isArrayResult){//批量操作 156 //JSONArray jsonarray = (JSONArray) object.get(key); 157 //從請求的參數中解析出查詢key對應的value值 158 String value = ""; 159 JSONArray beforeParamArray = new JSONArray(); 160 for (int i = 0; i < operateParamArray.size(); i++) { 161 JSONObject params = operateParamArray.getJSONObject(i); 162 JSONArray paramArray = (JSONArray) params.get(key); 163 if (paramArray != null) { 164 for (int j = 0; j < paramArray.size(); j++) { 165 String paramId = paramArray.getString(j); 166 //在此處判斷spring bean查詢的方法參數類型 167 Object data = getOperateBeforeData(paramType, serviceClass, queryMethod, paramId); 168 JSONObject json = (JSONObject) JSON.toJSON(data); 169 beforeParamArray.add(json); 170 } 171 } 172 } 173 log.setBeforeParams(beforeParamArray.toJSONString()); 174 175 }else{//單量操作 176 177 //從請求的參數中解析出查詢key對應的value值 178 String value = ""; 179 for (int i = 0; i < operateParamArray.size(); i++) { 180 JSONObject params = operateParamArray.getJSONObject(i); 181 value = params.getString(key); 182 if(StringUtils.isNotBlank(value)){ 183 break; 184 } 185 } 186 //在此處獲取操作前的spring bean的查詢方法 187 Object data = getOperateBeforeData(paramType, serviceClass, queryMethod, value); 188 JSONObject beforeParam = (JSONObject) JSON.toJSON(data); 189 log.setBeforeParams(beforeParam.toJSONString()); 190 } 191 } 192 193 try { 194 //執行頁面請求模塊方法,並返回 195 object = pjp.proceed(); 196 //獲取系統時間 197 String endTime = new SimpleDateFormat(FucdnStrConstant.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.getConstant()).format(new Date()); 198 log.setEndTime(endTime); 199 //將object 轉化為controller封裝返回的實體類:RequestResult 200 RequestResult requestResult = (RequestResult) object; 201 if(requestResult.isResult()){ 202 //操作流程成功 203 if(StringUtils.isNotBlank(requestResult.getErrMsg())){ 204 log.setResultMsg(requestResult.getErrMsg()); 205 }else if(requestResult.getData() instanceof String){ 206 log.setResultMsg((String) requestResult.getData()); 207 }else{ 208 log.setResultMsg("執行成功"); 209 } 210 }else{ 211 log.setResultMsg("失敗"); 212 } 213 //保存進數據庫 214 logservice.saveLog(log); 215 } catch (Throwable e) { 216 String endTime = new SimpleDateFormat(FucdnStrConstant.YEAR_MONTH_DAY_HOUR_MINUTE_SECOND.getConstant()).format(new Date()); 217 log.setEndTime(endTime); 218 219 log.setResultMsg(e.getMessage()); 220 logservice.saveLog(log); 221 } 222 } else { 223 //沒有包含注解 224 object = pjp.proceed(); 225 } 226 } else { 227 //不需要攔截直接執行 228 object = pjp.proceed(); 229 } 230 return object; 231 } 232 233 /** 234 * 235 * 功能描述: <br> 236 * 〈功能詳細描述〉 237 * 238 * @param paramType:參數類型 239 * @param serviceClass:bean名稱 240 * @param queryMethod:查詢method 241 * @param value:查詢id的value 242 * @return 243 * @see [相關類/方法](可選) 244 * @since [產品/模塊版本](可選) 245 */ 246 public Object getOperateBeforeData(String paramType,String serviceClass,String queryMethod,String value){ 247 Object obj = new Object(); 248 //在此處解析請求的參數類型,根據id查詢數據,id類型有四種:int,Integer,long,Long 249 if(paramType.equals("int")){ 250 int id = Integer.parseInt(value); 251 Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class ); 252 //用spring bean獲取操作前的參數,此處需要注意:傳入的id類型與bean里面的參數類型需要保持一致 253 obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id); 254 255 }else if(paramType.equals("Integer")){ 256 Integer id = Integer.valueOf(value); 257 Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class ); 258 //用spring bean獲取操作前的參數,此處需要注意:傳入的id類型與bean里面的參數類型需要保持一致 259 obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id); 260 261 }else if(paramType.equals("long")){ 262 long id = Long.parseLong(value); 263 Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class ); 264 //用spring bean獲取操作前的參數,此處需要注意:傳入的id類型與bean里面的參數類型需要保持一致 265 obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id); 266 267 }else if(paramType.equals("Long")){ 268 Long id = Long.valueOf(value); 269 Method mh = ReflectionUtils.findMethod(SpringContextUtil.getBean(serviceClass).getClass(), queryMethod,Long.class ); 270 //用spring bean獲取操作前的參數,此處需要注意:傳入的id類型與bean里面的參數類型需要保持一致 271 obj = ReflectionUtils.invokeMethod(mh, SpringContextUtil.getBean(serviceClass),id); 272 } 273 return obj; 274 } 275 }

 

 三.獲取spring bean的工具類

 1 import org.springframework.beans.BeansException;
 2 import org.springframework.context.ApplicationContext;
 3 import org.springframework.context.ApplicationContextAware;
 4 import org.springframework.stereotype.Component;
 5 
 6 
 7 /**
 8  * 獲取spring容器,以訪問容器中定義的其他bean
 9  *  xiang
10  *  MOSTsView 3.0 2009-11-16
11  */
12 @Component
13 public class SpringContextUtil implements ApplicationContextAware{
14      
15     private static ApplicationContext   applicationContext;
16  
17     /**
18      * 實現ApplicationContextAware接口的回調方法,設置上下文環境
19      */
20     public void setApplicationContext(ApplicationContext applicationContext){
21         SpringContextUtil.applicationContext = applicationContext;
22     }
23  
24     public static ApplicationContext getApplicationContext(){
25         return applicationContext;
26     }
27  
28     /**
29      * 獲取對象
30      * @return  Object 一個以所給名字注冊的bean的實例 (service注解方式,自動生成以首字母小寫的類名為bean name)
31      */
32     public static Object getBean(String name) throws BeansException{
33         return applicationContext.getBean(name);
34     }
35 }

 

 四.操作日志對應的實體類

 1 public class SystemControllerLogInfo{
 2     
 3     private long id; 4 5 /**用戶id*/ 6 private String userId; 7 8 /**用戶類型*/ 9 private int userType; 10 11 /**操作模塊*/ 12 private String module; 13 14 /**操作類型*/ 15 private String method; 16 17 /**操作前參數*/ 18 private String beforeParams; 19 20 /**操作時請求參數*/ 21 private String operateParams; 22 23 /**開始時間*/ 24 private String startTime; 25 26 /**結束時間*/ 27 private String endTime; 28 29 /**操作狀態描述*/ 30 private int resultStatus; 31 32 /**操作結果描述*/ 33 private String resultMsg;

 

 五.進行注解切面調用

 1 @ResponseBody
 2     @RequestMapping(value = "/delete", method = { RequestMethod.POST }) 3 @SystemControllerLog(module="域名管理",methods="域名刪除",serviceClass="domainConfService",queryMethod="queryDomain",parameterType="Long",parameterKey="customerDomainId") 4 public RequestResult delete(@RequestBody String param) { 5 // 定義請求數據 6 RequestResult result = new RequestResult(); 7 // 接收數據 8 CustomerDomain customerDomain = JSONObject.parseObject(param, CustomerDomain.class); 9 // 更新客戶域名 10 try { 11 String data = domainConfService.deleteDomain(customerDomain.getId()); 12 // 設置true 13 if (StringUtils.isBlank(data)) { 14 result.setData("刪除成功"); 15 } else { 16  result.setData(data); 17  } 18 result.setResult(true); 19 } catch (Exception e) { 20 // 記錄錯誤信息,並返回 21 LOGGER.error("delete failed", e); 22  result.setErrMsg(e.getMessage()); 23  } 24 // 返回 25 return result; 26 }

 

 六.數據實例

 補充:get請求參數類型解析和記錄

 1  JSONArray operateParamArray = new JSONArray();
 2         for (int i = 0; i < args.length; i++) {
 3             Object paramsObj = args[i];
 4             //通過該方法可查詢對應的object屬於什么類型:String type = paramsObj.getClass().getName();
 5             if(paramsObj instanceof String || paramsObj instanceof JSONObject){
 6                 String str = (String) paramsObj;
 7                 //將其轉為jsonobject
 8                 JSONObject dataJson = JSONObject.parseObject(str);
 9                 if(dataJson == null || dataJson.isEmpty() || "null".equals(dataJson)){
10                     break;
11                 }else{
12                     operateParamArray.add(dataJson);
13                 }
14             }else if(paramsObj instanceof Map){
15                 //get請求,以map類型傳參
16                 //1.將object的map類型轉為jsonobject類型
17                 Map<String, Object> map = (Map<String, Object>) paramsObj;
18                 JSONObject json =new JSONObject(map);
19                 operateParamArray.add(json);
20             }
21         }

get請求的controller示例:

@ResponseBody
    @RequestMapping(value = "/add", method = { RequestMethod.GET })
    @SystemControllerLog(module="域名管理",methods="域名新增")
    public RequestResult addDomain(@RequestParam Map<String, String> paramMap) {

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM