当前位置: 首页 > 后端技术 > Java

异步方法获取登录用户时出现问题

时间:2023-04-01 23:02:14 Java

重写分配任务算法时,需要获取当前登录用户区及其子区。改写后测试时,发现任务直接卡在了分配中。查看后台发现日志有错误。根据错误报告,我可以看到错误在哪里抛出。可以发现在调用getAuthUserDetailWithoutTransaction方法的时候出现了问题。进一步查看这个方法:publicOptionalgetAuthUserDetailWithoutTransaction(){logger.debug("根据认证Username获取当前登录,获取用户");身份验证authentication=SecurityContextHolder.getContext().getAuthentication();if(authentication!=null){AuthUserDetailsuserDetail;if(authenticationinstanceofUsernamePasswordAuthenticationToken){userDetail=(AuthUserDetails)authentication.getPrinciple);}elseif(authenticationinstanceofAuthUserDetails){userDetail=(AuthUserDetails)authentication;}elseif(authenticationinstanceofAnonymousAuthenticationToken){returnOptional.empty();}else{thrownewRuntimeException("获取类型不正确");}返回Optional.of(userDetail);}logger.debug("认证用户在数据库中不存在");returnOptional.empty();}根据后台日志输出,认证用户在数据库中不存在,所以可以断定认证为null,然后我尝试以debug模式运行后台进一步确认,发现在后台运行过程中经常用到上面的函数,获取到的认证不为null,只有分配任务时返回的认证为null,你从下面的简图可以知道函数调用关系:又因为报错信息中包含Unexpectedexceptionoccurredinvokingasyncmethod(调用异步方法时发生意外异常),所以我尝试去掉addTaskDetailsAndAddFormItemValuesAndUpdateStatistics方法的异步注解,并且重新执行后发现一切正常,没有报错,于是同时检查了SecurityContextHolder.getContext()和@Async。问题出现了,发现在@Async方法中使用SecurityContextHolder.getContext()会返回null。测试:运行结果:即调用异步函数会返回null,同时我们也可以发现我们当前的默认线程是nio-8081-exec-8,但是异步函数会新建一个task-1线程,并且无法在新线程中获取安全上下文。可以推测,安全上下文默认只会存在于主线程中,不会随着新线程的创建而传递过来。SecurityContextHolder用于存储安全上下文(securitycontext)信息。当前操作的用户是谁,用户是否经过认证,他有什么角色权限……这些都保存在SecurityContextHolder中。SecurityContextHolder默认使用ThreadLocal策略来存储认证信息。这意味着这是一个线程绑定策略。SpringSecurity在用户登录时自动将认证信息绑定到当前线程,在用户注销时自动清除当前线程的认证信息。如果我们在getAuthUserDetailWithoutTransaction中设置securitycontextpolicy,我们可以解决这个问题,但是这可能会影响到其他调用这个方法的方法,所以我们不得不另辟蹊径。publicOptionalgetAuthUserDetailWithoutTransaction(){logger.debug("根据认证获取当前登录用户名,获取用户");//设置可继承的本地线程策略,使其可以在异步方法中调用SecurityContextHolder.setStrategyName(SecurityContextHolder.MODE_INHERITABLETHREADLOCAL);...}一开始我以为只需要在这个异步方法中设置策略,但是经过测试发现策略不会随着方法调用一起传递。于是尝试在M层直接获取当前登录用户,将登录用户传给异步方法,再次尝试时又出现了新的错误。failedtolazilyinitializeacollectionofrole:club.yunzhi.smartcommunity.entity.District.children,couldnotinitializeproxy-noSession经过搜索,发现Hibernate在进行关联处理时通常使用延迟加载,可以避免多次传输的大量数据。也就是说,我们在Aservice中获取了用户,用户对应的分区域可以在Aservice中调用,但是如果将用户传递给Bservice,那么如果要调用的分区域用户对应的区域,会报错。源服务:接受传过来的服务:也就是说我们在传参的时候可以直接传对应的id来避免这种情况,然后根据id通过仓库层获取我们的数据。也就是说我们可以改成下面的方式来达到目的publicvoidtaskAssign(){。..OptionaloptionalWebUserId=this.webUserService.getCurrentLoginWebUserId();LongwebUserId=optionalWebUserId.orElse(null);//异步生成任务详情和表单,并更新各区域的统计信息。this.taskDetailAsyncService.addTaskDetailsAndAddFormItemValuesAndUpdateStatistics(task,residents,specification,webUserId);}ListgetManageDistrictsWithCurrentLoginUser(Long...webUserId);publicfinalListgetManageDistrictsWithCurrentLoginUserId){Long...OptionaloptionalUser;if(webUserId.length==0){optionalUser=this.webUserService.getCurrentLoginWebUser();}else{optionalUser=this.webUserRepository.findById(webUserId[0]);}。..}这时候再试一下就可以达到想要的效果了。另外说一下如何显示多选或者单选组件,我们要达到的效果:并且用户点击选项2不会改变显示,类似于disable的效果,但是如果我们直接声明输入为disable,会像下面这样显示的不是很清楚。如果我们经常使用redonly,会发现点击之后还是会给出对应的显示,但是数值不会改变。解决方法:在input问题3:如果一个人在项目中有多个属性,分配任务预览后会发现当遇到这种有多个记录的情况时,我首先想到的是分配任务的时候生成的数据重复,但是看了分配的代码,没有发现问题,查数据库也没有生成重复的数据项。也确认了之前调用的端口和后台是匹配的,于是尝试在前台输出页面,发现返回的数据一模一样。我也在后台查看了前台返回的数据,发现返回了三个一模一样的数据。对应的id也是一样的。至此,可以确认查询有问题。但是我们在前台没有传递任何查询参数,所以根据用户区域查询基本可以确定是后台有问题。后台查询条件是这样构造的switch(district.getType()){caseTYPE_BUILDING:returncriteriaBuilder.equal(buildingJoin.get("id").as(Long.class),districtId);...}换句话说,每个查询都是独立的,好吗?House1查询后返回一条数据,然后根据其他house查询返回,这不是我想象的方式:如果我们要去掉这些重复的,只需要在查询条件中加上这一条:publicstaticSpecificationdistinct(){return(root,criteriaQuery,criteriaBuilder)->{criteriaQuery.不同的(真);返回条件查询。得到限制();};}