springcloud api-gateway详解
網(wǎng)關(guān)
api-gateway
api-gateway是一款輕量級(jí)、高性能、易擴(kuò)展的基于zuul的網(wǎng)關(guān)產(chǎn)品,提供API的統(tǒng)一管理服務(wù)、涵蓋API發(fā)布、管理、運(yùn)維的全生命周期管理。對(duì)內(nèi)輔助用戶簡單、快速、低成本、低風(fēng)險(xiǎn)的實(shí)現(xiàn)微服務(wù)聚合、前后端分離、系統(tǒng)集成等功能;對(duì)外面向合作伙伴、開發(fā)者開放服務(wù)。通過使用API-Gateway,我們能快速幫助用戶實(shí)現(xiàn)傳統(tǒng)ESB面臨的主要場景,又能滿足新型業(yè)務(wù)場景(移動(dòng)應(yīng)用等)所需的高性能、安全、可靠等要求。
通用網(wǎng)關(guān)設(shè)計(jì)
軟負(fù)載ZUUL
網(wǎng)關(guān)活動(dòng)圖
api-gateway在項(xiàng)目中的位置
api gateway作用
- 簡化客戶端調(diào)用復(fù)雜度
在微服務(wù)架構(gòu)模式下后端服務(wù)的實(shí)例數(shù)一般是動(dòng)態(tài)的,對(duì)于客戶端而言,很難發(fā)現(xiàn)動(dòng)態(tài)改變的服務(wù)實(shí)例的訪問地址信息。因此在基于微服務(wù)的項(xiàng)目中為了簡化前端的調(diào)用邏輯,通常會(huì)引入API Gateway作為輕量級(jí)網(wǎng)關(guān),同時(shí)API Gateway中也會(huì)實(shí)現(xiàn)相關(guān)的認(rèn)證邏輯從而簡化內(nèi)部服務(wù)之間相互調(diào)用的復(fù)雜度。 - 數(shù)據(jù)裁剪以及聚合
通常而言不同的客戶端在顯示時(shí)對(duì)于數(shù)據(jù)的需求是不一致的,比如手機(jī)端或者Web端又或者在低延遲的網(wǎng)絡(luò)環(huán)境或者高延遲的網(wǎng)絡(luò)環(huán)境。因此為了優(yōu)化客戶端的使用體驗(yàn),API Gateway可以對(duì)通用性的響應(yīng)數(shù)據(jù)進(jìn)行裁剪以適應(yīng)不同客戶端的使用需求,同時(shí)還可以將多個(gè)API調(diào)用邏輯進(jìn)行聚合,從而減少客戶端的請(qǐng)求數(shù),優(yōu)化客戶端用戶體驗(yàn)。 - 多渠道支持
當(dāng)然我們還可以針對(duì)不同的渠道和客戶端提供不同的API Gateway,對(duì)于該模式的使用由另外一個(gè)大家熟知的方式叫Backend for front-end,在Backend for front-end模式當(dāng)中,我們可以針對(duì)不同的客戶端分別創(chuàng)建其BFF。 - 遺留系統(tǒng)的微服務(wù)改造
對(duì)于遺留系統(tǒng)而言進(jìn)行微服務(wù)改造通常是由于原有的系統(tǒng)存在或多或少的問題,比如技術(shù)債務(wù),代碼質(zhì)量,可維護(hù)性,可擴(kuò)展性等等。API Gateway的模式同樣適用于這一類遺留系統(tǒng)的改造,通過微服務(wù)化的改造逐步實(shí)現(xiàn)對(duì)原有系統(tǒng)中的問題的修復(fù),從而提升對(duì)于原有業(yè)務(wù)相應(yīng)力的提升。通過引入抽象層,逐步使用新的實(shí)現(xiàn)替換舊的實(shí)現(xiàn)。
在Spring Cloud體系中,Spring Cloud Zuul就是提供負(fù)載均衡,反向代理,權(quán)限認(rèn)證的一個(gè)API Gateway。
api-gateway代碼分析
開啟ZUUL
Zuul提供的功能
- 認(rèn)證鑒權(quán)-可以識(shí)別訪問資源的每一個(gè)請(qǐng)求,拒絕不滿足的請(qǐng)求
- 監(jiān)控埋點(diǎn),跟蹤有意義的數(shù)據(jù)并統(tǒng)計(jì),以便生成有意義的生產(chǎn)視圖
- 熔斷限流,為每一個(gè)請(qǐng)求分配容量,并丟棄超過限制的請(qǐng)求
- 抗壓設(shè)計(jì),線程池隔離增大并發(fā)能力
網(wǎng)關(guān)的2層超時(shí)調(diào)優(yōu)
hystrix ribbon活動(dòng)圖
hystrix ribbon配置
#設(shè)置最大容錯(cuò)超時(shí)時(shí)間 hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds: 90000 #設(shè)置最大超時(shí)時(shí)間 ribbon: eager-load:enabled: trueServerListRefreshInterval: 10 #刷新服務(wù)列表源的間隔時(shí)間httpclient:enabled: falseokhttp:enabled: true ReadTimeout: 90000 ConnectTimeout: 90000 OkToRetryOnAllOperations: trueMaxAutoRetries: 1MaxAutoRetriesNextServer: 1源碼分析
zuul內(nèi)部代碼
類圖
參考:https://www.jianshu.com/p/295e51bc1518
zuul基于eureka的服務(wù)發(fā)現(xiàn)路由
路由注冊相關(guān)邏輯
org.springframework.cloud.netflix.zuul.filters.discovery.DiscoveryClientRouteLocator
啟動(dòng)時(shí)配置mapping等信息
zuul轉(zhuǎn)發(fā)邏輯
以訪問http://127.0.0.1:9200/api-user/users-anon/login?username=admin為例
- 通過/api-user/users-anon/login查映射表
- 找到ZuulRoute映射關(guān)系
- 執(zhí)行pre過濾器org.springframework.cloud.netflix.zuul.filters.pre.PreDecorationFilter處理是否增加請(qǐng)求頭等信息
- 執(zhí)行route過濾器
- org.springframework.cloud.netflix.zuul.filters.route.RibbonRoutingFilter
總結(jié)
- pre:這種過濾器在請(qǐng)求被轉(zhuǎn)發(fā)之前調(diào)用,一般用來實(shí)現(xiàn)身份驗(yàn)證等
- routing:這種路由是用來路由到不同的后端服務(wù)的,底層可以使用httpclient或者ribbon請(qǐng)求微服務(wù)
- post:當(dāng)請(qǐng)求轉(zhuǎn)發(fā)到微服務(wù)以后,會(huì)調(diào)用當(dāng)前類型的過濾器。通常用來為響應(yīng)天啊及標(biāo)椎的HTTP Header、收集統(tǒng)計(jì)信息,等
- error:當(dāng)發(fā)生錯(cuò)誤是執(zhí)行的過濾器
網(wǎng)關(guān)自定義過濾器
- brave.servlet.TracingFilter :生成traceId
- com.open.capacity.common.filter.TraceContextFilter:傳遞traceId
- com.open.capacity.client.filter.AccessFilter:傳遞token
- com.open.capacity.client.filter.RequestStatFilter::傳遞traceId
- com.open.capacity.client.filter.ResponseStatFilter:響應(yīng)頭增加traceId
基于阿波羅配置中心的動(dòng)態(tài)路由
參考代碼:https://gitee.com/owenwangwen/config-center/tree/master/apollo-gateway
阿波羅官方已吸收此案例在github可下載
https://github.com/ctripcorp/apollo-use-cases/tree/master/spring-cloud-zuul/src/main/java/com/ctrip/framework/apollo/use/cases/spring/cloud/zuul
- 創(chuàng)建項(xiàng)目
- 創(chuàng)建配置
- 項(xiàng)目綁定配置
api-gateway 構(gòu)建資源服務(wù)器
<!-- 資源服務(wù)器 --><dependency><groupId>com.open.capacity</groupId><artifactId>uaa-client-spring-boot-starter</artifactId></dependency>uaa-client-spring-boot-starter源碼分析
網(wǎng)關(guān)認(rèn)證處理流程圖
網(wǎng)關(guān)白名單
security:oauth2:ignored: /test163/** , /api-auth/** , /doc.html ,/test111 ,/api-user/users-anon/login, /api-user/users/save, /user-center/users-anon/login,/document.html,**/v2/api-docs,/oauth/** ,/login.html ,/user/login,/**/**.css ,/**/**.js ,/getVersiontoken:store:type: redis網(wǎng)關(guān)統(tǒng)一異常
認(rèn)證處理核心代碼
public Authentication authenticate(Authentication authentication) throws AuthenticationException {if (authentication == null) {throw new InvalidTokenException("Invalid token (token not found)");}String token = (String) authentication.getPrincipal();OAuth2Authentication auth = tokenServices.loadAuthentication(token);if (auth == null) {throw new InvalidTokenException("Invalid token: " + token);}Collection<String> resourceIds = auth.getOAuth2Request().getResourceIds();if (resourceId != null && resourceIds != null && !resourceIds.isEmpty() && !resourceIds.contains(resourceId)) {throw new OAuth2AccessDeniedException("Invalid token does not contain resource id (" + resourceId + ")");}checkClientDetails(auth);if (authentication.getDetails() instanceof OAuth2AuthenticationDetails) {OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) authentication.getDetails();// Guard against a cached copy of the same detailsif (!details.equals(auth.getDetails())) {// Preserve the authentication details from the one loaded by token servicesdetails.setDecodedDetails(auth.getDetails());}}auth.setDetails(authentication.getDetails());auth.setAuthenticated(true);return auth;授權(quán)流程
啟用授權(quán)
@Beanpublic OAuth2WebSecurityExpressionHandler oAuth2WebSecurityExpressionHandler(ApplicationContext applicationContext) {OAuth2WebSecurityExpressionHandler expressionHandler = new OAuth2WebSecurityExpressionHandler();expressionHandler.setApplicationContext(applicationContext);return expressionHandler;}網(wǎng)關(guān)認(rèn)證授權(quán)總結(jié)
- 訪問者(Accessor)需要訪問某個(gè)資源(Resource)是這個(gè)場景最原始的需求,但并不是誰都可以訪問資源,也不是任何資源都允許任何人來訪問,所以中間我們要加入一些檢查和防護(hù)
- 在訪問資源的所經(jīng)之路上,可能遇到細(xì)菌,病毒,不管怎么樣,對(duì)于要防護(hù)的資源來說最好的方法就是設(shè)關(guān)卡點(diǎn),對(duì)于上圖的FilterSecurityInvation,MethodIncation,Jointpoint,這些在spring security oauth中統(tǒng)稱SecuredObjects
- 我們知道在哪里設(shè)置關(guān)卡點(diǎn)最合適,下一步就是設(shè)置關(guān)卡,對(duì)應(yīng)FileSecurityInterceptor,MethodSecurityInterceptor,AspectSecurityInterceptor,
這些關(guān)卡統(tǒng)一的抽象類是AbstractSecurityInterceptor - 有關(guān)卡點(diǎn),關(guān)卡了以后,到底誰該攔截誰不應(yīng)該呢,spring security oauth中由 AccessDecisionManager控制
- 最后一個(gè)問題,這個(gè)誰怎么定義,我們總得知道當(dāng)前這個(gè)訪問者是誰才能告訴AccessDecisionManager攔截還是放行,在spring security oauth框架中AuthenticationManager將解決訪問者身份認(rèn)證問題,只有確定你在冊了,才可以給授權(quán)訪問。AuthenticationManager,AccessDecisionManager,AbstractSecurityInterceptor屬于spring security框架的基礎(chǔ)鐵三角。
- 有了以上骨架,真正執(zhí)行防護(hù)任務(wù)的其實(shí)是SecurityFilterChain中定于的一系列Filter,其中ExceptionTranslationFilter,它負(fù)責(zé)接待或者送客,如果訪問者來訪,對(duì)方?jīng)]有報(bào)上名來,那么,它就會(huì)讓訪客去登記認(rèn)證(找AuthenticationManager做認(rèn)證),如果對(duì)方報(bào)上名了,但認(rèn)證失敗,那么請(qǐng)重新認(rèn)證送客,送客的方式是拋出相應(yīng)的Exception,所以名字叫做ExceptionTranslationFilter。
- 最后,這個(gè)filter序列中可能不滿足我們的需求,比如增加驗(yàn)證碼,所以我們需要在其中穿插自己的Filter實(shí)現(xiàn)類,為定制和擴(kuò)展Spring Security Oauth的防護(hù)體系。
- spring security內(nèi)置的filter序列
- 執(zhí)行過濾鏈
網(wǎng)關(guān)api權(quán)限設(shè)計(jì)
相同用戶,不同應(yīng)用的權(quán)限隔離
客戶端模式 :
客戶端A 申請(qǐng)的token ,可以訪問/api-user/menu/current ,
客戶端B 申請(qǐng)的token,不讓訪問/api-user/menu/current
密碼模式:
客戶端模式 :
客戶端A admin用戶 申請(qǐng)的token ,可以訪問/api-user/menu/current ,
客戶端B admin用戶 申請(qǐng)的token,不讓訪問/api-user/menu/current
參考issue:https://gitee.com/owenwangwen/open-capacity-platform/issues/IRG23
網(wǎng)關(guān)引依賴
網(wǎng)關(guān)是否開啟基于應(yīng)用隔離,代碼注釋了,只是基于token的合法性校驗(yàn),按建議開啟是否啟用api接口服務(wù)權(quán)限
OpenAuthorizeConfigManager
游樂場買了通票,有些地方可以隨便玩,有些地方另外
單獨(dú)校驗(yàn)買票
config.anyRequest().authenticated() ;
//這種通票,token校驗(yàn)正確訪問
config.anyRequest().access("@rbacService.hasPermission(request, authentication)"); //這種另外
單獨(dú)校驗(yàn),適用于網(wǎng)關(guān)對(duì)api權(quán)限校驗(yàn)
通過clientID隔離服務(wù)權(quán)限
通過應(yīng)用分配服務(wù)權(quán)限
網(wǎng)關(guān)hystrix-dashboard
api-gateway 應(yīng)用訪問次數(shù)控制
oauth_client_details 增加
| if_limit | 是否需要控制訪問次數(shù) |
| limit_count | 訪問閥值 |
auth-server 啟動(dòng)
redis存儲(chǔ)結(jié)構(gòu)
加載oauth_client_details 到redis
應(yīng)用訪問次數(shù)控制過濾器
/*** Created by owen on 2017/9/10. 根據(jù)應(yīng)用 url 限流 oauth_client_details if_limit 限流開關(guān)* limit_count 閾值*/ @Component public class RateLimitFilter extends ZuulFilter {private static Logger logger = LoggerFactory.getLogger(RateLimitFilter.class);private ThreadLocal<Result> error_info = new ThreadLocal<Result>();@Autowiredprivate RedisLimiterUtils redisLimiterUtils;@Autowiredprivate ObjectMapper objectMapper;@ResourceSysClientServiceImpl sysClientServiceImpl;@Overridepublic String filterType() {return "pre";}@Overridepublic int filterOrder() {return 0;}@Overridepublic boolean shouldFilter() {return true;}@Overridepublic Object run() {try {RequestContext ctx = RequestContext.getCurrentContext();HttpServletRequest request = ctx.getRequest();if (!checkLimit(request)) {logger.error("too many requests!");error_info.set(Result.failedWith(null, 429, "too many requests!"));serverResponse(ctx, 429);return null;}} catch (Exception e) {e.printStackTrace();}return null;}/**** 統(tǒng)一禁用輸出* * @param ctx* @param ret_message* 輸出消息* @param http_code* 返回碼*/public void serverResponse(RequestContext ctx, int http_code) {try {ctx.setSendZuulResponse(false);outputChineseByOutputStream(ctx.getResponse(), error_info);ctx.setResponseStatusCode(http_code);} catch (IOException e) {StackTraceElement stackTraceElement= e.getStackTrace()[0];logger.error("serverResponse:" + "---|Exception:" +stackTraceElement.getLineNumber()+"----"+ e.getMessage());}}/*** 使用OutputStream流輸出中文* * @param request* @param response* @throws IOException*/public void outputChineseByOutputStream(HttpServletResponse response, ThreadLocal<Result> data) throws IOException {/*** 使用OutputStream輸出中文注意問題: 在服務(wù)器端,數(shù)據(jù)是以哪個(gè)碼表輸出的,那么就要控制客戶端瀏覽器以相應(yīng)的碼表打開,* 比如:outputStream.write("中國".getBytes("UTF-8"));//使用OutputStream流向客戶端瀏覽器輸出中文,以UTF-8的編碼進(jìn)行輸出* 此時(shí)就要控制客戶端瀏覽器以UTF-8的編碼打開,否則顯示的時(shí)候就會(huì)出現(xiàn)中文亂碼,那么在服務(wù)器端如何控制客戶端瀏覽器以以UTF-8的編碼顯示數(shù)據(jù)呢?* 可以通過設(shè)置響應(yīng)頭控制瀏覽器的行為,例如: response.setHeader("content-type",* "text/html;charset=UTF-8");//通過設(shè)置響應(yīng)頭控制瀏覽器以UTF-8的編碼顯示數(shù)據(jù)*/OutputStream outputStream = response.getOutputStream();// 獲取OutputStream輸出流response.setHeader("content-type", "application/json;charset=UTF-8");// 通過設(shè)置響應(yīng)頭控制瀏覽器以UTF-8的編碼顯示數(shù)據(jù),如果不加這句話,那么瀏覽器顯示的將是亂碼/*** data.getBytes()是一個(gè)將字符轉(zhuǎn)換成字節(jié)數(shù)組的過程,這個(gè)過程中一定會(huì)去查碼表,* 如果是中文的操作系統(tǒng)環(huán)境,默認(rèn)就是查找查GB2312的碼表, 將字符轉(zhuǎn)換成字節(jié)數(shù)組的過程就是將中文字符轉(zhuǎn)換成GB2312的碼表上對(duì)應(yīng)的數(shù)字* 比如: "中"在GB2312的碼表上對(duì)應(yīng)的數(shù)字是98 "國"在GB2312的碼表上對(duì)應(yīng)的數(shù)字是99*//*** getBytes()方法如果不帶參數(shù),那么就會(huì)根據(jù)操作系統(tǒng)的語言環(huán)境來選擇轉(zhuǎn)換碼表,如果是中文操作系統(tǒng),那么就使用GB2312的碼表*/String msg = objectMapper.writeValueAsString(data.get());byte[] dataByteArr = msg.getBytes("UTF-8");// 將字符轉(zhuǎn)換成字節(jié)數(shù)組,指定以UTF-8編碼進(jìn)行轉(zhuǎn)換outputStream.write(dataByteArr);// 使用OutputStream流向客戶端輸出字節(jié)數(shù)組}public boolean checkLimit(HttpServletRequest request) {// 解決zuul token傳遞問題Authentication user = SecurityContextHolder.getContext().getAuthentication();if (user != null) {if (user instanceof OAuth2Authentication) {try {OAuth2Authentication athentication = (OAuth2Authentication) user;OAuth2AuthenticationDetails details = (OAuth2AuthenticationDetails) athentication.getDetails();String clientId = athentication.getOAuth2Request().getClientId();Map client = sysClientServiceImpl.getClient(clientId);if(client!=null){String flag = String.valueOf(client.get("if_limit") ) ;if("1".equals(flag)){String accessLimitCount = String.valueOf(client.get("limit_count") );if (!accessLimitCount.isEmpty()) {Result result = redisLimiterUtils.rateLimitOfDay(clientId, request.getRequestURI(),Long.parseLong(accessLimitCount));if (-1 == result.getResp_code()) {logger.error("token:" + details.getTokenValue() + result.getResp_msg());// ((ResultMsg)// this.error_info.get()).setMsg("clientid:" +// client_id + ":token:" + accessToken + ":" +// result.getMsg());// ((ResultMsg) this.error_info.get()).setCode(401);return false;}}}}} catch (Exception e) {StackTraceElement stackTraceElement= e.getStackTrace()[0];logger.error("checkLimit:" + "---|Exception:" +stackTraceElement.getLineNumber()+"----"+ e.getMessage());}}}return true;} }RedisLimiterUtils核心類
@Component public class RedisLimiterUtils {public static final String API_WEB_TIME_KEY = "time_key:";public static final String API_WEB_COUNTER_KEY = "counter_key:";private static final String EXCEEDS_LIMIT = "規(guī)定的時(shí)間內(nèi)超出了訪問的限制!";private static Logger logger = LoggerFactory.getLogger(RedisLimiterUtils.class);@ResourceRedisTemplate<Object, Object> redisTemplate;@Resource(name = "stringRedisTemplate")ValueOperations<String, String> ops;@Resource(name = "redisTemplate")ValueOperations<Object, Object> objOps;@ResourceRedisUtil redisUtil;public Result IpRateLimiter(String ip, int limit, int timeout) {String identifier = UUID.randomUUID().toString();String time_key = "time_key:ip:" + ip;String counter_key = "counter_key:ip:" + ip;if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {redisUtil.set(time_key, identifier, timeout);redisUtil.set(counter_key, 0);}if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {logger.info(EXCEEDS_LIMIT);return Result.failedWith(null, -1, EXCEEDS_LIMIT);}return Result.succeedWith(null, 0, "調(diào)用次數(shù):" + this.ops.get(counter_key) );}public Result clientRateLimiter(String clientid, int limit, int timeout) {String identifier = UUID.randomUUID().toString();String time_key = "time_key:clientid:" + clientid;String counter_key = "counter_key:clientid:" + clientid;if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {redisUtil.set(time_key, identifier, timeout);redisUtil.set(counter_key, 0);}if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {logger.info(EXCEEDS_LIMIT);return Result.failedWith(null, -1, EXCEEDS_LIMIT);}return Result.succeedWith(null, 0, "調(diào)用次數(shù):" + this.ops.get(counter_key) );}public Result urlRateLimiter(String path, int limit, int timeout) {String identifier = UUID.randomUUID().toString();String time_key = "time_key:path:" + path;String counter_key = "counter_key:path:" + path;if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {redisUtil.set(time_key, identifier, timeout);redisUtil.set(counter_key, 0);}if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {logger.info(EXCEEDS_LIMIT);return Result.failedWith(null, -1, EXCEEDS_LIMIT);}return Result.succeedWith(null, 0, "調(diào)用次數(shù):" + this.ops.get(counter_key) );}public Result clientPathRateLimiter(String clientid, String access_path, int limit, int timeout) {String identifier = UUID.randomUUID().toString();LocalDate today = LocalDate.now();String time_key = "time_key:clientid:" + clientid + ":path:" + access_path;String counter_key = "counter_key:clientid:" + clientid + ":path:" + access_path;if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {redisUtil.set(time_key, identifier, timeout);redisUtil.set(counter_key, 0);}if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {logger.info(EXCEEDS_LIMIT);return Result.failedWith(null, -1, EXCEEDS_LIMIT);}return Result.succeedWith(null, 0, "調(diào)用次數(shù):" + this.ops.get(counter_key) );}public Result rateLimitOfDay(String clientid, String access_path, long limit) {String identifier = UUID.randomUUID().toString();LocalDate today = LocalDate.now();String time_key = "time_key:date:" + today + ":clientid:" + clientid + ":path:" + access_path;String counter_key = "counter_key:date:" + today + ":clientid:" + clientid + ":path:" + access_path;if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {//當(dāng)天首次訪問,初始化訪問計(jì)數(shù)=0,有效期24hredisUtil.set(time_key, identifier, 24 * 60 * 60);redisUtil.set(counter_key, 0);}//累加訪問次數(shù), 超出配置的limit則返回錯(cuò)誤if (redisUtil.incr(counter_key, 1) > limit) {logger.info("日內(nèi)超出了訪問的限制!");return Result.failedWith(null, -1, "日內(nèi)超出了訪問的限制!");}return Result.succeedWith(null, 0, "調(diào)用總次數(shù):" + this.ops.get(counter_key) );}public Result acquireRateLimiter(String clientid, String access_path, int limit, int timeout) {String identifier = UUID.randomUUID().toString();LocalDate today = LocalDate.now();String time_key = "time_key:date:" + today + ":clientid:" + clientid + ":path:" + access_path;String counter_key = "counter_key:date:" + today + ":clientid:" + clientid + ":path:" + access_path;if (!redisUtil.hasKey(time_key) || redisUtil.getExpire(time_key) <= 0) {redisUtil.set(time_key, identifier, timeout);redisUtil.set(counter_key, 0);}if (redisUtil.hasKey(time_key) && redisUtil.incr(counter_key, 1) > limit) {logger.info(EXCEEDS_LIMIT);return Result.failedWith(null, -1, EXCEEDS_LIMIT);}return Result.succeedWith(null, 0, "調(diào)用次數(shù):" + this.ops.get(counter_key) );}public void save(String tokenType, String Token, int timeout) {redisUtil.set(tokenType, Token, timeout);}public String getToken(String tokenType) {return redisUtil.get(tokenType).toString();}public void saveObject(String key, Object obj, long timeout) {redisUtil.set(key, obj, timeout);}public void saveObject(String key, Object obj) {redisUtil.set(key, obj);}public Object getObject(String key) {return redisUtil.get(key);}public void removeObject(String key) {redisUtil.del(key);} }生產(chǎn)軟負(fù)載NGINX構(gòu)建ZUUL集群
pom核心依賴
總結(jié)
以上是生活随笔為你收集整理的springcloud api-gateway详解的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: eureka-server详解
- 下一篇: Eclipse安装反编译插件