什么是开放平台
企业需要把自己的服务通过接口的形式对外提供,提供接口的平台称之为开放平台。比如支付宝开放平台,淘宝开放平台。
调用接口那一方一般称之为ISV,独立软体开发商(independent software vendor),开放平台这一方称之为ISP,网络服务提供者(Internet Service Provider)。通常情况下需要为ISV分配一个appKey和appSecret,可以简单的理解为用户名密码,有了这个才能正常调用开放平台接口。
为了保证请求参数的合法性,客户端需要生成一个签名串,然后开放平台需要校验这个签名串。ISV可以通过appKey和AppSecret来生成签名串,这样就能保证客户端请求是合法的,服务端需要校验签名串是否合法,appKey是否合法,这里开放平台会提供一套签名算法,常见的有:支付宝开放平台签名算法
如何设计一个开放平台
开放平台的一个重要部分就是鉴权,鉴权功能和具体的业务无关,可以单独拿出来做,如果是单体应用的话可以把这部分操作写在一个Controller中。如果是微服务体系的话把鉴权部分放在网关是一个不错的选择,因为网关是一个统一入口,在入口处做好鉴权,后续的微服务不需要再做鉴权处理了,只需实现自己的业务逻辑即可。
在Spring Cloud微服务体系当中,充当网关的角色常见有两个,一个是Zuul,另一个是Spring Cloud Gateway。Zuul本质是一个Servlet,IO模型是BIO,阻塞式,而Spring Cloud Gateway基于Netty开发的,IO模型是AIO,也就是异步IO,在处理高并发请求场景下,Spring Cloud Gateway具有明显优势。两者各有优缺点,Zuul优点是架构简单,扩展起来比较方便,缺点是在处理高并发请求下稍显力不从心,Spring Cloud Gateway优点是高性能,可以处理高并发请求,缺点是架构复杂,需要了解异步编程、Netty、React等框架基本原理,调试起来比较困难。
本篇拿Spring Cloud Gateway来演示如何设计一个简单的开放平台。
首先简单介绍下Spring Cloud Gateway的基本功能,作为网关,首要的功能是路由功能,简单理解就是将一个A请求变成B请求,类似于Nginx的反向代理。另一个功能请求过滤,Spring Cloud Gateway允许开发者实现自定义过滤器,用来处理当前请求。
Spring Cloud Gateway的路由配置有两种,一种是写在配置文件里面,一种使用代码实现(Java DSL)。
- 使用配置文件
server:
port: 8090
spring:
cloud:
gateway:
routes:
- id: host_route
uri: https://www.baidu.com/
pedicates:
- Path=/
上面这个配置,在浏览器访问http://localhost:8090
,页面会出现百度首页。
另一种使用Java代码形式:
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder routeBuilder) {
return routeBuilder.routes()
.route("host_route", r ->
r.path("/")
.uri("https://www.baidu.com/")
)
.build();
}
其效果跟配置文件是一样的,如果路由配置固定不变可写在配置文件中,如果涉及到动态改变路由配置,就必须写在Java代码中了,因为代码更灵活。
假设我们我们的开放平台接口地址为:http://open.xx.com
,该接口提供一个参数method
,表示接口名,通过接口名来决定具体请求哪个微服务。比如访问http://open.xx.com/?method=goods.get
,转发到商品微服务http://192.168.1.1:8080/getGoods
,如下图所示:
由此可见,在网关需要配置一套路由:
spring:
cloud:
gateway:
routes:
- id: getGoods
uri: http://192.168.1.1:8080
predicates:
- Path=/getGoods
- id: getOrder
uri: http://192.168.1.2:8080
predicates:
- Path=/getOrder
接下来需要在网关中做几件事情:
- 鉴权,鉴权失败返回错误码
- 鉴权通过,进行路由转发
这些事情都可以在全局过滤器中执行,过滤器代码如下:
package com.example.gateway.filter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilter;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import java.util.HashMap;
import java.util.Map;
/**
网关入口过滤器
* @author tanghc
*/
@Component
public class IndexFilter implements WebFilter, Ordered {
private static Map<String, String> methodPathMap = new HashMap<>(16);
// 存放接口名对应的path
static {
methodPathMap.put("goods.get", "/getGoods");
methodPathMap.put("order.get", "/getOrder");
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// 获取请求参数
Map<String, String> query = exchange.getRequest().getQueryParams().toSingleValueMap();
// 鉴权
this.check(query);
String method = query.get("method");
String path = methodPathMap.get(method);
if (path != null) {
// 复制一个新的request
ServerHttpRequest newRequest = exchange.getRequest()
.mutate()
// == 关键在这里,重新定义转发的path
.path(path)
.build();
// 复制一个新的exchange,request用新的
ServerWebExchange newExchange = exchange
.mutate()
.request(newRequest)
.build();
// 向后转发新的exchange
return chain.filter(newExchange);
}
return chain.filter(exchange);
}
/**
* 鉴权
* @param query
*/
private void check(Map<String, String> query) {
}
@Override
public int getOrder() {
return Ordered.HIGHEST_PRECEDENCE;
}
}
这里通过一个map来存放method和path的对应关系,然后通过重写request中的path来实现转发功能。在浏览器请求http://localhost:8090/?method=order.get
,转发到http://192.168.1.1:8080/getGoods
至此开放平台的一个基本功能就实现了,不过依然存在几个问题:
- 路由配置是写死的,无法动态加载
- 接口名对应的Path是写死的,无法动态变更
- 如何获取POST请求参数
- 如何获取上传文件请求参数
针对第一个问题,解决办法是使用Java代码(Java DSL)来配置路由,具体的思路是让微服务来维护路由关系,然后网关在启动完毕后去各个微服务端拉取路由配置,保存到本地。
第二个问题,可以使用动态配置,如Spring Cloud Config或阿波罗配置来动态改变。
以上涉及到的所有问题在SOP中已经全部实现。