• 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:8080
    org.itkk.rms.properties.application.udf-demo.secret=ADSFHKW349546RFSGF
    org.itkk.rms.properties.application.udf-demo.purview=FILE_3
    org.itkk.rms.properties.application.udf-demo.all=false
    org.itkk.rms.properties.application.udf-demo.disabled=false
    org.itkk.rms.properties.application.udf-demo.description=sample application
    
    #定义了一个叫FILE_3的服务,后续使用这个服务编号进行调用即可
    org.itkk.rms.properties.service.FILE_3.owner=udf-demo
    org.itkk.rms.properties.service.FILE_3.uri=/service/file/download
    org.itkk.rms.properties.service.FILE_3.method=POST
    org.itkk.rms.properties.service.FILE_3.isHttps=false
    org.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")
    @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文件中进行配置

    #拦截路径
    org.itkk.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);
        //请求并且返回
        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);
    

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

    结束

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


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

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

  • 相关阅读:
    web前端的面试真题
    web前端面试真题! 面试的经历和回答只做参考1
    web前端面试真题! 面试的经历和回答只做参考
    html面试资料
    angluar.js的核心介绍
    解决 Chrome支持小于12px 的文字
    div居中效果出现的问题和解决方法
    li和li之间的bug解决方法
    前端面试题笔试考题和答案
    html5新增的标签和使用的方法
  • 原文地址:https://www.cnblogs.com/itkk/p/7473847.html
Copyright © 2020-2023  润新知