文档章节

spring boot / cloud (八) 使用RestTemplate来构建远程调用服务

wangkang80
 wangkang80
发布于 2017/06/13 10:33
字数 1534
阅读 3934
收藏 173

spring boot / cloud (八) 使用RestTemplate来构建远程调用服务

##前言

上周因家里突发急事,请假一周,故博客没有正常更新

###RestTemplate介绍:

RestTemplate是spring框架中自带的rest客户端工具类,具有丰富的API,并且在spring cloud中,标记@LoadBalanced注解,可以实现客户端负载均衡的rest调用.

##思路

RestTemplate虽然提供了丰富的API,但是这些API过于底层,如果不稍加控制,让开发人员随意使用,那后续的代码也将会变的五花八门,难以维护.

同时,当系统规模大了之后,将会有更多的服务,并且服务之间的调用关系也将更加复杂,如果不进行管控治理的话,同样,项目同期也将越来越不可控,

最后,服务间调用也需要有明确的权限认证机制,最好是能通过配置的方式来明确,哪些服务可以调用那些服务.从而来把控项目的复杂度.

本文将从以下几点来提供一个解决问题的思路:

  • 通过spring boot的@ConfigurationProperties机制来定义远程服务的元数据,从而实现权限认证的配置化

  • 使用HandlerInterceptor来进行拦截,实现权限的验证

  • 定义通用Rms类,来规范RestTemplate的使用

##实现

###1.实现权限配置

####1.定义Application元数据

public class ApplicationMeta implements Serializable {
  //ID
  private static final long serialVersionUID = 1L;
  //服务ID
  private String serviceId;
  //私钥
  private String secret;
  //权限
  private String purview;
  //所有服务的调用权限(优先判定)
  private Boolean all = false;
  //禁止服务调用
  private Boolean disabled = false;
  //描述
  private String description;
}

####2.定义Service元数据

public class ServiceMeta implements Serializable {
  //ID
  private static final long serialVersionUID = 1L;
  //应用名称
  private String owner;
  //地址
  private String uri;
  //服务方法
  private String method;
  //是否HTTPS
  private Boolean isHttps = false;
  //描述
  private String description;

####3.定义RmsProperties类

@Component
@ConfigurationProperties(prefix = "com.egridcloud.rms.properties")
public class RmsProperties implements Serializable {
  //ID
  private static final long serialVersionUID = 1L;
  //应用清单(应用名称 : 应用地址)
  private Map<String, ApplicationMeta> application;
  //服务路径(服务编号 : 服务元数据)
  private Map<String, ServiceMeta> service;

####4.在properties文件中进行配置

#定义了一个叫udf-demo(跟spring boot的应用ID一致),设置了私钥,以及可调用的服务
com.egridcloud.rms.properties.application.udf-demo.serviceId=127.0.0.1:8080
com.egridcloud.rms.properties.application.udf-demo.secret=ADSFHKW349546RFSGF
com.egridcloud.rms.properties.application.udf-demo.purview=FILE_3
com.egridcloud.rms.properties.application.udf-demo.all=false
com.egridcloud.rms.properties.application.udf-demo.disabled=false
com.egridcloud.rms.properties.application.udf-demo.description=sample application

#定义了一个叫FILE_3的服务,后续使用这个服务编号进行调用即可
com.egridcloud.rms.properties.service.FILE_3.owner=udf-demo
com.egridcloud.rms.properties.service.FILE_3.uri=/service/file/download
com.egridcloud.rms.properties.service.FILE_3.method=POST
com.egridcloud.rms.properties.service.FILE_3.isHttps=false
com.egridcloud.rms.properties.service.FILE_3.description=文件下载

###2.实现权限校验

####1.定义RmsAuthHandlerInterceptor拦截器

public class RmsAuthHandlerInterceptor implements HandlerInterceptor {
  //环境标识
  private static final String DEV_PROFILES = "dev";
  //配置
  @Autowired
  private RmsProperties rmsProperties;
  //环境变量
  @Autowired
  private Environment env;
  
  @Override
  public boolean preHandle(HttpServletRequest request, 
      HttpServletResponse response,
      Object handler) {
      
      .......
      
  }
}

####2.完善preHandle方法-取出认证信息

    String rmsApplicationName = request.getHeader(Constant.HEADER_RMS_APPLICATION_NAME_CODE);
    if (StringUtils.isBlank(rmsApplicationName)) {
      rmsApplicationName = request.getParameter(Constant.HEADER_RMS_APPLICATION_NAME_CODE);
    }
    //获取认证信息(sign)
    String rmsSign = request.getHeader(Constant.HEADER_RMS_SIGN_CODE);
    if (StringUtils.isBlank(rmsSign)) {
      rmsSign = request.getParameter(Constant.HEADER_RMS_SIGN_CODE);
    }
    //获取认证信息(服务代码)
    String rmsServiceCode = request.getHeader(Constant.HEADER_SERVICE_CODE_CODE);
    if (StringUtils.isBlank(rmsServiceCode)) {
      rmsServiceCode = request.getParameter(Constant.HEADER_SERVICE_CODE_CODE);
    }
    //获取请求地址
    String url = request.getAttribute(HandlerMapping.BEST_MATCHING_PATTERN_ATTRIBUTE).toString();
    //获取请求方法
    String method = request.getMethod();

####3.完善preHandle方法-校验

    //判断环境(开发环境无需校验)
    if (!DEV_PROFILES.equals(env.getProperty("spring.profiles.active"))) {
      //判断是否缺少认证信息
      if (StringUtils.isBlank(rmsApplicationName) || StringUtils.isBlank(rmsSign)
          || StringUtils.isBlank(rmsServiceCode)) {
        throw new AuthException("missing required authentication parameters (rmsApplicationName , rmsSign)");
      }
      //判断systemTag是否有效
      if (!this.rmsProperties.getApplication().containsKey(rmsApplicationName)) {
        throw new AuthException("unrecognized systemTag:" + rmsApplicationName);
      }
      //获得应用元数据
      ApplicationMeta applicationMeta = rmsProperties.getApplication().get(rmsApplicationName);
      //获得secret
      String secret = applicationMeta.getSecret();
      //计算sign
      String sign = Constant.sign(rmsApplicationName, secret);
      //比较sign
      if (!rmsSign.equals(sign)) {
        throw new AuthException("sign Validation failed");
      }
      //判断是否有调用所有服务的权限
      if (!applicationMeta.getAll()) {
        //判断是否禁止调用所有服务权限
        if (applicationMeta.getDisabled()) {
          throw new PermissionException(rmsApplicationName + " is disabled");
        }
        //判断是否有调用该服务的权限
        if (applicationMeta.getPurview().indexOf(rmsServiceCode) == -1) {
          throw new PermissionException("no access to this servoceCode : " + rmsServiceCode);
        }
        //判断服务元数据是否存在
        if (!rmsProperties.getService().containsKey(rmsServiceCode)) {
          throw new PermissionException("service code not exist");
        }
        //获得服务元数据
        ServiceMeta serviceMeta = rmsProperties.getService().get(rmsServiceCode);
        //比较url和method的有效性
        if (!serviceMeta.getUri().equals(url) || !serviceMeta.getMethod().equals(method)) {
          throw new PermissionException("url and method verification error");
        }
      }
    }

####4.定义RmsConfig类

@Configuration
@ConfigurationProperties(prefix = "com.egridcloud.rms.config")
@Validated
public class RmsConfig {

  //RMS扫描路径
  @NotNull
  private String rmsPathPatterns;

  .........
  
}

####5.定义RmsConfig类-注册bean

  @Bean
  @LoadBalanced
  RestTemplate restTemplate(ClientHttpRequestFactory requestFactory) {
    return new RestTemplate(requestFactory);
  }
  
  @Bean
  public RmsAuthHandlerInterceptor rmsAuthHandlerInterceptor() {
    return new RmsAuthHandlerInterceptor();
  }
  
  @Bean
  public WebMvcConfigurer rmsAuthConfigurer() { //NOSONAR
    return new WebMvcConfigurerAdapter() {
      @Override
      public void addInterceptors(InterceptorRegistry registry) {
        String[] rmsPathPatternsArray = rmsPathPatterns.split(",");
        registry.addInterceptor(rmsAuthHandlerInterceptor()).addPathPatterns(rmsPathPatternsArray);
        super.addInterceptors(registry);
      }
    };
  }

####6.在properties文件中进行配置

#拦截路径
com.egridcloud.rms.config.rmsPathPatterns=/service/**

###3.实现Rms类

####1.定义rms类

@Component
public class Rms {
  //应用名称
  @Value("${spring.application.name}")
  private String springApplicationName;
  //restTemplate
  @Autowired
  private RestTemplate restTemplate;
  //配置
  @Autowired
  private RmsProperties rmsProperties;

####2.定义rms类-call方法

  public <I, O> ResponseEntity<O> call(String serviceCode, I input, String uriParam,
      ParameterizedTypeReference<O> responseType, Map<String, ?> uriVariables) {
    //客户端权限验证
    verification(serviceCode);
    //构建请求路径
    String path = getRmsUrl(serviceCode);
    //获得请求方法
    String method = getRmsMethod(serviceCode);
    //拼装路径参数
    if (StringUtils.isNotBlank(uriParam)) {
      path += uriParam;
    }
    //构建请求头
    HttpHeaders httpHeaders = buildSystemTagHeaders(serviceCode);
    //构建请求消息体
    HttpEntity<I> requestEntity = new HttpEntity<>(input, httpHeaders);
    //请求并且返回
    LOGGER.info("rms url : {} , method : {} ", path, method);
    return restTemplate.exchange(path, HttpMethod.resolve(method), requestEntity, responseType,
        uriVariables != null ? uriVariables : new HashMap<String, String>());
  }

####3.定义rms类-其他方法

  //构建请求头
  private HttpHeaders buildSystemTagHeaders(String serviceCode) {
    String secret = rmsProperties.getApplication().get(springApplicationName).getSecret();
    HttpHeaders headers = new HttpHeaders();
    headers.add(Constant.HEADER_RMS_APPLICATION_NAME_CODE, springApplicationName);
    headers.add(Constant.HEADER_RMS_SIGN_CODE, Constant.sign(springApplicationName, secret));
    headers.add(Constant.HEADER_SERVICE_CODE_CODE, serviceCode);
    return headers;
  }
  //客户端验证
  private void verification(String serviceCode) {
    ApplicationMeta applicationMeta = rmsProperties.getApplication().get(springApplicationName);
    if (!applicationMeta.getAll()) {
      if (applicationMeta.getDisabled()) {
        throw new PermissionException(springApplicationName + " is disabled");
      }
      if (applicationMeta.getPurview().indexOf(serviceCode) == -1) {
        throw new PermissionException("no access to this servoceCode : " + serviceCode);
      }
    }
  }
  //获得请求方法
  private String getRmsMethod(String serviceCode) {
    return rmsProperties.getService().get(serviceCode).getMethod();
  }
  //构造url
  private String getRmsUrl(String serviceCode) {
    //获取服务元数据
    ServiceMeta serviceMeta = rmsProperties.getService().get(serviceCode);
    //构建请求路径
    StringBuilder url =
        new StringBuilder(serviceMeta.getIsHttps() ? Constant.HTTPS : Constant.HTTP);
    url.append(rmsProperties.getApplication().get(serviceMeta.getOwner()).getServiceId());
    url.append(serviceMeta.getUri());
    return url.toString();
  }
  //计算sign
  public static String sign(String rmsApplicationName, String secret) {
    final String split = "_";
    StringBuilder sb = new StringBuilder();
    sb.append(rmsApplicationName).append(split).append(secret).append(split)
        .append(new SimpleDateFormat(DATA_FORMAT).format(new Date()));
    return DigestUtils.md5Hex(sb.toString());
  }

###4.客户端调用

//获得文件信息
ResponseEntity<RestResponse<FileInfo>> fileInfo = rms.call("FILE_4", fileParam, null,
              new ParameterizedTypeReference<RestResponse<FileInfo>>() {
              }, null);

##结束

这样,规范了远程服务的调用,只关心接口编号和接口的入参和出参,能够增加沟通效率,并且也有了轻量级的服务治理机制,服务间的调用更可控,到最后,配置文件一拉出来一清二楚.


想获得最快更新,请关注公众号

想获得最快更新,请关注公众号

© 著作权归作者所有

共有 人打赏支持
wangkang80
粉丝 351
博文 22
码字总数 34117
作品 3
浦东
高级程序员
加载中

评论(8)

程序猿猴
程序猿猴

引用来自“wangkang80”的评论

引用来自“程序猿猴”的评论

有git么?

回复@程序猿猴 :现在没有,后续会有

引用来自“程序猿猴”的评论

好的,期待

引用来自“wangkang80”的评论

现在git有了,欢迎关注:

https://gitee.com/wangkang/udf
https://gitee.com/wangkang/udf-sample
好的,早就已经关注这个项目了
wangkang80
wangkang80

引用来自“wangkang80”的评论

引用来自“程序猿猴”的评论

有git么?

回复@程序猿猴 :现在没有,后续会有

引用来自“程序猿猴”的评论

好的,期待
现在git有了,欢迎关注:

https://gitee.com/wangkang/udf
https://gitee.com/wangkang/udf-sample
Nathans
Nathans
如果用outh是不是更好
程序猿猴
程序猿猴

引用来自“wangkang80”的评论

引用来自“程序猿猴”的评论

有git么?

回复@程序猿猴 :现在没有,后续会有
好的,期待
wangkang80
wangkang80

引用来自“程序猿猴”的评论

有git么?

回复@程序猿猴 :现在没有,后续会有
程序猿猴
程序猿猴
有git么?
wangkang80
wangkang80

引用来自“金氧”的评论

土豪康,呵呵 :smirk:
高董....有何指示:hear_no_evil:
金氧
金氧
土豪康,呵呵 :smirk:
UDF 集成样例--udf-sample

UDF 基于spring boot / spring cloud 的基础项目,脚手架,主要用于学习和实践 按照spring boot的思想,将各个不同的功能按照starter的形式拆分开来,做到灵活组合 物理架构示意 CI & CD 示意 代...

wangkang80
2017/08/27
344
0
疯狂Spring Cloud连载(9)——RestTemplate的负载均衡原理

本文节选自《疯狂Spring Cloud微服务架构实战》 京东购买地址:https://item.jd.com/12256011.html 当当网购买地址:http://product.dangdang.com/25201393.html Spring Cloud教学视频:htt...

杨大仙的程序空间
2017/10/18
0
2
[Spring Cloud] 2.Spring Cloud Native Application

Spring Cloud Native Application 原生应用 原生应用:是一种程序开发风格,是连续交付(持续集成)和值驱动领域的最佳实践。 12-factors 与12-Factor (SaaS)应用,有着类似的目标: (12-Fa...

秋雨霏霏
2016/06/29
373
2
Spring Cloud Netflix架构浅析

最近接触微服务这块的东西,对这方面有了一些了解,拿出来和大家分享一下。 微服务框架Spring Boot+Spring Cloud Spring Cloud是基于Spring Boot的一整套实现微服务的框架,可以说,Spring ...

海岸线的曙光
2017/12/20
0
0
【微服务】使用spring cloud搭建微服务框架,整理学习资料

写在前面   使用spring cloud搭建微服务框架,是我最近最主要的工作之一,一开始我使用bubbo加zookeeper制作了一个基于dubbo的微服务框架,然后被架构师否了,架构师曰:此物过时。随即,我...

grootzhang
06/27
0
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

java并发备忘

不安全的“先检查后执行”,代码形式如下: if(条件满足){ //这里容易出现线程安全问题//doSomething}else{//doOther} 读取-修改-写入 原子操作:使用CAS技术,即首先从V中读取...

Funcy1122
今天
0
0
SpringBoot2.0 停机

最近新建了个SpringBoot2.0的项目,因为原来一直使用的是传统的Tomcat部署war包的形式,所以这次SpringBoot内置Tomcat部署jar包的时候遇到了很多问题。其中一个就是因为没有外置的Tomcat容器...

Canaan_
昨天
0
1
Confluence 6 外部参考

一个外部参考的意思是任何站点链接到你 Confluence 的实例。任何时候当 Confluence 的用户单击这个外部链接的时候,Confluence 可以记录这次单击为参考。 在默认的情况下,外部链接的参考链接...

honeymose
昨天
0
0
Android中的设计模式之抽象工厂模式

参考 《设计模式解析》 第十一章 Abstract Factory模式 《设计模式:可复用面向对象软件的基础 》3.1 Abstract Factory 抽象工厂 对象创建型模式 《Android源码设计模式解析与实战》第6章 创...

newtrek
昨天
0
0
Redis | 地理空间(GEO)的一个坑

Redis的地理空间(Geo)是个好东西,轻轻松松的就可以把地图描点的问题处理了, 最近却遇到一个坑...Redis采用的Msater-Slave模式, 运用GEORADIUS在salve读取对应的数据,新增了从节点但是从不返...

云迹
昨天
1
0

没有更多内容

加载失败,请刷新页面

加载更多

下一页

返回顶部
顶部