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服務,因此不會配置默認的路由規則,會致使服務不可用。