Spring Boot - 将当前用户存储在全局变量中,并在创建 @service bean 时通过 API 调用进行初始化

Sri*_*aik 2 spring spring-mvc spring-boot

我正在创建一个以 Zuul 作为网关的微服务架构项目。我在名为 common-service 的服务中处理了所有身份验证。我已经公开了一个来自公共服务的 API 来返回当前登录的用户。这工作正常。

现在,我有另一个微服务,称为库存。在库存的服务类中,我想在多种方法中使用当前登录的用户名。因此,我正在对公共服务进行网络客户端调用并获取当前用户名。这工作正常,但每次我需要用户名时,我都会对公共服务进行网络客户端 API 调用。示例 - 如果我添加一个新条目,执行 API 调用,然后再次更新 API 调用等。这似乎不是一种优化的方式

所以问题是 - 我想在全局级别进行此 API 调用。即,每当我的服务 bean 被自动装配时,就应该进行这个 API 调用,并且用户名应该存储在我可以在服务调用中跨方法使用的地方。

我尝试了 @PostConstruct 和 @SessionAttributes 但无法解决确切的问题。

有人可以帮助我提供最适合的解决方案或概念来处理这个问题。

下面是代码片段

public class LeadService 
{
@Autowired
    WebClient.Builder webClientBuilder;
    
    @Autowired
    UserDetailsService userDetailsService;
//more autowiring

private void setLeadFields(Lead lead, @Valid LeadCreateData payload,String type) 
        {
            //some logic
            if(type.equalsIgnoreCase("create"))
            {
                lead.setAsigneeId(userDetailsService.getCurrentUser().getId());
                lead.setCreatorId(userDetailsService.getCurrentUser().getId());
            }
            else if(type.equalsIgnoreCase("update"))
            {
                //some logic
            }
            
        }

private StatusEnum setLeadStatus(Lead lead, StatusEnum status,String string) 
        {
            LeadStatus lstatus=null;
            switch(string)
            {
                
                case "create":
                    lstatus = new LeadStatus(lead.getLeadId(),status,userDetailsService.getCurrentUser().getId(),userDetailsService.getCurrentUser().getId());
                    lsRepo.save(lstatus);
                    break;
                case "udpate":
                    lstatus= lsRepo.FindLeadStatusByLeadID(lead.getLeadId()).get(0);
                    if(!lstatus.getStatus().equals(lstatus))
                    {
                        lstatus = new LeadStatus(lead.getLeadId(),status,userDetailsService.getCurrentUser().getId(),userDetailsService.getCurrentUser().getId());
                        lsRepo.save(lstatus);
                    }
                    break;
            }
            return lstatus.getStatus();
        }

private Address setAddress(@Valid LeadCreateData payload,Address address) 
        {
            //some setters
            address.setCreator(userDetailsService.getCurrentUser().getId());
            return aRepo.save(address);
        }

Run Code Online (Sandbox Code Playgroud)

如您所见,我在很多地方使用了 userDetailsS​​ervice.getCurrentUser().getId() 。我从下面的自动装配方法中获取这个 id。但每次我需要这个 id 时,都需要调用一次 API。

@Service
public class UserDetailsService 
{
    @Autowired
    WebClient.Builder webClientBuilder;
    
    @Autowired
    HttpServletRequest request;
    
    @Value("${common.serverurl}")
    private String reqUrl;
    
    public UserReturnData getCurrentUser()
    {
        UserReturnData userDetails = webClientBuilder.build()
                            .get()
                            .uri(reqUrl+"user/me")
                            .header("Authorization", request.getHeader("Authorization"))
                            .retrieve()
                            .bodyToMono(UserReturnData.class)
                            .block();
        return userDetails;
    }
}
Run Code Online (Sandbox Code Playgroud)

我想要一种最佳方法,可以调用此 API 方法来仅获取当前用户一次。我可以在我的@service 课程中使用它。

Kav*_*lai 5

  • 创建OncePerPrequestFilterGenericFilterBean其中有您的UserDetailsService自动装配。

  • 而且您还想创建类似于RequestContextHolderSecurityContextHolder可以将您保存UserReturnDataThreadLocal变量中的东西。看看这两个 spring 类来了解一下,但你的可以简单得多。就这样吧UserReturnDataContextHolder

  • 在您在步骤 1 中创建的过滤器中,当请求到来时填充它,当响应离开时清除它。

  • 现在您可以通过服务中的任何位置访问它,UserReturnDataContextHolder.getUserReturnData()并且您也无需进行多次调用

编辑:以下部分由作为Sridhar Patnaik参考贡献-

下面的代码让它工作

添加了一个类来存储当前用户ID

public class CurrentUser 
{
    private Long currentUserId;
//getter setter
}
Run Code Online (Sandbox Code Playgroud)

添加了当前用户过滤器来拦截请求并获取当前用户。

public class CurrentUserFilter implements Filter 
{

    @Autowired
    private CurrentUser currentUser;

    @Autowired
    UserDetailsService UserDetailsService;
    
    @Override
    public void init(FilterConfig arg0) throws ServletException {
        // NOOP
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) servletRequest;
        try 
        {
            this.currentUser.setCurrentUserId(UserDetailsService.getCurrentUser().getId());
            chain.doFilter(servletRequest, servletResponse);
        } 
        finally 
        {
            this.currentUser.clear();
        }
    }

    @Override
    public void destroy() {
        // NOOP
    }
}
Run Code Online (Sandbox Code Playgroud)

添加了必需的AppConfig

@Configuration
public class AppConfig 
{
    @Bean
    public Filter currentUserFilter() {
        return new CurrentUserFilter();
    }

    @Bean
    public FilterRegistrationBean tenantFilterRegistration() {
        FilterRegistrationBean result = new FilterRegistrationBean();
        result.setFilter(this.currentUserFilter());
        result.setUrlPatterns(Lists.newArrayList("/*"));
        result.setName("Tenant Store Filter");
        result.setOrder(1);
        return result;
    }

    @Bean(destroyMethod = "destroy")
    public ThreadLocalTargetSource threadLocalTenantStore() {
        ThreadLocalTargetSource result = new ThreadLocalTargetSource();
        result.setTargetBeanName("tenantStore");
        return result;
    }

    @Primary
    @Bean(name = "proxiedThreadLocalTargetSource")
    public ProxyFactoryBean proxiedThreadLocalTargetSource(ThreadLocalTargetSource threadLocalTargetSource) {
        ProxyFactoryBean result = new ProxyFactoryBean();
        result.setTargetSource(threadLocalTargetSource);
        return result;
    }

    @Bean(name = "tenantStore")
    @Scope(scopeName = "prototype")
    public CurrentUser tenantStore() {
        return new CurrentUser();
    }
}
Run Code Online (Sandbox Code Playgroud)

然后将 CurrentUser 自动连接到我现有的服务类。

{..
@Autowired
    CurrentUser currentUser;
...
private void setLeadFields(Lead lead, @Valid LeadCreateData payload,String type) 
        {
            //some logic
            
            if(type.equalsIgnoreCase("create"))
            {
                lead.setAsigneeId(currentUser.getCurrentUserId());
                lead.setCreatorId(currentUser.getCurrentUserId());
                lead.setAddress(setAddress(payload, new Address()));
            }
            else if(type.equalsIgnoreCase("update"))
            {
                lead.setAsigneeId(userDetailsService.getUserFromId(payload.getAssigneeId()).getId());
                lead.setAddress(setAddress(payload,lead.getAddress()));
            }
            
        }
Run Code Online (Sandbox Code Playgroud)