Spring AOP 自定义注解 实现系统日志

前言

最近公司一个开发完成的项目 , 在使用过程中要开始统计工作量了 , 但是发现之前没有记录日志, 所以统计不出来, 要求增加上日志这个功能

就写一个注解来实现把!

  1. 第一步定义日志注解
import java.lang.annotation.*;

/**
 * @author kwokql
 * @version 2019/3/26 16:18
 */
@Documented
@Retention(RetentionPolicy.RUNTIME) /* 编译器将Annotation存储于class文件中,并且可由JVM读入 */
@Target(ElementType.METHOD) /* 声明方法注解  */
public @interface SysLogPoint {

    String dm(); 	// 操作类型代码
    String mc(); // 操作类型名称
    String id() default ""; 	// 被操作人id
    String xm() default ""; 	// 被操作人姓名
    boolean splite() default false; // 是否根据被操作人id splite 插入多条日志
    String bj() default ","; //splite 方法的 媒介

}

2.然后创建一个日志实体

import javax.persistence.*;
import java.io.Serializable;

/**
 * @Auther kwokql
 * @Date 2020/7/3 16:41
 * 日志对象
 */
public class SysLogModel implements Serializable {

    private Long id; //id
    private String czrid; // 操作人id
    private String czrxm; // 操作人姓名
    private String czsj; // 操作时间
    private String bczrid; // 被操作人id
    private String bczrxm; // 被操作人姓名
    private String czzt; // 本次操作状态
    private String czrfw; // 请求服务 0:app , 1:pc
    private String czlxmc; // 操作类型名称
    private String czlxdm; // 操作类型代码
    private String czcs; // 请求参数
	//getter setter ....
}

3.然后就是 注解的业务实现了 也是要根据自己的业务来修改 ::代码较多

import com.fasterxml.jackson.databind.JsonNode;
import com.triman.common.util.SecurityUtil;
import com.triman.syrkquery.aspect.util.FetchValueUtil;
import com.triman.syrkquery.dao.SysLogDao;
import org.apache.commons.lang3.StringUtils;
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.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ThreadPoolExecutor;

/**
 * @author kwokql
 * @version 2019/3/26 16:22
 * @info 保存操作日志
 */
@Component
@Aspect
public class SysLogAspect {

    @Autowired
    private SysLogDao sysLogDao; //db媒介
    @Autowired
    private  ThreadPoolExecutor executor;//线程池
    
    private static DateTimeFormatter ymdhms = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");


    /**
     * 正常返回处理
     *
     * @param jp     连接点
     * @param point  注解
     * @param result 返回结果
     */
    @AfterReturning(value = "@annotation(point)", returning = "result")
    public void afterReturn(JoinPoint jp, SysLogPoint point, Object result) {
        SysLogModel sysLog = buildLog(jp, point, result, null);
        sysLog.setCzzt("成功");
        saveLog(sysLog,point);
    }

    /**
     * 抛出异常的处理
     *
     * @param jp    连接点
     * @param point 注解
     * @param ex    异常对象
     */
    @AfterThrowing(value = "@annotation(point)", throwing = "ex")
    public void afterThrowing(JoinPoint jp, SysLogPoint point, Throwable ex) {
        SysLogModel sysLog = buildLog(jp, point, null, ex);
        sysLog.setCzzt("失败");
        saveLog(sysLog,point);
    }

    /**
     * 保存到DB媒介中
     *
     * @param sysLog
     */
    private void saveLog(SysLogModel g,SysLogPoint point) {
        if(g == null) return;
        List<SysLogModel> logs = new ArrayList<>();
        if(point.splite()){
            String[] rids = g.getBczrid().split(point.bj());
            for (String rid : rids) {
                g.setBczrid(rid);
                logs.add(g);
            }
        }else{
            logs.add(g);
        }
        executor.execute(new TaskRunnable(sysLogDao, logs));
    }



    /**
     * 构建日志对象
     *
     * @param jp     连接点
     * @param point  注解
     * @param result 处理结果对象
     * @param ex     处理异常对象
     * @return 日志日志对象
     */
    private SysLogModel buildLog(JoinPoint jp, SysLogPoint point, Object result, Throwable ex)  {
     try{
         // 获取RequestAttributes
         RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
         // 从获取RequestAttributes中获取HttpServletRequest的信息
         HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);

         SysLogModel log = new SysLogModel();
         //修改为自己的 操作人信息方法
         log.setCzrid(SecurityUtil.getUserInfo().getId());// 操作人id
         log.setCzrxm(SecurityUtil.getUserInfo().getUser_name());// 操作人姓名
         log.setCzsj(ymdhms.format(LocalDateTime.now()));// 操作时间
         log.setCzrfw("1");// 请求服务
         log.setCzlxdm(point.dm());// 操作类型代码
         log.setCzlxmc(point.mc());// 操作类型名称

		 // 请求参数
         Map<String, String> paramMap = converMap(request.getParameterMap());
         if (!paramMap.isEmpty()){
             String paramJson = FetchValueUtil.JSONtoString(paramMap);
             log.setCzcs(paramJson);
         }else{
             Object[] args = jp.getArgs();
             String paramJson = FetchValueUtil.JSONtoString(args);
             log.setCzcs(paramJson);
         }


		 // 被操作人id
         String id = point.id();
         if (StringUtils.isNotBlank(id)) {
         	 log.setBczrid(s == null ? id : s);
         }
         // 被操作人姓名
         String xm = point.xm();
         if (StringUtils.isNotBlank(xm)) {
             log.setBczrxm(s == null ? xm : s);
         }
         return log;
     }catch (Exception e){
         e.printStackTrace();
         return null;
     }
    }


    /**
     * 转换request 请求参数
     *
     * @param paramMap request获取的参数数组
     */
    public Map<String, String> converMap(Map<String, String[]> paramMap) {
        Map<String, String> rtnMap = new HashMap<>();
        for (String key : paramMap.keySet()) {
            rtnMap.put(key, paramMap.get(key)[0]);
        }
        return rtnMap;
    }

}

一般情况来说到这里就可以开始使用测试了, 但是我突然想起来
这次日志是需要获取请求的参数存入数据库的
而且因为这是一个已经开发完的项目 接受的参数没有规定 所以是传的五花八门

接口代码全部改一遍 不现实, 
但是我又不想重新写 try/catch的写法 
所以我写了一个 能像js取值一样的工具类:如下 

注意: 需要依赖 jackson


import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.io.IOException;
import java.lang.reflect.Array;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

/**
 * @info 模拟js表达式取值
 * @author gql
 * @Date 2020年7月9日10:53:38
 */
public class FetchValueUtil {

    public static void main(String[] args) {
     /*
        let json = [
            [ { rid: 1 },{ rid: 2 } ] ,  对象1
            { rid: 3 },  对象2
            [4,5,6], 对象3
            '张三-李四', 对象4
            9 对象5
        ]
        handleInput(json, 'args[0].{rid}')  return: [1,2]
        handleInput(json, 'args[1].rid')    return: 3
        handleInput(json, 'args[2]')        return: [4,5,6]
        handleInput(json, 'args[2][0]')     return: 4
        handleInput(json, 'args[3]')        return: '张三,李四'
        handleInput(json, 'args[4]')        return: 9
      */
    }

    //js表达式 存入数组中方便获取下一个表达式
    private static List<String> keys = new ArrayList<>();
    private static final ObjectMapper om = new ObjectMapper();

    public static String JSONtoString(Object obj) throws JsonProcessingException {
       return om.writeValueAsString(obj);
    }

    /**
     * 处理输入参数
     *
     * @param args 入参
     * @param params  obj.key.value
     * @return 特殊处理都的入参
     */
    public static JsonNode handleInput(Object[] args, String params) {
        try {
            if (isEmpty(params) || isEmpty(args)) return null;

            keys = new ArrayList(Arrays.asList(params.split("\\.")));
            JsonNode root = om.readTree(om.writeValueAsString(args));

            return handleSensitiveParams(root, 0);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
            keys = null;
        }
    }

    /**
     * 根据js表达式 获取值
     *
     * @param root     jackson节点
     * @param i 当前表达式下标
     */
    public static JsonNode handleSensitiveParams(JsonNode root, int i) {
        try {
            String param = keys.get(i);
            int arrRegex = arraysCount(param);
            if (arrRegex > 0) { //数组下标取值

                //递归处理 [0][1][2] 多维数组
                if(arrRegex > 1) {
                    JsonNode jsonNode = root.get(getArrs(i));
                    return handleSensitiveParams(jsonNode,i);
                }else{
                    String key = getArrRegex(param);
                    int index = isArrRegex(param);
                    JsonNode result;
                    if(!"args".equals(key)){
                        JsonNode jsonNode = fetchRootParam(root, key);
                        if (jsonNode == null || !jsonNode.isArray()) {
                            return null;
                        }
                        result = jsonNode.get(index);
                    }else{
                        result = root.get(index);
                    }
                    if (i == keys.size() - 1) {
                        return result;
                    }
                    return handleSensitiveParams(result, ++i);
                }

            } else {
                //key属性取值

                String objRegex = isObjRegex(param);
                if(!isBlank(objRegex) && root.isArray()){
                    Iterator<JsonNode> iterator = root.iterator();
                    List<String> strings = new ArrayList<>();
                    while (iterator.hasNext()){
                        JsonNode next = iterator.next();
                        JsonNode jsonNode = next.get(objRegex);
                        strings.add(jsonNode.asText());
                    }
                    return om.readTree(om.writeValueAsString(strings));

                }else if (root.isObject()) {//obj 处理
                    Iterator<Map.Entry<String, JsonNode>> rootIt = root.fields();
                    while (rootIt.hasNext()) {
                        Map.Entry<String, JsonNode> node = rootIt.next();
                        if (param.equals(node.getKey())) {
                            if (i == keys.size() - 1) {
                                return node.getValue();
                            }
                            return handleSensitiveParams(node.getValue(), ++i);
                        }
                    }
                }
            }
            return null;
        } catch (RuntimeException | IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据js表达式 获取 JsonNode 的值
     *
     * @param root  jackson节点
     * @param param
     */
    public static JsonNode fetchRootParam(JsonNode root, String param) {
        if (!root.isObject()) {
            return null;
        }
        Iterator<Map.Entry<String, JsonNode>> rootIt = root.fields();
        while (rootIt.hasNext()) {
            Map.Entry<String, JsonNode> node = rootIt.next();
            if (param.equals(node.getKey())) {
                return node.getValue();
            }
        }
        return null;
    }


    /**
     * 是否为数组表达式
     * @param string key[0]
     * @return 0
     */
    public static int isArrRegex(String string) {
        try {
            String result = matcher("[\\[0-9*\\]]", string);
            if (result != null && !isBlank(matcher("[0-9*]", result))) {
                return Integer.parseInt(Objects.requireNonNull(matcher("[0-9*]", result)));
            }
            return 0;
        } catch (RuntimeException e) {
            //e.printStackTrace();
            return 0;
        }
    }

    /**
     * 是否为取值表达式
     * @param string key.{rid}
     * @return 0
     */
    public static String isObjRegex(String string) {
        try {
            String result = matcher("[\\{.*\\}]", string);
            if (result != null && !isBlank(matcherInsensitive("[a-z]{1,99}", string))) {
                return matcherInsensitive("[a-z]{1,99}", string);
            }
            return "";
        } catch (RuntimeException e) {
            //e.printStackTrace();
            return "";
        }
    }

    /**
     * 获取为js的数组表达式的key
     * @param string key[0]
     * @return key
     */
    public static String getArrRegex(String string) {
        try {
            return string.substring(0, string.indexOf("["));
        } catch (RuntimeException e) {
            //e.printStackTrace();
            return null;
        }
    }

    /**
     * 解决多维数组的取值 下一次的表达式会在keys中改变
     * @param i key[0][1]
     * @return 1
     */
    public static int getArrs(int i) {
        try {
            String string = keys.get(i);
            int start = string.indexOf("[");
            int end = string.indexOf("]");
            String k = string.substring(0, start);
            String j = string.substring(end + 1);
            keys.remove(i);
            keys.add(i,k+j);
            return Integer.parseInt(string.substring(start+1, end));
        } catch (RuntimeException e) {
            //e.printStackTrace();
            return 0;
        }
    }


    /**
     * 根据正则表达式获取字符串中匹配的值
     * @param regex  正则
     * @param string 字符串
     * @return 匹配的字符串
     */
    public static String matcher(String regex, String string) {
        try {
            Pattern p = Pattern.compile(regex);
            Matcher m = p.matcher(string);
            StringBuilder result = new StringBuilder();
            while (m.find()) {
                result.append(m.group());
            }
            return String.valueOf(result);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 根据正则表达式获取字符串中匹配的值
     * @param regex  正则
     * @param string 字符串
     * @return 匹配的字符串
     */
    public static String matcherInsensitive(String regex, String string) {
        try {
            Pattern p = Pattern.compile(regex,Pattern.CASE_INSENSITIVE);
            Matcher m = p.matcher(string);
            StringBuilder result = new StringBuilder();
            while (m.find()) {
                result.append(m.group());
            }
            return String.valueOf(result);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 判断[]的个数 确定需要递归几次数组
     * @param string 字符串
     * @return 匹配的字符串
     */
    public static int arraysCount(String string) {
        try {
            char[] chars = string.toCharArray();
            int start = 0;
            int end = 0;
            for (char aChar : chars) {
                String s = String.valueOf(aChar);
                if("[".equals(s)){
                    start++;
                }
                if("]".equals(s)){
                    end++;
                }
            }
            return Math.min(start, end);
        } catch (Exception e) {
            e.printStackTrace();
            return 0;
        }
    }


    /**
     * 判断对象是否为空
     * @param obj
     * @return 是否为空
     */
    public static boolean isEmpty(Object obj) {
        if (obj == null) {
            return true;
        } else if (obj instanceof Optional) {
            return !((Optional)obj).isPresent();
        } else if (obj instanceof CharSequence) {
            return ((CharSequence)obj).length() == 0;
        } else if (obj.getClass().isArray()) {
            return Array.getLength(obj) == 0;
        } else if (obj instanceof Collection) {
            return ((Collection)obj).isEmpty();
        } else {
            return obj instanceof Map && ((Map) obj).isEmpty();
        }
    }

    /**
     * 判断字符串是否为空
     * @param cs
     * @return
     */
    public static boolean isBlank(CharSequence cs) {
        if (cs != null && cs.length() != 0) {
            for(int i = 0; i < cs.length(); ++i) {
                if (!Character.isWhitespace(cs.charAt(i))) {
                    return false;
                }
            }
        }
        return true;
    }


}

测试结果如下

       [
			[{ rid: 1 }, { rid: 2 }],
			{ rid: 3 },
			[4, 5, 6],
			'张三-李四',
			9
		]
		handleInput(json, 'args[0].{rid}')  return: [1,2]  //{} == 获取一个数组中指定的属性 返回数租
        handleInput(json, 'args[1].rid')    return: 3
        handleInput(json, 'args[2]')        return: [4,5,6]
        handleInput(json, 'args[2][0]')     return: 4
        handleInput(json, 'args[3]')        return: '张三,李四'
        handleInput(json, 'args[4]')        return: 9

加入日志中

		 // 被操作人id
         String id= point.id();
         if (StringUtils.isNotBlank(id)) {
             String s = String.valueOf(FetchValueUtil.handleInput(jp.getArgs(), id));
             log.setBczrxm(s == null ? xm : s);// 被操作人id
         }

下面是使用

    @RequestMapping(value = "/removeTag/{id}", method = RequestMethod.GET)
    @SysLogPoint(dm = "***", mc = "***",id = "args[0]")
    public Object search(@PathVariable("id") Integer id) throws Exception 


    @RequestMapping(value = "/addTag", method = RequestMethod.POST)
    @SysLogPoint(dm = "***", mc = "***", id = "args[1]")
    public Object search(@RequestParam("bqcode") String bqcode, @RequestParam("rid") String rid) throws Exception {


 
    @RequestMapping(value = "/rjbxx", method = RequestMethod.POST)
    @SysLogPoint(dm = "***", mc = "***", id = "args[0].rid")
    public Object search(@RequestBody TRjbxx trjbxx) throws Exception 

不出问题的话到这里就结束了 接下来就可以去 测试了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值