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);

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

结束

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


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

spring boot / cloud (八) 使用RestTemplate来构建远程调用服务的更多相关文章

  1. spring boot / cloud (十四) 微服务间远程服务调用的认证和鉴权的思考和设计,以及restFul风格的url匹配拦截方法

    spring boot / cloud (十四) 微服务间远程服务调用的认证和鉴权的思考和设计,以及restFul风格的url匹配拦截方法 前言 本篇接着<spring boot / cloud ...

  2. spring boot / cloud (十八) 使用docker快速搭建本地环境

    spring boot / cloud (十八) 使用docker快速搭建本地环境 在平时的开发中工作中,环境的搭建其实一直都是一个很麻烦的事情 特别是现在,系统越来越复杂,所需要连接的一些中间件也越 ...

  3. spring boot / cloud (三) 集成springfox-swagger2构建在线API文档

    spring boot / cloud (三) 集成springfox-swagger2构建在线API文档 前言 不能同步更新API文档会有什么问题? 理想情况下,为所开发的服务编写接口文档,能提高与 ...

  4. 使用Intellij中的Spring Initializr来快速构建Spring Boot/Cloud工程(十五)

    在之前的所有Spring Boot和Spring Cloud相关博文中,都会涉及Spring Boot工程的创建.而创建的方式多种多样,我们可以通过Maven来手工构建或是通过脚手架等方式快速搭建,也 ...

  5. 使用Intellij中的Spring Initializr来快速构建Spring Boot/Cloud工程

    在之前的所有Spring Boot和Spring Cloud相关博文中,都会涉及Spring Boot工程的创建.而创建的方式多种多样,我们可以通过Maven来手工构建或是通过脚手架等方式快速搭建,也 ...

  6. Spring Boot教程(十五)使用Intellij中的Spring Initializr来快速构建Spring Boot/Cloud工程

    在之前的所有Spring Boot和Spring Cloud相关博文中,都会涉及Spring Boot工程的创建.而创建的方式多种多样,我们可以通过Maven来手工构建或是通过脚手架等方式快速搭建,也 ...

  7. spring boot / cloud (五) 自签SSL证书以及HTTPS

    spring boot / cloud (五) 自签SSL证书以及HTTPS 前言 什么是HTTPS? HTTPS(全称:Hyper Text Transfer Protocol over Secur ...

  8. spring boot / cloud (十五) 分布式调度中心进阶

    spring boot / cloud (十五) 分布式调度中心进阶 在<spring boot / cloud (十) 使用quartz搭建调度中心>这篇文章中介绍了如何在spring ...

  9. spring boot / cloud (十六) 分布式ID生成服务

    spring boot / cloud (十六) 分布式ID生成服务 在几乎所有的分布式系统或者采用了分库/分表设计的系统中,几乎都会需要生成数据的唯一标识ID的需求, 常规做法,是使用数据库中的自动 ...

随机推荐

  1. 最短路径之Dijkstra算法和Floyd-Warshall算法

    最短路径算法 最短路径算法通常用在寻找图中任意两个结点之间的最短路径或者是求全局最短路径,像是包括Dijkstra.A*.Bellman-Ford.SPFA(Bellman-Ford的改进版本).Fl ...

  2. Spring源码情操陶冶-AbstractApplicationContext#invokeBeanFactoryPostProcessors

    阅读源码有利于陶冶情操,承接前文Spring源码情操陶冶-AbstractApplicationContext#postProcessBeanFactory 约定:web.xml中配置的context ...

  3. Unity Shader入门教程(四)反射光斑的实现

    本节内容介绍PhongModel(也就是上文说的反射光的计算模型),采用的计算方法是BlinPhong(也即是用视线方向V+光源方向L的和,与N做点积,随后幂化得到高光反射系数)下图采用了csdn博文 ...

  4. JARVIS 手机监控局域网内PC

    JARVIS 通过反向的Socket连接,实现通过手机(或任何可以发送Http请求的设备)对局域网内PC的监控.在外时可以远程监视家里PC任务的执行情况,甚至远程唤醒家里的PC提前打开游戏也可以实现( ...

  5. CentOS安装配置MySql数据库

    CentOS版本7.2,MySql版本5.7 1.下载MySql安装源     wget https://dev.mysql.com/get/mysql57-community-release-el7 ...

  6. jsp 之 解决 Mysql net start mysql启动,提示发生系统错误 5 拒绝访问的问题

    在dos下运行net start mysql时 !!!提示发生系统错误 5:拒绝访问!只要切换到管理员模式就可以启动了. 所以我们要以管理员身份来运行cmd程序来启动mysql. 1.在开始菜单的搜索 ...

  7. Qt之对话框消失动画

    一.效果展示 最近做了一个提示框消失的功能,觉着挺有意思,以前一直以为Qt子窗口不能做淡出效果,其实Qt的淡出功能已经帮我们封装好了,我们仅仅只需要几行代码就可以做出酷炫的窗口关闭效果,写此篇文章的时 ...

  8. Shopex如何清理缓存

    一.进入后台,点击 右上角 的"关于" 二.点击:缓存系统: 三.点击"清空缓存" 四.清除成功!

  9. EntityFramework Core查询问题集锦(一)

    前言 和大家脱离了一段时间,有时候总想着时间挤挤总是会有的,但是并非人愿,后面会借助周末的时间来打理博客,如有问题可以在周末私信我或者加我QQ皆可,欢迎和大家一起探讨,本节我们来讨论EF Core中的 ...

  10. HAUT--1262--魔法宝石(暴力)

    1262: 魔法宝石 时间限制: 2 秒  内存限制: 64 MB提交: 525  解决: 157提交 状态 题目描述 小s想要创造n种魔法宝石.小s可以用ai的魔力值创造一棵第i种魔法宝石,或是使用 ...