Open API 即开放 API,也称开放平台。 所谓的开放 API(OpenAPI)是服务型网站常见的一种应用,网站的服务商将本身的网站服务封装成一系列
API(Application Programming Interface,应用编程接口)开放出去,供第三方开发者使用,这种行为就叫作开放网站的 API,所开放的 API 就被称做 OpenAPI(开放 API )。java
Representational State Transfer,翻译是”表现层状态转化”。能够总结为一句话:REST 是全部 Web 应用都应该遵照的架构设计指导原则。
面向资源是 REST 最明显的特征,对于同一个资源的一组不一样的操做。资源是服务器上一个可命名的抽象概念,资源是以名词为核心来组织的,首先关注的是名词。REST 要求,必须经过统一的接口来对资源执行各类操做。对于每一个资源只能执行一组有限的操做。mysql
什么是 RESTful API?
符合 REST 设计标准的 API,即 RESTful API。REST 架构设计,遵循的各项标准和准则,就是 HTTP 协议的表现,换句话说,HTTP 协议就是属于 REST 架构的设计模式。好比,无状态,请求-响应。。。git
那如何构建我们本身的Open API,这里作了简单的代码示例,包括基础的权限验证、限流控制,方便笔者本身构建其余应用服务时的调用。github
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency>
open-api工程很简单,实现业务逻辑,对外暴露接口便可,这里咱们简单示例,新建一个测试Controller,返回一行文本。web
@RestController @RequestMapping("/v1") public class TestController { @GetMapping("/info") public String info(){ return "Hello World!"; } }
启动服务,能够经过 http://localhost:8081/v1/info 正常访问。redis
建立Gateway工程(api-gateway),导入相关依赖算法
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-actuator</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-jpa</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-config-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId> </dependency> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-netflix-zuul</artifactId> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency>
CREATE TABLE `access_info` ( `access_key` varchar(32) NOT NULL COMMENT '访问码', `access_desc` varchar(32) NOT NULL COMMENT '访问说明', `visit_module` varchar(32) NOT NULL COMMENT '访问模块', `access_status` tinyint(3) NOT NULL DEFAULT '0' COMMENT '访问状态, 0:不容许访问 1:容许访问', `create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '建立时间', `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间', PRIMARY KEY (`access_key`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8;
@Data @Entity public class AccessInfo { /** * 访问码. */ @Id private String accessKey; /** * 访问说明. */ private String accessDesc; /** * 访问模块. */ private String visitModule; /** * 访问状态, 0:不容许访问 1:容许访问 */ private AccessStatus accessStatus; /** * 建立时间. */ private Date createTime; /** * 更新时间. */ private Date updateTime; } @Repository public interface AccessInfoRepository extends JpaRepository<AccessInfo, String> { } @Service public class AccessInfoService { private final AccessInfoRepository accessInfoRepository; public AccessInfoService(AccessInfoRepository accessInfoRepository) { this.accessInfoRepository = accessInfoRepository; } /** * 获取全部访问权限信息 * * @return */ public List<AccessInfo> findAll() { return accessInfoRepository.findAll(); } }
AccessFilter
,继承ZuulFilter
,来实现权限验证@Component public class AccessFilter extends ZuulFilter { private final AccessInfoService accessInfoService; public AccessFilter(AccessInfoService accessInfoService) { this.accessInfoService = accessInfoService; } @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { return 0; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { RequestContext currentContext = RequestContext.getCurrentContext(); HttpServletRequest request = currentContext.getRequest(); if (!isAuthorized(request)) { HttpStatus httpStatus = HttpStatus.UNAUTHORIZED; currentContext.setSendZuulResponse(false); currentContext.setResponseStatusCode(httpStatus.value()); } return null; } /** * 判断请求是否有权限 * * @param request * @return */ private boolean isAuthorized(HttpServletRequest request) { // 检查请求参数是否包含 access_key String access_key = request.getParameter("access_key"); if (!StringUtils.isEmpty(access_key)) { // 检查 access_key 是否匹配 List<AccessInfo> accessInfos = accessInfoService.findAll(); Optional<AccessInfo> accessInfo = accessInfos.stream() .filter(s -> access_key.equals(s.getAccessKey())).findAny(); if (accessInfo.isPresent()) { return true; } return false; } return false; } }
access_key
,网关服务会直接返回 401 未受权的错误;access_key
,可是与咱们数据库的安全验证不匹配,网关服务也会直接返回 401 错误;access_key
,也经过后台数据库验证,则调用成功以上已经实现了基本权限的验证,可是每次api的请求,都会进行数据库的校验。spring
2020-01-13 16:44:43.591 INFO 25028 --- [trap-executor-0] c.n.d.s.r.aws.ConfigClusterResolver : Resolving eureka endpoints via configuration Hibernate: select accessinfo0_.access_key as access_k1_0_, accessinfo0_.access_desc as access_d2_0_, accessinfo0_.access_status as access_s3_0_, accessinfo0_.create_time as create_t4_0_, accessinfo0_.update_time as update_t5_0_, accessinfo0_.visit_module as visit_mo6_0_ from access_info accessinfo0_ Hibernate: select accessinfo0_.access_key as access_k1_0_, accessinfo0_.access_desc as access_d2_0_, accessinfo0_.access_status as access_s3_0_, accessinfo0_.create_time as create_t4_0_, accessinfo0_.update_time as update_t5_0_, accessinfo0_.visit_module as visit_mo6_0_ from access_info accessinfo0_ Hibernate: select accessinfo0_.access_key as access_k1_0_, accessinfo0_.access_desc as access_d2_0_, accessinfo0_.access_status as access_s3_0_, accessinfo0_.create_time as create_t4_0_, accessinfo0_.update_time as update_t5_0_, accessinfo0_.visit_module as visit_mo6_0_ from access_info accessinfo0_
实际生产中确定不能这么操做,对数据库的压力太大,因此,咱们要对权限验证的数据进行缓存。sql
@EnableCaching
注解@EnableDiscoveryClient @EnableZuulProxy @SpringBootApplication @EnableCaching public class ApiGatewayApplication { public static void main(String[] args) { SpringApplication.run(ApiGatewayApplication.class, args); } }
AccessInfo
须要实现Serializable
接口,方便序列化后保存在redis中。@Data @Entity public class AccessInfo implements Serializable { //... }
@Cacheable(value = "api-gateway:accessInfo") public List<AccessInfo> findAll() { return accessInfoRepository.findAll(); }
RateLimiter是guava提供的基于令牌桶算法的实现类,能够很是简单的完成限流特技,而且根据系统的实际状况来调整生成token的速率。docker
RateLimitFilter
继承ZuulFilter
,定义一个RateLimiter,这里为了测试方便,每秒设置最多2个请求/** * 限流 */ @Component public class RateLimitFilter extends ZuulFilter { //每秒产生N个令牌 private static final RateLimiter rateLimiter = RateLimiter.create(2); @Override public String filterType() { return PRE_TYPE; } @Override public int filterOrder() { return SERVLET_DETECTION_FILTER_ORDER - 1; } @Override public boolean shouldFilter() { return true; } @Override public Object run() { if (!rateLimiter.tryAcquire()) { RequestContext currentContext = RequestContext.getCurrentContext(); HttpStatus httpStatus = HttpStatus.TOO_MANY_REQUESTS; currentContext.setSendZuulResponse(false); currentContext.setResponseStatusCode(httpStatus.value()); } return null; } }
将打包的jar文件生成docker镜像,而后部署在我的服务器上,以前笔者已经部署过服务注册中心(eureka-server)和统一配置中心(config-server),因此把两个新应用注册并部署便可。
这里是微服务部署,将服务注册到服务中心,并从统一配置中心获取配置属性,后面能够经过实例名称来进行访问。
eureka: client: serviceUrl: defaultZone: http://eureka1:8761/eureka/,http://eureka2:8762/eureka/ # 指定服务注册地址 spring: application: name: open-api # 应用名称 server: port: 8081
eureka: client: serviceUrl: defaultZone: http://eureka-server:8761/eureka/ #指定服务注册地址 spring: application: name: api-gateway #应用名称 cloud: config: discovery: enabled: true service-id: config-server
依次启动eureka-server、config-server、open-api、api-gateway服务,这样咱们就能够经过访问域名地址来访问本身的API了。这里尤为注意open-api启动后再启动api-gateway服务,否则api-gateway服务在eureka-server上没法找到open-api服务,因此不会配置默认的路由规则,会致使服务不可用。