简账(开源记账软件)-解决AOP日志记录错误

567 阅读2分钟

这是我参与更文挑战的第2天,活动详情查看: 更文挑战

前言

在看日志表的时候,发现请求参数全是空的。就在想肯定是哪里出现问题了,下面就是我的解决问题的一些思路

往期链接

一、问题复现

经过测试,请求下面这个接口可以稳定重现此问题

image.png

数据库记录如下所示:

image.png

二、解决问题

既然能稳定重现,那么解决问题也就不会太困难了。

相关代码

使用AOP记录日志主要部分的代码如下所示: 切面类

@Component
@Aspect
@Slf4j
public class LogAspect implements Ordered {
    // 执行顺序,越小越先执行(遵从同心圆的概念)
    private final int order = 100;

    @Pointcut("@annotation(cn.jackbin.SimpleRecord.common.anotations.CommonLog)")
    public void doHandler(){

    }

    /**
     * 处理请求后执行
     */
    @AfterReturning(pointcut = "doHandler()", returning = "jsonResult")
    public void doAfterReturning(JoinPoint joinPoint, Object jsonResult) {
        handleLog(joinPoint, null, jsonResult);
    }

    @AfterThrowing(pointcut = "doHandler()", throwing = "e")
    public void doAfterThrowing(JoinPoint joinPoint, Exception e) {
        handleLog(joinPoint, e, null);
    }

    /**
     * 处理日志
     */
    private void handleLog(final JoinPoint joinPoint, final Exception e, final Object jsonResult) {
        try {
            // 获得自定义注解
            CommonLog annotationLog = getAnnotationLog(joinPoint);
            if (annotationLog == null)
            {
                return;
            }

            // 插入数据操作(已省略)
            
            // 异步保存日志
            AsyncManager.me().schedule(AsyncFactory.recordCommonLog(logDO));
        } catch (Exception exception) {
            log.error("日志记录异常,msg{}", exception.getMessage());
        }
    }

    @Override
    public int getOrder() {
        return order;
    }
}

获取请求参数的方法

/**
     * 获取请求的参数,放到log中
     *
     * @param commonLogDO 操作日志
     * @throws Exception 异常
     */
    private void setRequestValue(CommonLogDO commonLogDO) throws Exception
    {
        Map<String, String[]> map = ServletUtil.getRequest().getParameterMap();
        if (StringUtil.isNotEmpty(map))
        {
            PropertyPreFilters.MySimplePropertyPreFilter excludefilter = new PropertyPreFilters().addFilter();
            excludefilter.addExcludes(EXCLUDE_PROPERTIES);
            String params = JSONObject.toJSONString(map, excludefilter);
            commonLogDO.setRequestParam(StringUtil.substring(params, 0, 2000));
        }
    }

调试

先看下请求

image.png

打断点发现无法获取到Request Payload

image.png

经过实际测试才知道,原来request.getParameterMap() 只能获取到 form-data 中的参数
要在在SelectRequest中获取到 Request Payload 需要通过获取请求中的流,再解析出来

解决方案

踩坑

直接使用 req.getInputStream() 这种方式获取到流,在读取流的时候会报 Stream Closed 异常
所以要自己重写一个过滤器,重新将流放进去

实现如下图所示:

image.png

实现代码

从流中取数据的逻辑如下所示

    // 从流中读取请求数据
    ServletInputStream is;
    is = req.getInputStream();
    int nRead = 1;
    int nTotalRead = 0;
    byte[] bytes = new byte[10240];
    while (nRead > 0) {
        nRead = is.read(bytes, nTotalRead, bytes.length - nTotalRead);
        if (nRead > 0)
            nTotalRead = nTotalRead + nRead;
    }
    params = new String(bytes, 0, nTotalRead, StandardCharsets.UTF_8);

结果

现在已可以正确记录日志,结果如下图所示:

image.png

三、总结

以上代码均可在简账后端中找到

感谢看到最后,非常荣幸能够帮助到你~♥