Springcloud开发之OpenFeign调用和认证
SpringCloud开发cloud具有巨大的灵活性。
在调用其它服务的时候有多种方式,虽然本质一样,但是细节还是有所差异。
一、概述
当a服务调用b服务的时候有多种方式进行:
1.通过openFeign接口方式
优点:我们一般会使用这种方式,因为代码量相对少一些,安全可以通过openFeign拦截器来实现。
缺点:无法在请求的时候直接设置请求头,请求体等(当然可以通过拦截器或者属性配置解决这个。但在运行时,动态修改并不方便)
总结:总体而言,还是推荐的。
2.通过RestTemplate的方式指定服务名调用
优点:灵活度较高,可以自定义处理请求(头+参数等)和返回的操作,不需要定义openFeign接口
确定:如果类似的请求比较多,则工作量还是有一些的。
总结: 如果是最求灵活性,可以考虑。如果不做cloud开发,那么RestTemplate是非常不错的选择。
3.通过RestTemplate的方式指定ip方式调用
第二种方式的变体,一般不那么做,因为这失去了cloud的优势-集群+负载均衡等等。所以在后文的例子中,有关此部分略。
做工程,自然要兼顾效率和可维护性,所以一般我们都推荐使用第1种方式。技术总是要服务于工程和需求。
下面我使用例子来说明。
二、例子
2.1 概述
为了测试,我们构建了四个服务:
a.base(ConfigService) -负责认证 ,返回token
b.McService --管理中心,负责一些管理信息处理
c.CustomerService-负责客户有关信息
d.ConfigServiceTest-配置测试服务
测试的脚本写在服务ConfigServiceTest中。
为了解决篇幅,下文例子只是贴出了各个service的接口以及ConfigServiceTest的cloud有关配置。
2.2 配置代码
a服务的接口-/api/services/app/Users/Login
@RequestMapping(value="/api/services/app/Users/Login",method= {RequestMethod.POST,RequestMethod.GET}) @ResponseBody public PublicReturn login(HttpServletRequest request,@RequestBody AuthParam input) { AuthParam auth=jwtUtil.parseAuthToken(request, "Authorization"); if (auth!=null) { LOG.debug("token Value From header is:"+JSON.toJSONString(auth)); } LOG.debug(JSON.toJSONString(input)); LOG.debug(input.getCustomerSystemCode()); try { PublicReturn result= this.authService.login(input); LOG.debug("result is:"+JSON.toJSONString(result)); return result; } catch(Exception e) { return PublicReturn.getUnSuccessful("获取认证信息失败"); } }
b 服务的接口-/api/services/app/test
/**
* 仅用于测试
* @param name
* @return
*/
@RequestMapping("/app/test")
@ResponseBody
public PublicReturn test(@RequestParam(value="name",required = true) String name){
return PublicReturn.getSuccessful(name);
}
c服务的接口-/customer/list
@Controller @RequestMapping(value="/customer") public class TestController { /** * 返回list * @return */ @RequestMapping(value = "/list") @ResponseBody public Object list() { // TODO: Map<String,Object> customer=new HashMap<String,Object>(); customer.put("name","lzf"); customer.put("sex","男"); customer.put("age","99"); customer.put("address","中华"); return customer; } }
以上三个服务的代码不用特别关注。
d服务(ConfigServiceTest)的测试代码,分两大部分:配置和接口
- 配置-OpenFeign接口和相关配置
- 用于测试的接口
配置代码:
1.异常处理-ErrorHandler(ErrorDecoder)
2.openFeign拦截器等配置-ConfigServiceFeignInter,McServiceFeignInter
3.Spring-Mvc配置-CustomWebMvcConfigurer(WebMvcConfigurationSupport),负责定义RestTemplate bean等等
4.openFeign接口-ConfigServiceOpenFeignInterface、McServiceOpenFeignInterface
测试代码:
1.TestController
--------------------
下面逐一贴出以上代码(注:为了节约篇幅,部分代码只选择紧要部分)
d的两个拦截器代码
public class ConfigServiceFeignInter { @Bean public RequestInterceptor currentUserRequestInterceptor() { return (RequestTemplate template) -> { //Map<String, Collection<String>> header=template.request().headers(); //System.out.println(JSON.toJSONString(header, true)); }; } } public class McServiceFeignInter { @Bean public ErrorDecoder feignErrorDecoder() { return new ErrorHandler(); } @Bean public RequestInterceptor currentUserRequestInterceptor() { return (RequestTemplate template) -> { //Map<String, Collection<String>> header=template.request().headers(); //System.out.println(JSON.toJSONString(header, true)); String token = LoginCache.get(); System.out.println(token); template.header("Authorization", token); }; } }
d的两个openFeign接口
@FeignClient(value = "ConfigService",configuration= ConfigServiceFeignInter.class) @Component public interface ConfigServiceOpenFeignInterface { @PostMapping("/api/services/app/Users/Login") @ResponseBody PublicReturn login(@RequestBody AuthParam input); } @FeignClient(value = "McService",configuration= McServiceFeignInter.class) @Component public interface McServiceOpenFeignInterface { @RequestMapping(value = "/api/services/app/test") public PublicReturn test(@RequestParam(value="name",required = true) String name); }
d的mvc配置(局部)
@LoadBalanced @Bean public RestTemplate restTemplate() { RestTemplate restTemplate = new RestTemplate(); // 把自定义的ClientHttpRequestInterceptor添加到RestTemplate,可添加多个 //restTemplate.setInterceptors(Collections.singletonList(actionTrackInterceptor)); return restTemplate; }
注意:
1.springMvc的RestTemplate功能还是很强大的,可以设定拦截器等等。
2.我们并没有为服务c(CustomerService)定义标准的OpenFeign接口
2.3 测试代码
@RequestMapping(value = "/login_and_request") @ResponseBody public Object loginAndRequest() { // TODO: AuthParam authParam=new AuthParam(); //此处略 PublicReturn loginResult=configService.login(authParam); if (loginResult.isSuccessful()){ String token=loginResult.getDateItem("token").toString(); LoginCache.set(token); try{ PublicReturn result=mcService.test("this is luzhifei"); return result; } catch(Exception e){ return PublicReturn.getUnSuccessful(e.getMessage()); } } else{ return PublicReturn.getUnSuccessful("登录失败"); } } /** * 使用RestTemplate来调用 * @return */ @RequestMapping(value = "/request3srv") @ResponseBody public Object request3srv() { // TODO: AuthParam authParam=new AuthParam(); //此处略... PublicReturn loginResult=configService.login(authParam); if (loginResult.isSuccessful()){ String token=loginResult.getDateItem("token").toString(); String url="http://McService/api/services/app/test"; MultiValueMap<String, String> header=new LinkedMultiValueMap<>(); header.add("Authorization",token); MultiValueMap<String, String> param = new LinkedMultiValueMap<>(); param.add("name","lzf"); HttpEntity<MultiValueMap<String, String>> requst=new HttpEntity<>(param,header); try{ PublicReturn result=restTemplate.postForObject(url,requst,PublicReturn.class); return result; } catch(Exception e){ return PublicReturn.getUnSuccessful(e.getMessage()); } } else{ return PublicReturn.getUnSuccessful("登录失败"); } } @RequestMapping(value = "/list-customer") @ResponseBody public Object listCustomer() { // TODO: String url="http://CustomerService/customer/list"; MultiValueMap<String, String> header=new LinkedMultiValueMap<>(); header.add("Authorization","青山遮不住,毕竟东流去!"); MultiValueMap<String, String> param = new LinkedMultiValueMap<>(); param.add("name","lzf"); HttpEntity<MultiValueMap<String, String>> requst=new HttpEntity<>(param,header); Object result=restTemplate.postForObject(url,requst,Object.class); return result; }
2.4测试
编写完成后,启动a,b,c,d服务。
之后通过浏览器执行:
#通过原生openFeign接口调用
http://localhost:9081/test/login_and_request
#混合调用(原生openFeign+无openFeign方式调用)
http://localhost:9081/test/request3srv
#不需要定义OpenFeign接口的测试
http://localhost:9081/test/list-customer
通过上文list-customer的例子可以看到:不需要专门定义OpenFeign接口,也可以直接调用(通过服务名称而不是ip+url)来实现对其它
服务的调用。
测试的时候发现,如果故意停止服务b.McService,那么会等待比较长一段时间才能返回。
如果觉得这个TimeOut时间太长,那么可以设置OpenFeign调用的TimeOut参数
feign:
client:
config:
default:
connectTimeout: 30000
注意:这影响所有的Client
---
关于Timeout可以参阅:SpringCloud开发之OpenFeign timeout和压缩等问题