微服务获取用户信息和OpenFeign传递用户

问题一:

网关已经完成登录校验并获取登录用户身份信息。但是当网关将请求转发到微服务时,微服务又该如何获取用户身份呢?

由于网关发送请求到微服务依然采用的是Http请求,因此我们可以将用户信息以请求头的方式传递到下游微服务。然后微服务可以从请求头中获取登录用户信息。考虑到微服务内部可能很多地方都需要用到登录用户信息,因此我们可以利用SpringMVC的拦截器来实现登录用户信息获取,并存入ThreadLocal,方便后续使用。

据图流程图如下:

 

因此,接下来我们要做的事情有:

改造网关过滤器,在获取用户信息后保存到请求头,转发到下游微服务

String userInfo = userId.toString();
exchange.mutate()
        .request(builder -> builder.header("user-info",userInfo)).build();

编写微服务拦截器,拦截请求获取用户信息,保存到ThreadLocal后放行

ThreadLocal相关的类:

package com.hmall.common.utils;

public class UserContext {
    private static final ThreadLocal<Long> tl = new ThreadLocal<>();

    /**
     * 保存当前登录用户信息到ThreadLocal
     * @param userId 用户id
     */
    public static void setUser(Long userId) {
        tl.set(userId);
    }

    /**
     * 获取当前登录用户信息
     * @return 用户id
     */
    public static Long getUser() {
        return tl.get();
    }

    /**
     * 移除当前登录用户信息
     */
    public static void removeUser(){
        tl.remove();
    }
}

 拦截器:

package com.hmall.common.interceptor;

import cn.hutool.core.util.StrUtil;
import com.hmall.common.utils.UserContext;
import org.springframework.web.servlet.HandlerInterceptor;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class UserInfoInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(final HttpServletRequest request, final HttpServletResponse response, final Object handler) throws Exception {
        String userId = request.getHeader("User-info");
        if(StrUtil.isNotEmpty(userId)){
            UserContext.setUser(Long.parseLong(userId));
        }
        return true;
    }

    @Override
    public void afterCompletion(final HttpServletRequest request, final HttpServletResponse response, final Object handler, final Exception ex) throws Exception {
        UserContext.removeUser();
    }
}

配置类:

package com.hmall.common.config;

import com.hmall.common.interceptor.UserInfoInterceptor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
@ConditionalOnClass(DispatcherServlet.class) //排除网关服务
public class MvcConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new UserInfoInterceptor()).addPathPatterns("/**");
    }
}

基于SpringBoot的自动装配原理,我们要将其添加到resources目录下的META-INF/spring.factories文件中:

 内容如下:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.hmall.common.config.MyBatisConfig,\
  com.hmall.common.config.MvcConfig

问题二:

前端发起的请求都会经过网关再到微服务,由于我们之前编写的过滤器和拦截器功能,微服务可以轻松获取登录用户信息。

但有些业务是比较复杂的,请求到达微服务后还需要调用其它多个微服务。比如下单业务,流程如下:

 下单的过程中,需要调用商品服务扣减库存,调用购物车服务清理用户购物车。而清理购物车时必须知道当前登录的用户身份。但是,订单服务调用购物车时并没有传递用户信息,购物车服务无法知道当前用户是谁!

这里要借助Feign中提供的一个拦截器接口:feign.RequestInterceptor

@Bean
public RequestInterceptor userInfoRequestInterceptor(){
    return new RequestInterceptor() {
        @Override
        public void apply(RequestTemplate template) {
            // 获取登录用户
            Long userId = UserContext.getUser();
            if(userId == null) {
                // 如果为空则直接跳过
                return;
            }
            // 如果不为空则放入请求头中,传递给下游微服务
            template.header("user-info", userId.toString());
        }
    };
}

此外还需要在调用服务的启动类上加上:

@EnableFeignClients(basePackages = "com.hmall.api.client",defaultConfiguration = DefaultFeignConfig.class)

### OpenFeign 实现微服务远程调用的具体过程 #### 创建 Feign 客户端接口 为了使一个接口成为 Feign 客户端,需在其上标注 `@FeignClient` 注解并指定目标微服务的名字。这使得该接口能够作为 HTTP 请求的入口点去访问其他的服务。 ```java @FeignClient(name = "example-service") public interface ExampleServiceClient { @RequestMapping(method = RequestMethod.GET, value = "/api/example") String getExample(); } ``` 上述代码片段展示了如何定义一个简单的 Feign 客户端接口用于向名为 `"example-service"` 的微服务发起 GET 请求获取数据[^2]。 #### 配置 Spring Boot 应用程序支持 Feign 为了让应用程序识别并使用 Feigen 客户端,在项目的依赖管理文件中加入相应的 starter 依赖项,并确保启用了对 Feign 的自动配置功能: 对于 Maven 用户来说,可以在 pom.xml 文件里添加如下依赖: ```xml <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> ``` 同时还需要开启全局扫描以发现所有的 Feign Client 接口,通常是在主类或任意配置类上面加上 `@EnableFeignClients` 注解即可完成设置[^3]。 #### 使用 Feign 客户端进行远程调用 一旦完成了前面两步操作之后就可以像平常那样注入所需的 Feign 客户端实例并通过它来进行跨服务之间的交互了。下面给出了一段示例代码说明怎样在一个控制器内通过已有的 Feign 客户端来触发一次对外部 API 的请求: ```java @RestController @RequestMapping("/consumer") public class ConsumerController { private final ExampleServiceClient exampleServiceClient; public ConsumerController(ExampleServiceClient exampleServiceClient){ this.exampleServiceClient = exampleServiceClient; } @GetMapping("/consume-example") public ResponseEntity<String> consumeExample(){ try{ String result = exampleServiceClient.getExample(); return new ResponseEntity<>(result , HttpStatus.OK); }catch (Exception e){ logger.error("Failed to call external service",e); throw new RuntimeException(e.getMessage()); } } } ``` 这段 Java 代码展示了一个 RESTful Web Service 控制器的方法,当接收到 `/consumer/consume-example` 路径下的 GET 请求时会尝试调用之前定义好的 `ExampleServiceClient#getExample()` 方法从而间接地实现了与其他微服务间的数据交换行为[^5]。 ### OpenFeign的工作原理 OpenFeign 是一种声明式的 HTTP 客户端工具包,其核心理念在于允许开发人员仅关注于业务逻辑层面的设计而不必关心底层网络通信细节。具体而言就是说只要按照既定规则编写好对应的客户端接口描述文档(即所谓的契约),剩下的诸如 URL 组装、HTTP 头信息维护以及响应解析等工作都将由框架本身负责处理完毕后再返回给使用者最终的结果集。 每当应用启动期间,Spring Cloud 就会对所有标记有 `@FeignClient` 的组件做初始化工作——依据所提供的元数据动态生成具体的代理对象;随后每当遇到某个特定类型的函数调用事件发生之际便会激活内部预设的一系列拦截机制进而完成整个消息传递流程[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值