zoukankan      html  css  js  c++  java
  • 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) {
  • 相关阅读:
    ios10 获取idfa的坑
    iOS 获取手机sim卡的运营商(移动,电信,联通) 相关信息
    iOS获取手机IP地址
    UIScrollView 与 touchesBegan 冲突解决方法
    32位与64位基础
    MySQL数据库基础_表&简单查询
    MySQL数据库基础
    Java_File、递归
    Java_lambda表达式
    Java线程锁,等待唤醒和线程池
  • 原文地址:https://www.cnblogs.com/zjdxr-up/p/10573936.html
Copyright © 2011-2022 走看看