[讨论]当自定义的注解用在异步方法上时,如何在Spring AOP切面恰当的位置获取到请求头里token等信息

@Ta 04-02 11:22发布,04-02 12:44修改 8475点击

SpringBoot项目,项目中早已整合了一套Spring AOP切面相关技术实现,该有的流程都有,常规操作就是给方法加上自定义注解就可以在被调用时,根据当前请求头获取到用户信息(token)、请求IP地址等信息,然后分两种情况写入数据库日志表:

  • 1、正常请求日志,每次该方法被调用时都会被记录入库;
  • 2、当抛出异常时,额外记录一条异常日志信息(与1同表);

上面文字提到的内容是系统架构中目前已经实现的一整套相关业务的具体实现。但是...

但是现在有个新的需求,如题,当我尝试将该现有的日志注解写在主线程中的“某个异步方法”时,切面日志类在写入数据库之前有个“获取请求头”信息的的操作理所当然的报错了。

这两天我没少查阅资料,为了存请求头信息,考虑过redis、MQTT、MySQL在记录一条请求头的记录等等,都被我推翻了。目前我采用写死的方案:

  • 1、我假设一个不存在的异步方法操作员的用户的信息,定义好这个用户信息Object的所有预设属性值。
  • 2、请求方式既不是GET也不是POST,由于写入数据库日志表的时候这个字段是String,我直接预设“Async”。
  • 3、还缺什么信息我全部都预设...

然后我将当前的日志切面类(LogAspect)抽离成父类(BaseLogAspect),再定义一个异常日志切面类(LogAsyncAspect),分别继承自父类(BaseLogAspect),其中LogAspect留空不写,完整继承父类;LogAsyncAspect则是部分继承,部分重写父类方法,这里我重写了获取用户请求头信息的那个方法,我不再通过String userStr = request.getHeader(AuthConstant.USER_TOKEN_HEADER);获取,而是改成用我上面预设的用户信息,请求ip用localhost;(以上还在撸代码,执行结果暂时未知是否符合当前预期)

/**
 * @apiNote 日志异步切面<br>
 * 部分继承 BaseLogAspect 抽象类,部分重写。
 * @since 2024年3月29日
 * @author TabKey9
 * @version 0.0.1.240329
 */
@Slf4j
@Aspect
@Component
public class LogAsyncAspect extends BaseLogAspect {

    @Override
    protected void handleLog(final JoinPoint joinPoint, Log controllerLog, final Exception e, Object jsonResult) {
        try {
            // 虚拟一个异步线程专用账号信息
            UserDto userDto = new UserDto();
            userDto.setId(-1L);
            userDto.setUsername("异步操作专员");
            // MySQL 日志记录表实体对象
            SysOperLogEntity operLog = new SysOperLogEntity();
            // 操作状态
            operLog.setStatus(BusinessStatus.SUCCESS.ordinal());
            // 请求的地址 【获取不到】
            operLog.setOperIp("localhost");
            // 请求url 【获取不到】
            operLog.setOperUrl("/**");
            // 操作人员
            operLog.setOperName(userDto.getUsername());
            if (e != null) {
                operLog.setStatus(BusinessStatus.FAIL.ordinal());
                operLog.setErrorMsg(e.getMessage());
            }
            // 设置方法名称
            String className = joinPoint.getTarget().getClass().getName();
            String methodName = joinPoint.getSignature().getName();
            operLog.setMethod(className + "." + methodName + "()");
            // 设置请求方式
            operLog.setRequestMethod("Async");
            // 处理设置注解上的参数 【调用父类方法】
            getControllerMethodDescription(joinPoint, controllerLog, operLog, jsonResult);
            // 设置消耗时间 【TIME_THREADLOCAL在父类用protected修饰符定义】
            operLog.setCostTime(System.currentTimeMillis() - TIME_THREADLOCAL.get());
            operLog.setOperTime(new Date());
            // 保存数据库 【sysOperLogService在父类用protected修饰符定义】
            sysOperLogService.save(operLog);
        } catch (Exception exp) {
            // 记录本地异常日志
            log.error("异常信息:{}", exp.getMessage());
            exp.printStackTrace();
        } finally {
            //【TIME_THREADLOCAL在父类用protected修饰符定义】
            TIME_THREADLOCAL.remove();
        }
    }
}

请问:我的做法合理吗?或者有哪里考虑不周吗?以及还有其它解决方案嘛?

回复列表(4|隐藏机器人聊天)
  • HW
    @Ta / 04-02 18:29 / /
    ThreadLocal
  • @Ta / 04-02 19:24 / /
    我记得切面不是有个前置的实现吗,试试可行不
  • @Ta / 04-03 10:06 / /
    @沉迷幻境,试不了,我对切面不熟,改造了一个,但是都不知道怎么验证(触发)生效没有。

    当我了解越深入越觉得超纲了,目前我技术不够扎实,硬要把这需求实现起来不仅麻烦,还有一堆可能存在却还是捉摸不透的隐患。改用最传统的方式实现了,try/catch,调异步方法的时候,传入请求头信息,在catch的时候自己写(写一条错误日志到MySQL),或者在finally的时候,虽然简单粗bao,但自我感觉安全可控。
  • @Ta / 04-03 10:19 / /

    @HWThreadLocal在同一线程下还有这样的妙用,感谢分享,但是因为切面那一块知识点整不明白,玩不转,这个需求我得简化实现了

添加新回复
回复需要登录