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

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 = "org.itkk.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一致),设置了私钥,以及可调用的服务org.itkk.rms.properties.application.udf-demo.serviceId=127.0.0.1:8080org.itkk.rms.properties.application.udf-demo.secret=ADSFHKW349546RFSGForg.itkk.rms.properties.application.udf-demo.purview=FILE_3org.itkk.rms.properties.application.udf-demo.all=falseorg.itkk.rms.properties.application.udf-demo.disabled=falseorg.itkk.rms.properties.application.udf-demo.description=sample application#定义了一个叫FILE_3的服务,后续使用这个服务编号进行调用即可org.itkk.rms.properties.service.FILE_3.owner=udf-demoorg.itkk.rms.properties.service.FILE_3.uri=/service/file/downloadorg.itkk.rms.properties.service.FILE_3.method=POSTorg.itkk.rms.properties.service.FILE_3.isHttps=falseorg.itkk.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 = "org.itkk.rms.config")@Validatedpublic 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文件中进行配置

#拦截路径org.itkk.rms.config.rmsPathPatterns=/service/**

3.实现Rms类1.定义rms类

@Componentpublic 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);    //请求并且返回    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());  }

3.客户端调用

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

代码仓库 (博客配套代码)

udf-starter : 基础项目,脚手架,框架

udf-sample : 集成样例

结束

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


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

捕捉最后的流星,坐在最高的山顶上,可以听音乐,聊电影,

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

相关文章:

你感兴趣的文章:

标签云: