feign 实现签名、服务地址动态切换

什么是feign

Feign是一个http请求调用的轻量级框架,可以以Java接口注解的方式调用Http请求。Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的请求,封装了http调用流程。

快速启动

1. 客户端

  • maven依赖:
        <dependency>
            <groupId>com.netflix.feign</groupId>
            <artifactId>feign-core</artifactId>
            <version>8.18.0</version>
        </dependency>
  • feign声明服务方api
public interface HelloControllerApi {
	@RequestLine("GET /api/hello?name={name}")
	String hello(@Param(value = "name") String name);
}
  • 调用服务提供方api

public class HelloControllerApiTest {

	private HelloControllerApi service;

	@Before
	public void before(){
		service = Feign.builder()
				.options(new Request.Options(1000, 3500))
				.retryer(new Retryer.Default(5000, 5000, 3))
				.target(HelloControllerApi.class, "http://127.0.0.1:8080");
	}
	@Test
	public void hello(){
        // 调用http://127.0.0.1:8080/api/hello?name=world 的http接口
		System.out.println(service.hello("world"));
	}

}

2. 服务端

服务端是spring-web服务,当然可以基于其他形式web服务。

  • 服务代码:
@Controller
@RequestMapping(value = "api")
public class HelloController {
	@RequestMapping(value = "/hello", method = {RequestMethod.GET})
	@ResponseBody
	public String list(@RequestParam String name) {
		return "Hello " + name;
	}
}
  • 启动类
@SpringBootApplication(scanBasePackages = {"com.vhicool.manager"})
public class ManagerApplication {
	public static void main(String[] args) {
		SpringApplication.run(ManagerApplication.class, args);
	}
}


实现签名验证

1. 客户端

  • 添加feign拦截器,并在请求heard中添加token
        service = Feign.builder()
				.options(new Request.Options(1000, 3500))
				.retryer(new Retryer.Default(5000, 5000, 3))
				.requestInterceptor(new AuthRequestInterceptor(authKey))
				.target(HelloControllerApi.class, "http://127.0.0.1:8080");
public class AuthRequestInterceptor implements RequestInterceptor {
	private String token;

	public AuthRequestInterceptor(String token) {
		this.token = token;
	}

	@Override
	public void apply(RequestTemplate template) {
		template.header("token", token);
	}

}

2. 服务端

  • 服务端添加过滤器,对token进行验证
@Component
public class AuthFilter implements Filter {

	@Value("${token:test_token}")
	private String token;

	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
		String remoteToken = ((HttpServletRequest) servletRequest).getHeader("token");
		Assert.isTrue(token.equals(remoteToken), "签名验证失败");
		filterChain.doFilter(servletRequest, servletResponse);
	}
}

服务端自动生成feign

通过继承实现feign客户端和服务提供方api共用

在这里插入图片描述

spring-cloud-start-openfeign由于有支持spring-mvc controller主机,因此本例使用spring-cloud-start-openfeign依赖。实现原理为:1. 提取controller rest接口抽象接口,2. 在客户端由Feign接口继承,3. 在服务端由controller类实现处理逻辑。

在这里插入图片描述

1. 提取公共api
  • maven依赖
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
    </dependencies>
  • 公共服务接口(客户端和服务提供方,都需要继承改接口):
public interface IUserController {
	@RequestMapping(value = "user/list-all", method = {RequestMethod.GET})
	List<String> listAll(@RequestParam String name);
}

2. 服务端实现公共api
  • maven依赖
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.vhicool</groupId>
            <artifactId>feign-api</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
  • controller
@Controller
@RequestMapping
public class UserController implements IUserController {
	@Override
	@ResponseBody
	public List<String> listAll(String name) {
		ArrayList<String> list = new ArrayList<>();
		list.add("达菲");
		list.add("olu");
		list.add(name);
		return list;
	}
}

  • token验证拦截器
@Component
public class AuthFilter implements Filter {

	@Value("${token:test_token}")
	private String token;

	@Override
	public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
		String remoteToken = ((HttpServletRequest) servletRequest).getHeader("token");
		if(!token.equals(remoteToken)){
			((HttpServletResponse)servletResponse).setStatus(401);
			servletResponse.getWriter().write("签名验证失败");
			return;
		}
		filterChain.doFilter(servletRequest, servletResponse);
	}
}

  • 启动类
@SpringBootApplication(scanBasePackages = {"com.vhicool.manager"})
public class ManagerApplication {
	public static void main(String[] args) {
		SpringApplication.run(ManagerApplication.class, args);
	}
}

3. 客户端继承公共api
  • mavne依赖
         <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
            <version>2.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>compile</scope>
        </dependency>
         <dependency>
            <groupId>com.vhicool</groupId>
            <artifactId>feign-api</artifactId>
            <version>1.0-SNAPSHOT</version>
            <scope>compile</scope>
        </dependency>
  • 客户端调用类
@FeignClient(value = "user", url = "http://localhost:8080")
public interface UserApi extends IUserController {
}
  • 启动类
@SpringBootApplication
@EnableFeignClients
public class ManagerApplication {
	public static void main(String[] args) {
		SpringApplication.run(ManagerApplication.class, args);
	}
}
  • feign公共配置
@Configuration
public class FeignConfiguration {
	/**
	 * 请求超时时间
	 * @return
	 */
	@Bean
	public Request.Options options() {
		return new Request.Options(2000, 3500);
	}

	/**
	 * 拦截器
	 * @return
	 */
	@Bean
	public RequestInterceptor interceptor() {
		return new AuthRequestInterceptor("test_token");
	}
}
public class AuthRequestInterceptor implements RequestInterceptor {
	private String token;

	public AuthRequestInterceptor(String token) {
		this.token = token;
	}

	@Override
	public void apply(RequestTemplate template) {
		template.header("token", token);
	}

}
  • 测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class UserControllerTest {
	@Autowired
	private UserApi userApi;

	@Test
	public void listAll() {
		System.out.println(userApi.listAll("饼饼"));
	}
}

客户端实现动态连接服务端url

背景

现在客户端调用服务端还是1对1的场景,在实际使用场景中,我们需要客户端可以连接不同的服务,例如:一个Manager 同时管理多个woker,每个worker对应一个业务环境(生产、测试、开发等)。用户使用Manager进行环境切换,后端也需要对应访问不同环境的worker服务。

方案(基于服务端自动生成feign api

步骤
1. 在manager处理请求时,获取当前业务环境,并设置为当前线程上下文环境(基于`ThreadLocal`的`com.alibaba.transmittable-thread-local`)
2. 在触发feign调用是,根据当前线程上下文环境,动态设置服务提供方url(feign拦截器`RequestInterceptor`)

代码

客户端(manager)
  • 新增maven依赖
      <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>transmittable-thread-local</artifactId>
            <version>2.10.2</version>
       </dependency>
  • 设置当前线程所在环境
public class ZBaseContextManager {
    private static final TransmittableThreadLocal<String> ZBASE_CONTEXT = new TransmittableThreadLocal();

    /**
     * 设置当前线程环境
     * @param env
     */
    public static void setEnv(String env) {
        ZBASE_CONTEXT.set(env);
    }

    /**
     * 获取当前线程环境
     * @return
     */
    public static String currentEnv() {
        return ZBASE_CONTEXT.get();
    }
}
  • feign请求获取当前环境,并将url替换成对应环境worker的url
public class EnvRouterInterceptor implements RequestInterceptor {
	private static final Logger LOG = LoggerFactory.getLogger(EnvRouterInterceptor.class);
	private Map<String, String> envUrlMap;

	public EnvRouterInterceptor(Map<String, String> envUrlMap) {
		this.envUrlMap = envUrlMap;
	}

	@Override
	public void apply(RequestTemplate template) {
		String env = ZBaseContextManager.currentEnv();
		String url = this.envUrlMap.get(env);
		template.target(url);
		LOG.info("env : {} ,target url : {}", env, template.url());
	}
}
  • 设置feign全局拦截器
    @Bean
	public RequestInterceptor envRouterInterceptor() {
		Map<String, String> envMap = new HashMap<>();
		envMap.put("dev", "http://localhost:8080");
		envMap.put("fat", "http://10.10.134.196:8080");
		return new EnvRouterInterceptor(envMap);
	}
  • 测试类
@RunWith(SpringRunner.class)
@SpringBootTest
public class DynamicUserControllerTest {

	@Autowired
	private UserApi userApi;

	@Test
	public void listAllFat() {
		ZBaseContextManager.setEnv("fat");
		System.out.println(userApi.listAll("meimei"));
	}
	@Test
	public void listAllDev() {
		ZBaseContextManager.setEnv("dev");
		System.out.println(userApi.listAll("meimei2"));
	}
}

输出日志

 env : fat ,target url : http://10.10.134.196:8080/user/list-all?name=meimei
 env : dev ,target url : http://localhost:8080/user/list-all?name=meimei2
  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值