• 基于spring aop的操作日志功能


    公司有一个项目需要加一个操作日志的功能。领导明确说明不要用触发器,所以想到了aop,并在网上找到了一些例子进行学习。我根据业务需要增加了一些功能,在这里做一下记录。

    一、开启aop。在web.xml中contextConfigLocation对应的配置文件内加入<aop:aspectj-autoproxy proxy-target-class="false"/>。因为我需要记录的是mapper层,所以将proxy-target-class设为false,使用jdk代理。
    二、自定义一个注解。该注解用于配置需要记录的操作接口。
    @Target({ ElementType.PARAMETER, ElementType.METHOD })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface SystemMapperLog {
    
    /** 要执行的具体操作比如:添加用户 **/
    //操作详情
    String operation() default "";
    //操作表
    String table() default "";
    //操作列
    //查询新旧值使用,非更新不需要填写
    String columns() default "";
    //操作列名
    //查询新旧值使用,非更新不需要填写
    String columnsName() default "";
    //操作模块
    String operateObject() default "";
    //参数-拼接操作详情使用
    String param() default "";
    //类型-
    String type() default "";
    //条件-新增时会用到
    String condition() default "";
    }
    三、声明一个切面,定义一个切点,创建切入之后进行的操作方法
    1、切面声明
    //切面声明
    
    @Component
    @Aspect
    public class LogAopAction {
    
    }

    2、切点定义

    1 private final String MAPPER_POINT = "execution(public * com.seeyoui.kensite..persistence.*.*(..))";
    2 
    3 //mapper层切点
    4 @Pointcut(MAPPER_POINT)
    5 private void mapperAspect() {
    6 }
    这个切点切入的是所有的mapper层方法,但这显然是不对的,不对在切入后会根据注解进行判断,在存在对应注解的方法处才行进行日志的保存操作。
    3、创建切入之后执行的方法
    //mapper层切入后的处理方法-环绕
    	@SuppressWarnings("unchecked")
    	@Around("mapperAspect()")
    	public Object doAroundMapper(ProceedingJoinPoint pjp) throws Throwable {
    		try {
    			Class[] parameterTypes = ((MethodSignature) pjp.getSignature()).getMethod().getParameterTypes();
    			MethodSignature signature = (MethodSignature) pjp.getSignature();
    			Method method = signature.getMethod();
    			//如果有注解,说明是需要监听的方法
    			if(method.isAnnotationPresent(SystemMapperLog.class)){
    				SysUser sysUser = UserUtils.getUser();
    				Object[] args = pjp.getArgs();
    				//从注解中获取需要的信息
    				HashMap<String,String> map = getMapperMthodDescription(pjp);
    				String operation = map.get("operation");
    				String table = map.get("table");
    				String columns = map.get("columns");
    				String columnsName = map.get("columnsName");
    				String param = map.get("param");
    				String operateObject = map.get("operateObject");
    				String type = map.get("type");
    				String condition = map.get("condition");
    				String[] columnNameArr = null;//获取操作行名称
    				Map map1= new HashMap();//储存方法内参数
    				List<ChangeList> changeList = new ArrayList();//储存新旧值变化
    				if(args != null && args.length != 0 && !("deleteA").equals(type)){
    					map1 = BeanUtils.describe(args[0]);
    				}
    				if(("save").equals(type)){
    					String[] paramArr = null;
    					//如果是保存
    					//判断数据是否满足条件,不满足不需要保存日志
    					if(StringUtils.isNoneBlank(condition)){
    						//第一步拆分,拆分出每个条件
    						String[] conArr = condition.split(",");
    						for (int i = 0; i < conArr.length; i++) {
    							//第二部拆分,拆分出每个条件的key和value
    							String[] conditionArr = conArr[i].split("\|");
    							if(!(conditionArr[1].equals((String)map1.get(conditionArr[0])))){
    								//如果不满足条件,直接跳出方法
    								Object result = pjp.proceed();
    								return result;
    							}
    						}
    					}
    					//如果有param从参数中取出
    					if(StringUtils.isNoneBlank(param)){
    						paramArr = param.split(",");
    						//如果填写了自定义拼接操作需要的信息,开始拼接操作信息
    					}
    					if(operation.indexOf("param") != -1){
    						String[] operationArr = operation.split("param");
    						operation = "";
    						for (int i = 0; i < operationArr.length; i++) {
    							//根据|分割
    							String[] pa = paramArr[i].split("\|");
    							String str = "";
    							for (int j = 0; j < pa.length; j++) {
    								str = (String)map1.get(StringUtils.toCamelCase(pa[j]));
    								if(StringUtils.isBlank(str)){
    									continue;
    								}else{
    									break;
    								}
    							}
    							operation += operationArr[i] + str;
    						}
    					}
    				}else if(("update").equals(type)){
    					if(StringUtils.isNoneBlank(table)){
    						columnNameArr = columnsName.split(",");
    					}
    					//根据所获取的信息拼接出日志对象
    					if(StringUtils.isNoneBlank(table)&&StringUtils.isNoneBlank(columns)){
    						String paramStr = "";
    						String[] paramArr = null;
    						//若传入的参数中存在delFlag且他的值为0,则认定此次操作为假删除,不处理各个字段的变化,保存一条删除记录
    						boolean isDel = false;//假删除标识
    						String[] operationArr = operation.split("param");
    						if(("0").equals((String)map1.get("delFlag"))){
    							isDel = true;
    							operationArr[0] = "删除记录";
    						}
    						if(StringUtils.isNoneBlank(param)){
    							paramStr = ","+param;
    							paramArr = param.split(",");
    						}
    						//如果填写了表信息和字段信息
    						String sql = "select " + columns + paramStr + " from " + table + " where id='"+map1.get("id")+"'";
    						if((columns.indexOf(",") != -1) && !isDel){
    							//如果列中存在逗号,说明是多列
    							String[] columnArr = columns.split(",");
    							for (int i = 0; i < columnArr.length; i++) {
    								ChangeList cl = new ChangeList();
    								String oldValue = DBUtils.getString(sql, columnArr[i]);
    								String newValue = (String)map1.get(StringUtils.toCamelCase(columnArr[i]));
    								try {
    									//根据北京时间格式转换新值。有异常说明不是时间格式
    									String DATE_FORMAT = "EEE MMM dd HH:mm:ss z yyyy";
    									Date date = new SimpleDateFormat(DATE_FORMAT, Locale.US).parse(newValue);
    									SimpleDateFormat format0 = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    									newValue = format0.format(date);
    									//如果新值是时间格式。将旧值的.0处理掉
    									oldValue = oldValue.substring(0,oldValue.length()-2);
    								} catch (Exception e) {
    									// TODO: handle exception
    								}
    								//如果要修改的新值为空。不予记录
    								if(StringUtils.isBlank(newValue)){
    									continue;
    								}
    								cl.setColumn(columnArr[i]);
    								cl.setOldValue(oldValue);
    								cl.setNewValue(newValue);
    								changeList.add(cl);
    							}
    						}else if(columns.indexOf(",") == -1){
    							//单列
    							ChangeList cl = new ChangeList();
    							cl.setColumn(columns);
    							cl.setOldValue(DBUtils.getString(sql, columns));
    							cl.setNewValue((String)map1.get(StringUtils.toCamelCase(columns)));
    							changeList.add(cl);
    						}
    						//如果填写了自定义拼接操作需要的信息,开始拼接操作信息
    						if(operation.indexOf("param") != -1){
    							operation = "";
    							for (int i = 0; i < operationArr.length; i++) {
    								operation += operationArr[i] + DBUtils.getString(sql, paramArr[i]);
    							}
    						}
    					}
    				}else if(type.indexOf("delete") != -1){
    					//如果是删除
    					String[] paramArr = null;
    					if(StringUtils.isNoneBlank(param)){
    						paramArr = param.split(",");
    					}
    					String sql = "";
    					if(("deleteA").equals(type)){
    						//如果delete的传入值是list
    						sql = "select " + param + " from " + table + " where id='"+args[0].toString()+"'";
    					}else{
    						//如果delete的穿入值是对象
    						sql = "select " + param + " from " + table + " where id='"+map1.get("id")+"'";
    					}
    					//如果填写了表信息和字段信息
    					if(operation.indexOf("param") != -1){
    						String[] operationArr = operation.split("param");
    						operation = "";
    						for (int i = 0; i < operationArr.length; i++) {
    							operation += operationArr[i] + DBUtils.getString(sql, paramArr[i]);
    						}
    					}
    				}
    				SystemLog systemLog = new SystemLog();
    				systemLog.setUserAccount(sysUser.getUserName());
    				systemLog.setDelFlag("1");
    				systemLog.setType("9");
    				systemLog.setOperationCode("1");
    				systemLog.setCampId(sysUser.getCampId());
    				systemLog.setGroupId(sysUser.getGroupId());
    				systemLog.setOperateObject(operateObject);
    				systemLog.setOperation(operation);
    				if(changeList.size() != 0){
    					for (int i = 0; i < changeList.size(); i++) {
    						if(!changeList.get(i).getOldValue().equals(changeList.get(i).getNewValue())){
    							systemLog.setId(GeneratorUUID.getId());
    							systemLog.setOldValue(changeList.get(i).getOldValue());
    							systemLog.setNewValue(changeList.get(i).getNewValue());
    							//获取操作对象名
    							systemLog.setFeeItem(columnNameArr[i]);
    							systemLog.preInsert();
    							systemLogMapper.save(systemLog);
    						}
    					}
    				}else{
    					systemLog.preInsert();
    					systemLogMapper.save(systemLog);
    				}
    			}
    		} catch (Exception e) {
    			// TODO: handle exception
    			System.out.println(e.getMessage());
    		}
    		Object result = pjp.proceed();
    		return result;
    	}
    

      

    这个方法是这个日志功能的核心所在,因为需要记录操作的新旧值,所以进行了一系列的判断。在这个方法中,用到了从注解中取值的操作,具体方法如下:
     1 /**
     2      * 获取注解中对方法的描述信息 用于mapper层注解
     3      * 
     4      * @param joinPoint
     5      *            切点
     6      * @return 方法描述
     7      * @throws Exception
     8      */
     9     public static HashMap getMapperMthodDescription(JoinPoint joinPoint)
    10             throws Exception {
    11         MethodSignature signature = (MethodSignature) joinPoint.getSignature();
    12         Method method = signature.getMethod();
    13         String methodName = signature.getName();
    14         Object[] arguments = joinPoint.getArgs();
    15         HashMap<String,String> map = new HashMap<String,String>();
    16         if (method.getName().equals(methodName)) {
    17             Class[] clazzs = method.getParameterTypes();
    18             if (clazzs.length == arguments.length) {
    19                 map.put("operation", method.getAnnotation(SystemMapperLog.class).operation());
    20                 map.put("table", method.getAnnotation(SystemMapperLog.class).table());
    21                 map.put("columns", method.getAnnotation(SystemMapperLog.class).columns());
    22                 map.put("columnsName", method.getAnnotation(SystemMapperLog.class).columnsName());
    23                 map.put("operateObject", method.getAnnotation(SystemMapperLog.class).operateObject());
    24                 map.put("param", method.getAnnotation(SystemMapperLog.class).param());
    25                 map.put("type", method.getAnnotation(SystemMapperLog.class).type());
    26                 map.put("condition", method.getAnnotation(SystemMapperLog.class).condition());
    27             }
    28         }
    29         return map;
    30     }

     

    在最初编写好切面后,又想到了一个新的需求:很多状态都是用的数字,在保存时需要将其转化为对应的字符串。所以想到了一种解决方法:将所有状态保存为对应的常量,在注解中的传入字段对应的常量。
    因为并没有写完,所以单独列出这个功能
     1 private final static Map<String,String> CATER_ORDER_STATE = new HashMap<String,String>();
     2 
     3 static{
     4     CATER_ORDER_STATE.put("6", "预订");
     5 }
     6 
     7 /**
     8      * 获取对应常量对应值的名称
     9      * @param field
    10      * @param key
    11      * @return
    12      */
    13     public static String getFinalValue(String field,String key){
    14         try {
    15             Class<LogAopAction> clazz = LogAopAction.class;
    16             Map map = (Map) clazz.getDeclaredField(field).get(null);
    17             return (String)map.get(key);
    18         } catch (Exception e) {
    19             // TODO Auto-generated catch block
    20             e.printStackTrace();
    21             return null;
    22         }
    23     }
    这样在保存新旧值时调用getFinalValue方法即可将状态值转化为字符串。
    四、在编写好了切面后,就可以通过注解配置需要保存日志的切面了
    具体配置方式:
    @SystemMapperLog(operation="修改餐饮订单param",operateObject="餐饮订单管理",param="name",table="",columns="name,price",columnsName="菜品名称,菜品价格",type="update")
    1、operation是操作详情,可自定义操作详情的内容,其中的参数全部使用param,在解析注解保存日志时对其进行了处理。注意:operation内的param一定要和后面的param对应,param内的字段名使用逗号分隔即可
    2、operateObject是模块名称,根据对应的操作模块填写
    3、param上文说过,不再详细介绍
    4、table中写入需要查询的表名,在不需要储存新旧值变化时不需要填写此字段
    5、columns中写入需要记录新旧值变化的字段,在不需要储存新旧值变化时不需要填写此字段,该字段内的数据用逗号分隔
    6、columnsName中写入需要记录新旧值变化的字段名称,在不需要储存新旧值变化时不需要填写此字段,该字段内的数据用逗号分隔
    7、type为该接口的类型,类型分为save(保存)、update(修改)、deleteA(a类删除)、deleteB(b类删除)
    因为不用的人写代码的习惯不同,所以删除类型分成了两种(也可以继续添加)。
    A类删除适用于方法传入对象是list的情况,会取到list中第一个值作为id进行查询记录操作。
    B类删除适用于方法传入对象是object的情况,去从中取到id进行查询记录操作。在实际应用过程中,B类删除不止可以适用于删除方法。
     
    至此。整个日志操作模块基本完成,在这里做一个记录。
     
    2018-07-20更新:
    1、格式化新值中的日期,使其可以正常对比新旧值
    2、修改了操作列拼接的方法,之前多参数的拼接有bug
  • 相关阅读:
    asp.net控件开发基础(17)
    AjaxControlToolkit更新
    asp.net控件开发基础(18)
    asp.net控件开发基础(15)
    新装了vista,不容易啊
    asp.net控件开发基础(19)
    Oracle创建用户及数据表
    RMAN快速入门指南
    Oracle数据库中sql基础
    PL/SQL循序渐进全面学习教程Oracle
  • 原文地址:https://www.cnblogs.com/emojio/p/9212333.html
Copyright © 2020-2023  润新知