0
点赞
收藏
分享

微信扫一扫

开发问题--Dubbo服务日志记录

生命中最美的是成长 2022-04-01 阅读 65
java

开发问题记录

需求描述:甲方要求能看到用户的行为记录,需要给现有的所有业务添加操作日志记录,记录当前用户做的是什么业务,使用的什么功能,操作数据的id,业务执行是否成功以及操作时间。(数据会定期删除,并且用户量不是很大,记录字段也不长,不会有数据量过大的问题)

1. 使用SpringBoot拦截器intercepter

解决思路:因为使用的Dubbo调用(这里其实是个坑,思路从一开始就错了),所以不能使用过滤器,选择使用拦截器进行逻辑编写,因为controller层的每个类和方法都添加swagger的注解,所以可以通过反射获取到执行的业务和功能、用户和操作的数据id可以通过请求参数进行获取、业务是否成功可以通过返回的结果封装类进行获取、操作时间可以通过数据库自动生成。业务流程大概如下。

流程

思路应该可以说是很清晰的,但是实践的时候才发现还是自己太天真。

遇到的问题

  • HttpServletRequest的流只能获取一次,也就是说如果我在拦截器中获取了,那么在业务层获取到的参数就为空。
  • HttpServeletResponse只能使用一种流,不能同时存在两种流(getWritegetOutputStream两个方法互斥),而在其他的地方已经使用了getOutputStream了,也就意味着我也只能使用getOutputStream,但是我似乎没办法通过这个流获取到结果对象。。
  • 其实最后我才发现,我们的服务是Dubbo调用,虽然在调用接口时拦截器确实会被执行,但是HttpServletRequestHttpServeletResponse中根本就没有我要的参数和结果。。。。不过这一次错误思路也让我对拦截器有了新的理解。

2. 使用Dubbo过滤器

解决思路:其实大体和用拦截器解决的思路相同,只是说变成了Dubbo过滤而已。

遇到的问题

  • 我似乎没有办法通过invoker获取到调用的类,也就没有办法获取类上的注解。
  • 不知道什么原因,Dubbo过滤器未生效,直到最后也没有解决。

3. 最终解决方案AOP

这是最终解决方案,其实三个方法的思路都是一致的,只是采用的手段不一样。

解决思路:定义一个切面,在controller层的所有返回类型为ResponseBase(结果类型)的方法的前后执行逻辑。

代码实现(感觉代码写的不是很好,后续继续优化,如果有好的建议或解决方案欢迎指导):

@Aspect
@Component
public class UserOperationLogAspect {
    private final String sql = "INSERT INTO LOG_USER_OPERATION (USER_NAME, TOKEN, BUSINESS_NAME, OPERATION_NAME, RESPONSE_STSTUS) VALUES (?, ?, ?, ?, ?)";

    // controller层
    @Around("execution(antu.microservice.ResponseBase antu.jsydsp.provider..*.*(..))")
    public Object addOperationLog(ProceedingJoinPoint pjp) throws Throwable {
        UserOperationLog operationLog = new UserOperationLog();
        // 目标对象Class
        Class<?> aClass = pjp.getTarget().getClass();
        Object target = aClass;
        // 方法参数
        Object[] args = pjp.getArgs();
        // 方法标识
        MethodSignature signature = (MethodSignature)pjp.getSignature();
        // 方法
        Method method = aClass.getMethod(signature.getName(), signature.getParameterTypes());

        if (aClass.isAnnotationPresent(Api.class)){
            Api api = aClass.getAnnotation(Api.class);
            operationLog.setBusinessName(Strings.isNotBlank(api.value()) ? api.value() : api.description());
        }
        if (method.isAnnotationPresent(ApiOperation.class)){
            ApiOperation apiOperation = method.getAnnotation(ApiOperation.class);

            operationLog.setOperationName(apiOperation.value());
        }
        Annotation[][] parameterAnnotations = method.getParameterAnnotations();
        for (int i = 0; i < parameterAnnotations.length; i++) {
            for (Annotation annotation : parameterAnnotations[i]) {
                if (annotation instanceof ApiParam){
                    ApiParam apiParam = (ApiParam) annotation;
                    if ("token".equals(apiParam.value())){
                        operationLog.setToken(args[i].toString());
                    }

                    if ("IID".equals(apiParam.value()) || "iid".equals(apiParam.value())){
                        operationLog.setIid(args[i].toString());
                    }
                }
            }
        }

        Object responseBase = pjp.proceed();
        try{
            ResponseBase response = (ResponseBase) responseBase;
            operationLog.setStatus(response.getStatus());
        }catch (ClassCastException e){
            System.out.println("当前方法非controller: " + signature.getName());
        }

        // 存库
        Database db = new Database();
        db.executeUpdate(sql, operationLog.getUserName(), operationLog.getToken(), operationLog.getBusinessName(), operationLog.getOperationName(), operationLog.getStatus());

        return responseBase;
    }
}
举报

相关推荐

0 条评论