ThreadLocal线程本地变量在dubbo服务使用时候遇到的一个坑

我昨天遇到一个问题,就是我springboot项目里面有一个提供代办服务审核的dubbo接口,这个接口给房源项目调用,但是碰到一个问题就是,房源项目每天凌晨5点会查询满足条件过期的数据,然后调用我这边的代办审核dubbo接口,将这个代办任务变成自动拒绝

复制代码
@Override
@Transactional
public WorkListDataResult auditWorkList(WorkListAuditCmd workListAuditCmd, WorkListLoginUserVo loginUserVo) {
    log.info("auditWorkList WorkListAuditCmd={},WorkListLoginUserVo={}", JSON.toJSONString(workListAuditCmd), JSON.toJSONString(loginUserVo));
    LoginUserUtil.setCurrentUser(loginUserVo);
    WorkListAuditAdapterCmd workListAuditAdapterCmd = AutoMapper.transform(WorkListAuditAdapterCmd.class, workListAuditCmd);
    workListAuditAdapterCmd.setAuditComments(AuditStatusEnum.PASS.getCode().equals(workListAuditCmd.getAuditStatus()) || AuditStatusEnum.PENDING_REEVALUATION.getCode().equals(workListAuditCmd.getAuditStatus()) ? "系统通过" : "系统拒绝");
    workListAuditAdapterCmd.setExtAuditComments(workListAuditAdapterCmd.getAuditComments());
    auditWorkListInternal(workListAuditAdapterCmd);

    WorkListAuditReq workListAuditReq = AutoMapper.transform(WorkListAuditReq.class, workListAuditCmd);
    Boolean needCallback = workListAuditAdapterCmd.getNeedCallback();
    if (needCallback != null && needCallback) {
        //如果dubbo接口里面参数需要回调则回调
        // 注册事务同步器,在事务提交后执行异步任务
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                // 确保数据提交后执行异步任务
                CompletableFuture.runAsync(() -> pushAuditResultToBusiness(workListAuditReq), threadPoolExecutor);
            }
        });
    }
    return new WorkListDataResult();
}

这个定时任务调用这个rpc接口时候,第二个参数WorkListLoginUserVo是传的null,这里到后面审核的时候会根据这个 LoginUserUtil.getCurrentUser 判断不为空,就不会进入权限校验的逻辑

复制代码
protected void auditMultiProcessWorkTaskInternal(WorkListAuditAdapterCmd workListAuditAdapterCmd) {
    log.info("auditMultiProcessWorkTaskInternal WorkListAuditAdapterCmd={}",JSON.toJSONString(workListAuditAdapterCmd));
    //校验是否有审核的
    WorkListRespVo workListInfo = workListQueryService.getWorkListInfoByKeyId(workListAuditAdapterCmd.getWorkListKeyId());

    setAuditInfo(workListAuditAdapterCmd);
    Integer auditStatus = workListInfo.getAuditStatus();
    LoginUser currentUser = Identify.getCurrentUser();
    if(AuditStatusEnum.PENDING_REVIEW.getCode().equals(auditStatus)){
        workListAuditAdapterCmd.setFirstAuditDeptKeyId(currentUser == null ? workListAuditAdapterCmd.getAuditDeptKeyId() : currentUser.getDepartmentKeyId());
        workListAuditAdapterCmd.setFirstAuditUserKeyId(currentUser == null ? workListAuditAdapterCmd.getAuditUserKeyId() : currentUser.getUserKeyId());
        workListAuditAdapterCmd.setAuditStatus(AuditStatusEnum.PASS.getCode().equals(workListAuditAdapterCmd.getAuditStatus()) || AuditStatusEnum.PENDING_REEVALUATION.getCode().equals(workListAuditAdapterCmd.getAuditStatus()) ? AuditStatusEnum.PENDING_REEVALUATION.getCode() : AuditStatusEnum.REFUSE.getCode());
        workListAuditAdapterCmd.setFirstAuditTime(LocalDateTime.now());
    }else {
        workListAuditAdapterCmd.setSecondAuditUserKeyId(currentUser == null ? workListAuditAdapterCmd.getAuditUserKeyId() : currentUser.getUserKeyId());
        workListAuditAdapterCmd.setSecondAuditDeptKeyId(currentUser == null ? workListAuditAdapterCmd.getAuditDeptKeyId() : currentUser.getDepartmentKeyId());
        workListAuditAdapterCmd.setSecondAuditTime(LocalDateTime.now());
        workListAuditAdapterCmd.setFirstAuditTime(null);
        workListAuditAdapterCmd.setFirstAuditDeptKeyId(null);
        workListAuditAdapterCmd.setFirstAuditUserKeyId(null);
    }
    log.info("auditMultiProcessWorkTaskInternal WorkListAuditAdapterCmd={},WorkListRespVo={}",JSON.toJSONString(workListAuditAdapterCmd),JSON.toJSONString(workListInfo));
    // 获取权限代码
    setPermissionCode(workListAuditAdapterCmd, workListInfo);

    //校验是否有审核权限,排除房源定时Job调用的情况
    if(currentUser != null && StringUtils.isNotBlank(currentUser.getUserKeyId()) && StringUtils.isNotBlank(currentUser.getRoleKeyId())){
        checkAuditPermission(currentUser,workListAuditAdapterCmd,workListInfo);
    }
    //更新代办任务数据
    updateWorkList(workListAuditAdapterCmd);
}

生产上面却发现了一个问题,就是有一定概率会有些数据进行了权限校验,也就是currentUser不为空了,后面跟踪房源那边代码才发现,那边调用这个审核的dubbo接口,会调用另外一个查询的rpc接口,

复制代码
public WorkListDataResult<List<WorkListRespDto>> getWorkListStatusByCondion(WorkListStatusReq req, WorkListLoginUserVo loginUserVo) {
    log.info("getWorkListStatusByCondion WorkListStatusReq={},WorkListLoginUserVo={}", JSON.toJSONString(req), JSON.toJSONString(loginUserVo));
    LoginUserUtil.setCurrentUser(loginUserVo);
    WorkListDataResult<List<WorkListRespDto>> result = new WorkListDataResult<>();
    if (StringUtils.isBlank(req.getApplyType())) {
        result.setFlag(false);
        result.setErrorMessage("待办类型不能为空!");
        return result;
    }
    if (StringUtils.isBlank(req.getPropertyKeyId())) {
        result.setFlag(false);
        result.setErrorMessage("房源ID不能为空!");
        return result;
    }
    List<WorkListRespDto> listResps = new ArrayList<>();
    List<UserByIdOrNumberRpcDto> userList = new ArrayList<>();
    //用于存放用户ID的集合
    List<String> userKeyIdList = new ArrayList<>();
    List<DepartmentByIdOrNoRpcDto> deptList = new ArrayList<>();
    //用于存放部门ID的集合
    List<String> deptIdList = new ArrayList<>();
    //将空值置为null
    SpringBeanUtil.convertEmptyToNull(req);

    WorkListSearchVo workListSearchVo = AutoMapper.transform(WorkListSearchVo.class, req);
    log.info("getWorkListStatusByCondion WorkListSearchVo={}", JSON.toJSONString(workListSearchVo));
    List<WorkListRespVo> list = workListQueryService.getWorkListStatusByCondion(workListSearchVo);
    log.info("getWorkListStatusByCondion list.total=" + list.size());
    if (list != null && list.size() > 0) {
        for (WorkListRespVo viewResp : list) {
            if (viewResp != null) {
                //设置userId和deptId集合
                setDeptOrUserList(viewResp, userKeyIdList, deptIdList);
            }
        }
        if (userKeyIdList != null && userKeyIdList.size() > 0) {
            //批量查询用户信息
            userList = userAndDeptDubboService.queryUserByIdOrNumber(userKeyIdList);
            log.info("getWorkListStatusByCondion userList={}", userList.toString());
        }
        if (deptIdList != null && deptIdList.size() > 0) {
            //批量查询部门信息
            deptList = userAndDeptDubboService.queryByKeyIdOrNo(deptIdList);
            log.info("getWorkListStatusByCondion deptList={}", deptList.toString());
        }
        for (WorkListRespVo viewResp : list) {
            //设置枚举描述
            setWorkListVoDesc(viewResp);
            //设置待办对象用户和部门信息
            setWorkListRespVoDeptOrUser(viewResp, userList, deptList);
            WorkListRespDto workListRespVo = AutoMapper.transform(WorkListRespDto.class, viewResp);
            listResps.add(workListRespVo);
        }
    }
    result.setFlag(true);
    result.setData(listResps);
    return result;
}

这个查询的接口也进行了LoginUserUtil.setCurrentUser(loginUserVo),这段代码会放到ThreadLocal 的线程本地变量里面

而整个这个查询和审核的接口都没有进行这个ThreadLocal线程变量的清除,因为dubbo提供的rpc接口,本质上是使用了线程池技术,会复用一些线程,比如说19号先执行了这个查询代办任务的rpc接口,这个时候设置了currentUser,然后后面调用这个代办审核的接口时候,又刚好拿到了这个19号线程,这个时候通过getCuurentUser拿到的用户就不为空,然后就进入了权限校验的逻辑,解决办法通过自定义dubbo的过滤器,在过滤器里面完成资源的清除

相关推荐
萧瑟余晖6 天前
Dubbo详解篇四:注册中心与服务发现
服务发现·dubbo
Star(up up up)7 天前
Docker系列---Docker容器基本命令(新手小白保姆级实操教程)
运维·学习·程序人生·青少年编程·docker·容器·dubbo
ai_coder_ai7 天前
在自动化脚本中调用大语言模型
语言模型·自动化·dubbo
南楼旧雨8 天前
深度解析宝塔插件开发:从底层逻辑到实战定制(附独立开发案例)
dubbo
镜子AI8 天前
镜子AI 内容方法论:GEO 到底怎么起作用,以及一份四步操作清单
人工智能·dubbo
百度一见11 天前
百度朱名发:让现场治理标准与经验,成为持续进化的组织资产
dubbo
海的透彻15 天前
如何让docker容器启动时用自定义的用户
java·docker·dubbo
骑着蜗牛撵大象32716 天前
飞牛fnOS部署Damselfly:Compose编排、照片目录与7100固定访问
java·docker·rpc·dubbo·fnos·照片管理·damselfly
用户85634995443319 天前
从 HTTP 到 Java Provider:Apache Dubbo Admin 泛化调用的参数契约与跨语言 E2E 实践
dubbo
Wang's Blog21 天前
Java 项目实战: 交友项目 - 完善用户信息章节复盘:套路清单与自检清单
java·dubbo·交友