使用AOP记录用户请求参数

起因

在生产环境中,遇到报错,后面通过查找,可以找到报错的接口或者方法,但是如果没有参数,则无法重现问题,因此记录参数请求,对于重复问题非常重要

需求

1、打印请求的方法及参数
2、用户密码相关以及List类型(太长)的参数不打印

实现过程

1、添加aop类,切面方式使用execution

@Slf4j
@Aspect
@Component
public class ControllerLog {
    private static final ThreadLocal<Long> START_TIME_THREAD_LOCAL =
            new NamedThreadLocal<>("ThreadLocal StartTime");

    private static final ThreadLocal<Integer> ID_THREAD_LOCAL =
            new NamedThreadLocal<>("ThreadLocal ID");

    private static final ThreadLocal<String> LOG_PREFIX_THREAD_LOCAL =
            new NamedThreadLocal<>("ThreadLocal LogPrefix");

    /**
     * <li>Before       : 在方法执行前进行切面</li>
     * <li>execution    : 定义切面表达式</li>
     * <p>public * com.example.javadevelopmentframework.javadevelopmentframework.controller..*.*(..))
     * <li>public :匹配所有目标类的public方法,不写则匹配所有访问权限</li>
     * <li>第一个* :方法返回值类型,*代表所有类型 </li>
     * <li>第二个* :包路径的通配符</li>
     * <li>第三个..* :表示impl这个目录下所有的类,包括子目录的类</li>
     * <li>第四个*(..) : *表示所有任意方法名,..表示任意参数</li>
     * </p>
     *
     * @param
     */
    @Pointcut("execution(public * XXX.XXX.XXX.*.*.controller..*.*(..))")
    public void exectionMethod() {
    }


    @Before("exectionMethod()")
    public void doBefore(JoinPoint joinPoint) {
        START_TIME_THREAD_LOCAL.set(System.currentTimeMillis());
        // 接收到请求,记录请求内容
        StringBuilder argsDes = new StringBuilder();
        //获取方法名
        String className = joinPoint.getSignature().getDeclaringType().getSimpleName();
        String methodName = joinPoint.getSignature().getName();
        String logPrefix = className + "." + methodName + "()";
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Object[] args = joinPoint.getArgs();
        Method method = signature.getMethod();
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        if (args != null && paramNames != null) {
            argsDes.append(LogUtil.getMethodParams(signature, args, paramNames));
        }
        LOG_PREFIX_THREAD_LOCAL.set(logPrefix);
        int id = RandomUtil.randomInt();
        ID_THREAD_LOCAL.set(id);
        log.info("id={} =========开始请求:{},参数:{}", id, logPrefix, argsDes.toString());
    }

    @AfterReturning(pointcut = "exectionMethod()", returning = "rtn")
    public Object doAfter(Object rtn) {
        long endTime = System.currentTimeMillis();
        long begin = START_TIME_THREAD_LOCAL.get();
        if (rtn instanceof R) {
            R result = (R) rtn;
            if (result.getErrcode() != HttpStatus.OK.value()) {
                log.warn("请求异常:{},响应结果:{}", getLogPrefix(), rtn);
            }
        }
        log.info("id={} =========结束请求:{},耗时:{} ms", ID_THREAD_LOCAL.get(), getLogPrefix(), endTime - begin);
        destroyThreadLocal();
        return rtn;
    }

    private static String getLogPrefix() {
        return LOG_PREFIX_THREAD_LOCAL.get();
    }

    private static void destroyThreadLocal() {
        START_TIME_THREAD_LOCAL.remove();
        LOG_PREFIX_THREAD_LOCAL.remove();
        ID_THREAD_LOCAL.remove();
    }
}

2、LogUtil.getMethodParams方法实现

	public static StringBuilder getMethodParams(MethodSignature signature, Object[] args, String[] paramNames) {
        StringBuilder params = new StringBuilder();
        //密码类型的信息,不打印出来
        PasswordType passwordType = signature.getMethod().getAnnotation(PasswordType.class);
        if (null == passwordType) {
            for (int i = 0; i < args.length; i++) {
                //参数类型为List的参数也不打印值
                if (args[i] instanceof List) {
                    params.append(paramNames[i]).append("=").append("【List类型的参数不打印】").append(",");
                } else {
                    params.append(paramNames[i]).append("=").append(args[i]).append(",");
                }

            }
        } else {
            String[] fieldNames = passwordType.fieldNames();
            for (int i = 0; i < args.length; i++) {
                Map<String, Object> map = JsonUtil.objectToMap(args[i]);
                for (String fieldName : fieldNames) {
                    if (map != null && map.containsKey(fieldName) && null != map.get(fieldName)) {
                        map.put(fieldName, "*");
                    }
                }
                params.append(paramNames[i]).append("=").append(map).append(",");
            }
        }
        return params;
    }

3、 @PasswordType注解,默认是password,但是有可能会是其他的命名

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface PasswordType {
    String[] fieldNames() default {"password"};
}

4、@PasswordType使用示例
修改密码,json传参

	@PasswordType(fieldNames = {"password","newPassword"})
    @PostMapping("/editPwd")
    public R changePwd(@Valid @RequestBody UserChangePwdParamDTO dto) {
       
    }

效果

04-09 15:24:12.245 INFO  [xxx.xxx.xxx.aop.ControllerLog] - id=1638566979 =========开始请求:XXXXController.getSetting(),参数:yxH=10572,
04-09 15:24:12.255 INFO  [xxx.xxx.xxx..aop.ControllerLog] - id=1638566979 =========结束请求:XXXXController.getSetting(),耗时:11 ms

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值