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);
代码仓库 (博客配套代码)
结束
这样,规范了远程服务的调用,只关心接口编号和接口的入参和出参,能够增加沟通效率,并且也有了轻量级的服务治理机制,服务间的调用更可控,到最后,配置文件一拉出来一清二楚.
想获得最快更新,请关注公众号
来源:http://www.cnblogs.com/itkk/p/7473847.html