mirror of
https://gitee.com/wangkang/udf.git
synced 2026-10-21 11:59:59 +00:00
404 lines
12 KiB
Markdown
404 lines
12 KiB
Markdown
# 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元数据
|
|
|
|
|
|
``` java
|
|
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元数据
|
|
|
|
|
|
``` java
|
|
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类
|
|
|
|
|
|
``` java
|
|
@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文件中进行配置
|
|
|
|
|
|
``` 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拦截器
|
|
|
|
|
|
``` java
|
|
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方法-取出认证信息
|
|
|
|
|
|
``` java
|
|
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方法-校验
|
|
|
|
|
|
``` java
|
|
//判断环境(开发环境无需校验)
|
|
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类
|
|
|
|
|
|
``` java
|
|
@Configuration
|
|
@ConfigurationProperties(prefix = "org.itkk.rms.config")
|
|
@Validated
|
|
public class RmsConfig {
|
|
|
|
//RMS扫描路径
|
|
@NotNull
|
|
private String rmsPathPatterns;
|
|
|
|
.........
|
|
|
|
}
|
|
```
|
|
|
|
|
|
####5.定义RmsConfig类-注册bean
|
|
|
|
|
|
``` java
|
|
@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文件中进行配置
|
|
|
|
|
|
``` properties
|
|
#拦截路径
|
|
org.itkk.rms.config.rmsPathPatterns=/service/**
|
|
```
|
|
|
|
|
|
###3.实现Rms类
|
|
|
|
|
|
####1.定义rms类
|
|
|
|
|
|
``` java
|
|
@Component
|
|
public class Rms {
|
|
//应用名称
|
|
@Value("${spring.application.name}")
|
|
private String springApplicationName;
|
|
//restTemplate
|
|
@Autowired
|
|
private RestTemplate restTemplate;
|
|
//配置
|
|
@Autowired
|
|
private RmsProperties rmsProperties;
|
|
```
|
|
|
|
|
|
####2.定义rms类-call方法
|
|
|
|
|
|
``` java
|
|
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类-其他方法
|
|
|
|
|
|
``` java
|
|
//构建请求头
|
|
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.客户端调用
|
|
|
|
|
|
``` java
|
|
//获得文件信息
|
|
ResponseEntity<RestResponse<FileInfo>> fileInfo = rms.call("FILE_4", fileParam, null,
|
|
new ParameterizedTypeReference<RestResponse<FileInfo>>() {
|
|
}, null);
|
|
```
|
|
|
|
## **代码仓库** (博客配套代码)
|
|
|
|
- [udf-starter : 基础项目,脚手架,框架](https://gitee.com/wangkang/udf)
|
|
|
|
- [udf-sample : 集成样例](https://gitee.com/wangkang/udf-sample)
|
|
|
|
##结束
|
|
|
|
这样,规范了远程服务的调用,只关心接口编号和接口的入参和出参,能够增加沟通效率,并且也有了轻量级的服务治理机制,服务间的调用更可控,到最后,配置文件一拉出来一清二楚.
|
|
|
|
---------
|
|
|
|
想获得最快更新,请关注公众号
|
|
|
|

|
|
|