From 1b3e122313f3a9678d3bebc7387944f71bd21f9c Mon Sep 17 00:00:00 2001
From: yangzl <youngzil@163.com>
Date: Sun, 3 Nov 2024 21:33:21 +0800
Subject: [PATCH 01/15] feat(portal): Add current limiting function to
 ConsumerToken

---
 .../apollo/openapi/entity/ConsumerToken.java  |  12 ++
 .../filter/ConsumerAuthenticationFilter.java  |  49 +++++++-
 .../openapi/service/ConsumerService.java      |  14 ++-
 .../apollo/openapi/util/ConsumerAuthUtil.java |   5 +
 .../portal/component/config/PortalConfig.java |   8 ++
 .../AuthFilterConfiguration.java              |   8 +-
 .../ConsumerAuthenticationFilterTest.java     | 112 +++++++++++++++++-
 .../profiles/h2-default/apolloportaldb.sql    |   1 +
 .../v230-v240/apolloportaldb-v230-v240.sql    |  43 +++++++
 .../apolloportaldb.sql                        |   1 +
 .../v230-v240/apolloportaldb-v230-v240.sql    |  39 ++++++
 .../profiles/mysql-default/apolloportaldb.sql |   1 +
 .../v230-v240/apolloportaldb-v230-v240.sql    |  41 +++++++
 scripts/sql/src/apolloportaldb.sql            |   1 +
 .../v230-v240/apolloportaldb-v230-v240.sql    |  25 ++++
 15 files changed, 347 insertions(+), 13 deletions(-)
 create mode 100644 scripts/sql/profiles/h2-default/delta/v230-v240/apolloportaldb-v230-v240.sql
 create mode 100644 scripts/sql/profiles/mysql-database-not-specified/delta/v230-v240/apolloportaldb-v230-v240.sql
 create mode 100644 scripts/sql/profiles/mysql-default/delta/v230-v240/apolloportaldb-v230-v240.sql
 create mode 100644 scripts/sql/src/delta/v230-v240/apolloportaldb-v230-v240.sql

diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/entity/ConsumerToken.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/entity/ConsumerToken.java
index baa1c0dc7c7..ce4a8fa2396 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/entity/ConsumerToken.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/entity/ConsumerToken.java
@@ -41,6 +41,9 @@ public class ConsumerToken extends BaseEntity {
   @Column(name = "`Token`", nullable = false)
   private String token;
 
+  @Column(name = "LimitCount")
+  private Integer limitCount;
+
   @Column(name = "`Expires`", nullable = false)
   private Date expires;
 
@@ -60,6 +63,14 @@ public void setToken(String token) {
     this.token = token;
   }
 
+  public Integer getLimitCount() {
+    return limitCount;
+  }
+
+  public void setLimitCount(Integer limitCount) {
+    this.limitCount = limitCount;
+  }
+
   public Date getExpires() {
     return expires;
   }
@@ -71,6 +82,7 @@ public void setExpires(Date expires) {
   @Override
   public String toString() {
     return toStringHelper().add("consumerId", consumerId).add("token", token)
+        .add("limitCount", limitCount)
         .add("expires", expires).toString();
   }
 }
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
index c08de6dd505..dff359e4a81 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
@@ -16,9 +16,13 @@
  */
 package com.ctrip.framework.apollo.openapi.filter;
 
+import com.ctrip.framework.apollo.openapi.entity.ConsumerToken;
 import com.ctrip.framework.apollo.openapi.util.ConsumerAuditUtil;
 import com.ctrip.framework.apollo.openapi.util.ConsumerAuthUtil;
-
+import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import com.google.common.util.concurrent.RateLimiter;
 import java.io.IOException;
 
 import javax.servlet.Filter;
@@ -29,18 +33,29 @@
 import javax.servlet.ServletResponse;
 import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 import org.springframework.http.HttpHeaders;
 
 /**
  * @author Jason Song(song_s@ctrip.com)
  */
 public class ConsumerAuthenticationFilter implements Filter {
+
+  private static final Logger logger = LoggerFactory.getLogger(ConsumerAuthenticationFilter.class);
+
   private final ConsumerAuthUtil consumerAuthUtil;
   private final ConsumerAuditUtil consumerAuditUtil;
+  private final PortalConfig portalConfig;
 
-  public ConsumerAuthenticationFilter(ConsumerAuthUtil consumerAuthUtil, ConsumerAuditUtil consumerAuditUtil) {
+  private static final Cache<String, ImmutablePair<Long, RateLimiter>> LIMITER = CacheBuilder.newBuilder().build();
+  private static final int WARMUP_MILLIS = 1000; // ms
+
+  public ConsumerAuthenticationFilter(ConsumerAuthUtil consumerAuthUtil, ConsumerAuditUtil consumerAuditUtil, PortalConfig portalConfig) {
     this.consumerAuthUtil = consumerAuthUtil;
     this.consumerAuditUtil = consumerAuditUtil;
+    this.portalConfig = portalConfig;
   }
 
   @Override
@@ -55,14 +70,28 @@ public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain
     HttpServletResponse response = (HttpServletResponse) resp;
 
     String token = request.getHeader(HttpHeaders.AUTHORIZATION);
+    ConsumerToken consumerToken = consumerAuthUtil.getConsumerToken(token);
 
-    Long consumerId = consumerAuthUtil.getConsumerId(token);
-
-    if (consumerId == null) {
+    if (null == consumerToken || consumerToken.getConsumerId() <= 0) {
       response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
       return;
     }
 
+    Integer limitCount = consumerToken.getLimitCount();
+    if (portalConfig.isOpenApiLimitEnabled() && limitCount > 0) {
+      try {
+        ImmutablePair<Long, RateLimiter> rateLimiterPair = getOrCreateRateLimiterPair(token, limitCount);
+        long warmupToMillis = rateLimiterPair.getLeft() + WARMUP_MILLIS;
+        if (System.currentTimeMillis() > warmupToMillis && !rateLimiterPair.getRight().tryAcquire()) {
+          response.sendError(HttpServletResponse.SC_FORBIDDEN, "Too many call requests, the flow is limited");
+          return;
+        }
+      } catch (Exception e) {
+        logger.error("ConsumerAuthenticationFilter ratelimit error", e);
+      }
+    }
+
+    long consumerId = consumerToken.getConsumerId();
     consumerAuthUtil.storeConsumerId(request, consumerId);
     consumerAuditUtil.audit(request, consumerId);
 
@@ -73,4 +102,14 @@ public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain
   public void destroy() {
     //nothing
   }
+
+  private ImmutablePair<Long, RateLimiter> getOrCreateRateLimiterPair(String key, Integer limitCount) {
+    ImmutablePair<Long, RateLimiter> rateLimiterPair = LIMITER.getIfPresent(key);
+    if (rateLimiterPair == null) {
+      rateLimiterPair = ImmutablePair.of(System.currentTimeMillis(), RateLimiter.create(limitCount));
+      LIMITER.put(key, rateLimiterPair);
+    }
+    return rateLimiterPair;
+  }
+
 }
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
index 90164feea7f..becaf48a7b2 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
@@ -138,12 +138,15 @@ public ConsumerToken getConsumerTokenByAppId(String appId) {
     return consumerTokenRepository.findByConsumerId(consumer.getId());
   }
 
-  public Long getConsumerIdByToken(String token) {
+  public ConsumerToken getConsumerTokenByToken(String token) {
     if (Strings.isNullOrEmpty(token)) {
       return null;
     }
-    ConsumerToken consumerToken = consumerTokenRepository.findTopByTokenAndExpiresAfter(token,
-                                                                                        new Date());
+    return consumerTokenRepository.findTopByTokenAndExpiresAfter(token, new Date());
+  }
+
+  public Long getConsumerIdByToken(String token) {
+    ConsumerToken consumerToken = getConsumerTokenByToken(token);
     return consumerToken == null ? null : consumerToken.getConsumerId();
   }
 
@@ -311,7 +314,9 @@ public void createConsumerAudits(Iterable<ConsumerAudit> consumerAudits) {
   @Transactional
   public ConsumerToken createConsumerToken(ConsumerToken entity) {
     entity.setId(0); //for protection
-
+    if (entity.getLimitCount() <= 0) {
+      entity.setLimitCount(portalConfig.openApiLimitCount());
+    }
     return consumerTokenRepository.save(entity);
   }
 
@@ -322,6 +327,7 @@ private ConsumerToken generateConsumerToken(Consumer consumer, Date expires) {
 
     ConsumerToken consumerToken = new ConsumerToken();
     consumerToken.setConsumerId(consumerId);
+    consumerToken.setLimitCount(portalConfig.openApiLimitCount());
     consumerToken.setExpires(expires);
     consumerToken.setDataChangeCreatedBy(createdBy);
     consumerToken.setDataChangeCreatedTime(createdTime);
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/util/ConsumerAuthUtil.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/util/ConsumerAuthUtil.java
index 83d5e02ab4e..1eff110210d 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/util/ConsumerAuthUtil.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/util/ConsumerAuthUtil.java
@@ -16,6 +16,7 @@
  */
 package com.ctrip.framework.apollo.openapi.util;
 
+import com.ctrip.framework.apollo.openapi.entity.ConsumerToken;
 import com.ctrip.framework.apollo.openapi.service.ConsumerService;
 import org.springframework.stereotype.Service;
 
@@ -37,6 +38,10 @@ public Long getConsumerId(String token) {
     return consumerService.getConsumerIdByToken(token);
   }
 
+  public ConsumerToken getConsumerToken(String token) {
+    return consumerService.getConsumerTokenByToken(token);
+  }
+
   public void storeConsumerId(HttpServletRequest request, Long consumerId) {
     request.setAttribute(CONSUMER_ID, consumerId);
   }
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java
index 982515be490..f95be758038 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java
@@ -242,6 +242,14 @@ public String consumerTokenSalt() {
     return getValue("consumer.token.salt", "apollo-portal");
   }
 
+  public int openApiLimitCount() {
+    return getIntProperty("open.api.limit.count", 20);
+  }
+
+  public boolean isOpenApiLimitEnabled() {
+    return getBooleanProperty("open.api.limit.enabled", false);
+  }
+
   public boolean isEmailEnabled() {
     return getBooleanProperty("email.enabled", false);
   }
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/configuration/AuthFilterConfiguration.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/configuration/AuthFilterConfiguration.java
index 66414b3eef2..40eb0309029 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/configuration/AuthFilterConfiguration.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/configuration/AuthFilterConfiguration.java
@@ -19,6 +19,7 @@
 import com.ctrip.framework.apollo.openapi.filter.ConsumerAuthenticationFilter;
 import com.ctrip.framework.apollo.openapi.util.ConsumerAuditUtil;
 import com.ctrip.framework.apollo.openapi.util.ConsumerAuthUtil;
+import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -28,12 +29,13 @@ public class AuthFilterConfiguration {
 
   @Bean
   public FilterRegistrationBean<ConsumerAuthenticationFilter> openApiAuthenticationFilter(
-          ConsumerAuthUtil consumerAuthUtil,
-          ConsumerAuditUtil consumerAuditUtil) {
+      ConsumerAuthUtil consumerAuthUtil,
+      ConsumerAuditUtil consumerAuditUtil,
+      PortalConfig portalConfig) {
 
     FilterRegistrationBean<ConsumerAuthenticationFilter> openApiFilter = new FilterRegistrationBean<>();
 
-    openApiFilter.setFilter(new ConsumerAuthenticationFilter(consumerAuthUtil, consumerAuditUtil));
+    openApiFilter.setFilter(new ConsumerAuthenticationFilter(consumerAuthUtil, consumerAuditUtil, portalConfig));
     openApiFilter.addUrlPatterns("/openapi/*");
 
     return openApiFilter;
diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
index 876562849bb..dcdaaec2f86 100644
--- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
+++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
@@ -16,9 +16,16 @@
  */
 package com.ctrip.framework.apollo.openapi.filter;
 
+import com.ctrip.framework.apollo.openapi.entity.ConsumerToken;
 import com.ctrip.framework.apollo.openapi.util.ConsumerAuditUtil;
 import com.ctrip.framework.apollo.openapi.util.ConsumerAuthUtil;
 
+import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
+import java.io.IOException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import javax.servlet.ServletException;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -33,6 +40,9 @@
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.atLeast;
+import static org.mockito.Mockito.atLeastOnce;
+import static org.mockito.Mockito.atMost;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
@@ -48,6 +58,9 @@ public class ConsumerAuthenticationFilterTest {
   private ConsumerAuthUtil consumerAuthUtil;
   @Mock
   private ConsumerAuditUtil consumerAuditUtil;
+  @Mock
+  private PortalConfig portalConfig;
+
   @Mock
   private HttpServletRequest request;
   @Mock
@@ -57,7 +70,7 @@ public class ConsumerAuthenticationFilterTest {
 
   @Before
   public void setUp() throws Exception {
-    authenticationFilter = new ConsumerAuthenticationFilter(consumerAuthUtil, consumerAuditUtil);
+    authenticationFilter = new ConsumerAuthenticationFilter(consumerAuthUtil, consumerAuditUtil, portalConfig);
   }
 
   @Test
@@ -89,4 +102,101 @@ public void testAuthFailed() throws Exception {
     verify(consumerAuditUtil, never()).audit(eq(request), anyLong());
     verify(filterChain, never()).doFilter(request, response);
   }
+
+
+  @Test
+  public void testRateLimitSuccessfully() throws Exception {
+    String someToken = "someToken";
+    Long someConsumerId = 1L;
+    int qps = 5;
+    int durationInSeconds = 10;
+
+    setupRateLimitMocks(someToken, someConsumerId, qps);
+
+    Runnable task = () -> {
+      try {
+        authenticationFilter.doFilter(request, response, filterChain);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      } catch (ServletException e) {
+        throw new RuntimeException(e);
+      }
+    };
+
+    executeWithQps(qps, task, durationInSeconds);
+
+    int total = qps * durationInSeconds;
+
+    verify(consumerAuthUtil, times(total)).storeConsumerId(request, someConsumerId);
+    verify(consumerAuditUtil, times(total)).audit(request, someConsumerId);
+    verify(filterChain, times(total)).doFilter(request, response);
+
+  }
+
+
+  @Test
+  public void testRateLimitPartFailure() throws Exception {
+    String someToken = "someToken";
+    Long someConsumerId = 1L;
+    int qps = 5;
+    int durationInSeconds = 10;
+
+    setupRateLimitMocks(someToken, someConsumerId, qps);
+
+    Runnable task = () -> {
+      try {
+        authenticationFilter.doFilter(request, response, filterChain);
+      } catch (IOException e) {
+        throw new RuntimeException(e);
+      } catch (ServletException e) {
+        throw new RuntimeException(e);
+      }
+    };
+
+    executeWithQps(qps + 1, task, durationInSeconds);
+
+    int leastTimes = qps * durationInSeconds;
+    int mostTimes = (qps + 1) * durationInSeconds;
+
+    verify(response, atLeastOnce()).sendError(eq(HttpServletResponse.SC_FORBIDDEN), anyString());
+
+    verify(consumerAuthUtil, atLeast(leastTimes)).storeConsumerId(request, someConsumerId);
+    verify(consumerAuthUtil, atMost(mostTimes)).storeConsumerId(request, someConsumerId);
+    verify(consumerAuditUtil, atLeast(leastTimes)).audit(request, someConsumerId);
+    verify(consumerAuditUtil, atMost(mostTimes)).audit(request, someConsumerId);
+    verify(filterChain, atLeast(leastTimes)).doFilter(request, response);
+    verify(filterChain, atMost(mostTimes)).doFilter(request, response);
+
+  }
+
+
+  private void setupRateLimitMocks(String someToken, Long someConsumerId, int qps) {
+    ConsumerToken someConsumerToken = new ConsumerToken();
+    someConsumerToken.setConsumerId(someConsumerId);
+    someConsumerToken.setLimitCount(qps);
+
+    when(request.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(someToken);
+    when(consumerAuthUtil.getConsumerId(someToken)).thenReturn(someConsumerId);
+    when(consumerAuthUtil.getConsumerToken(someToken)).thenReturn(someConsumerToken);
+    when(portalConfig.isOpenApiLimitEnabled()).thenReturn(true);
+  }
+
+
+  public static void executeWithQps(int qps, Runnable task, int durationInSeconds) {
+    ExecutorService executor = Executors.newFixedThreadPool(qps);
+    long totalTasks = qps * durationInSeconds;
+
+    for (int i = 0; i < totalTasks; i++) {
+      executor.submit(task);
+      try {
+        TimeUnit.MILLISECONDS.sleep(1000 / qps); // Control QPS
+      } catch (InterruptedException e) {
+        Thread.currentThread().interrupt();
+        break;
+      }
+    }
+
+    executor.shutdown();
+  }
+
 }
diff --git a/scripts/sql/profiles/h2-default/apolloportaldb.sql b/scripts/sql/profiles/h2-default/apolloportaldb.sql
index e1be67d3d6e..4ebf711c343 100644
--- a/scripts/sql/profiles/h2-default/apolloportaldb.sql
+++ b/scripts/sql/profiles/h2-default/apolloportaldb.sql
@@ -161,6 +161,7 @@ CREATE TABLE `ConsumerToken` (
   `Id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增Id',
   `ConsumerId` int(11) unsigned DEFAULT NULL COMMENT 'ConsumerId',
   `Token` varchar(128) NOT NULL DEFAULT '' COMMENT 'token',
+  `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值',
   `Expires` datetime NOT NULL DEFAULT '2099-01-01 00:00:00' COMMENT 'token失效时间',
   `IsDeleted` boolean NOT NULL DEFAULT FALSE COMMENT '1: deleted, 0: normal',
   `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds',
diff --git a/scripts/sql/profiles/h2-default/delta/v230-v240/apolloportaldb-v230-v240.sql b/scripts/sql/profiles/h2-default/delta/v230-v240/apolloportaldb-v230-v240.sql
new file mode 100644
index 00000000000..7bb1675a1f5
--- /dev/null
+++ b/scripts/sql/profiles/h2-default/delta/v230-v240/apolloportaldb-v230-v240.sql
@@ -0,0 +1,43 @@
+--
+-- Copyright 2024 Apollo Authors
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- delta schema to upgrade apollo portal db from v2.3.0 to v2.4.0
+
+-- 
+-- ===============================================================================
+-- ==                                                                           ==
+-- ==                     Generated from 'scripts/sql/src/'                     ==
+-- == by running 'mvn compile -pl apollo-build-sql-converter -Psql-converter'.  ==
+-- ==                              DO NOT EDIT !!!                              ==
+-- ==                                                                           ==
+-- ===============================================================================
+-- 
+
+-- H2 Function
+-- ------------------------------------------------------------
+CREATE ALIAS IF NOT EXISTS UNIX_TIMESTAMP FOR "com.ctrip.framework.apollo.common.jpa.H2Function.unixTimestamp";
+
+-- 
+
+ALTER TABLE `ConsumerToken` ADD COLUMN `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值' AFTER `Token`;
+
+-- 
+-- ===============================================================================
+-- ==                                                                           ==
+-- ==                     Generated from 'scripts/sql/src/'                     ==
+-- == by running 'mvn compile -pl apollo-build-sql-converter -Psql-converter'.  ==
+-- ==                              DO NOT EDIT !!!                              ==
+-- ==                                                                           ==
+-- ===============================================================================
diff --git a/scripts/sql/profiles/mysql-database-not-specified/apolloportaldb.sql b/scripts/sql/profiles/mysql-database-not-specified/apolloportaldb.sql
index e6de5a1a4a9..2649d658a75 100644
--- a/scripts/sql/profiles/mysql-database-not-specified/apolloportaldb.sql
+++ b/scripts/sql/profiles/mysql-database-not-specified/apolloportaldb.sql
@@ -162,6 +162,7 @@ CREATE TABLE `ConsumerToken` (
   `Id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增Id',
   `ConsumerId` int(11) unsigned DEFAULT NULL COMMENT 'ConsumerId',
   `Token` varchar(128) NOT NULL DEFAULT '' COMMENT 'token',
+  `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值',
   `Expires` datetime NOT NULL DEFAULT '2099-01-01 00:00:00' COMMENT 'token失效时间',
   `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal',
   `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds',
diff --git a/scripts/sql/profiles/mysql-database-not-specified/delta/v230-v240/apolloportaldb-v230-v240.sql b/scripts/sql/profiles/mysql-database-not-specified/delta/v230-v240/apolloportaldb-v230-v240.sql
new file mode 100644
index 00000000000..e3dbbbea4e3
--- /dev/null
+++ b/scripts/sql/profiles/mysql-database-not-specified/delta/v230-v240/apolloportaldb-v230-v240.sql
@@ -0,0 +1,39 @@
+--
+-- Copyright 2024 Apollo Authors
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- delta schema to upgrade apollo portal db from v2.3.0 to v2.4.0
+
+-- 
+-- ===============================================================================
+-- ==                                                                           ==
+-- ==                     Generated from 'scripts/sql/src/'                     ==
+-- == by running 'mvn compile -pl apollo-build-sql-converter -Psql-converter'.  ==
+-- ==                              DO NOT EDIT !!!                              ==
+-- ==                                                                           ==
+-- ===============================================================================
+-- 
+-- 
+
+ALTER TABLE `ConsumerToken`
+    ADD COLUMN `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值' AFTER `Token`;
+
+-- 
+-- ===============================================================================
+-- ==                                                                           ==
+-- ==                     Generated from 'scripts/sql/src/'                     ==
+-- == by running 'mvn compile -pl apollo-build-sql-converter -Psql-converter'.  ==
+-- ==                              DO NOT EDIT !!!                              ==
+-- ==                                                                           ==
+-- ===============================================================================
diff --git a/scripts/sql/profiles/mysql-default/apolloportaldb.sql b/scripts/sql/profiles/mysql-default/apolloportaldb.sql
index 264f948a53b..a0b01c9c413 100644
--- a/scripts/sql/profiles/mysql-default/apolloportaldb.sql
+++ b/scripts/sql/profiles/mysql-default/apolloportaldb.sql
@@ -167,6 +167,7 @@ CREATE TABLE `ConsumerToken` (
   `Id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增Id',
   `ConsumerId` int(11) unsigned DEFAULT NULL COMMENT 'ConsumerId',
   `Token` varchar(128) NOT NULL DEFAULT '' COMMENT 'token',
+  `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值',
   `Expires` datetime NOT NULL DEFAULT '2099-01-01 00:00:00' COMMENT 'token失效时间',
   `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal',
   `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds',
diff --git a/scripts/sql/profiles/mysql-default/delta/v230-v240/apolloportaldb-v230-v240.sql b/scripts/sql/profiles/mysql-default/delta/v230-v240/apolloportaldb-v230-v240.sql
new file mode 100644
index 00000000000..51fcbd5f7ed
--- /dev/null
+++ b/scripts/sql/profiles/mysql-default/delta/v230-v240/apolloportaldb-v230-v240.sql
@@ -0,0 +1,41 @@
+--
+-- Copyright 2024 Apollo Authors
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- delta schema to upgrade apollo portal db from v2.3.0 to v2.4.0
+
+-- 
+-- ===============================================================================
+-- ==                                                                           ==
+-- ==                     Generated from 'scripts/sql/src/'                     ==
+-- == by running 'mvn compile -pl apollo-build-sql-converter -Psql-converter'.  ==
+-- ==                              DO NOT EDIT !!!                              ==
+-- ==                                                                           ==
+-- ===============================================================================
+-- 
+-- 
+-- Use Database
+Use ApolloPortalDB;
+
+ALTER TABLE `ConsumerToken`
+    ADD COLUMN `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值' AFTER `Token`;
+
+-- 
+-- ===============================================================================
+-- ==                                                                           ==
+-- ==                     Generated from 'scripts/sql/src/'                     ==
+-- == by running 'mvn compile -pl apollo-build-sql-converter -Psql-converter'.  ==
+-- ==                              DO NOT EDIT !!!                              ==
+-- ==                                                                           ==
+-- ===============================================================================
diff --git a/scripts/sql/src/apolloportaldb.sql b/scripts/sql/src/apolloportaldb.sql
index 323a8191149..3398e5db88e 100644
--- a/scripts/sql/src/apolloportaldb.sql
+++ b/scripts/sql/src/apolloportaldb.sql
@@ -155,6 +155,7 @@ CREATE TABLE `ConsumerToken` (
   `Id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增Id',
   `ConsumerId` int(11) unsigned DEFAULT NULL COMMENT 'ConsumerId',
   `Token` varchar(128) NOT NULL DEFAULT '' COMMENT 'token',
+  `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值',
   `Expires` datetime NOT NULL DEFAULT '2099-01-01 00:00:00' COMMENT 'token失效时间',
   `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal',
   `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds',
diff --git a/scripts/sql/src/delta/v230-v240/apolloportaldb-v230-v240.sql b/scripts/sql/src/delta/v230-v240/apolloportaldb-v230-v240.sql
new file mode 100644
index 00000000000..b7fbb33ba0d
--- /dev/null
+++ b/scripts/sql/src/delta/v230-v240/apolloportaldb-v230-v240.sql
@@ -0,0 +1,25 @@
+--
+-- Copyright 2024 Apollo Authors
+--
+-- Licensed under the Apache License, Version 2.0 (the "License");
+-- you may not use this file except in compliance with the License.
+-- You may obtain a copy of the License at
+--
+-- http://www.apache.org/licenses/LICENSE-2.0
+--
+-- Unless required by applicable law or agreed to in writing, software
+-- distributed under the License is distributed on an "AS IS" BASIS,
+-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+-- See the License for the specific language governing permissions and
+-- limitations under the License.
+--
+-- delta schema to upgrade apollo portal db from v2.3.0 to v2.4.0
+
+-- ${gists.autoGeneratedDeclaration}
+-- ${gists.h2Function}
+-- ${gists.useDatabase}
+
+ALTER TABLE `ConsumerToken`
+    ADD COLUMN `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值' AFTER `Token`;
+
+-- ${gists.autoGeneratedDeclaration}

From 1bae71f865a1b845675ac967bdd71fcaf1e4a535 Mon Sep 17 00:00:00 2001
From: yangzl <youngzil@163.com>
Date: Sun, 3 Nov 2024 22:59:15 +0800
Subject: [PATCH 02/15] =?UTF-8?q?fix=EF=BC=9Aadd=20CHANGES.md=20and=20opti?=
 =?UTF-8?q?mize=20some=20codes?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 CHANGES.md                                          |  1 +
 .../filter/ConsumerAuthenticationFilter.java        | 13 +++++++++++--
 .../apollo/SkipAuthorizationConfiguration.java      |  6 ++++++
 .../filter/ConsumerAuthenticationFilterTest.java    |  8 +++++---
 docs/en/deployment/distributed-deployment-guide.md  | 12 ++++++++++++
 docs/zh/deployment/distributed-deployment-guide.md  | 11 +++++++++++
 6 files changed, 46 insertions(+), 5 deletions(-)

diff --git a/CHANGES.md b/CHANGES.md
index 48d7b9967b0..88a2833ca9d 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -19,6 +19,7 @@ Apollo 2.4.0
 * [Feature: openapi query namespace support not fill item](https://github.com/apolloconfig/apollo/pull/5249)
 * [Refactor: align database ClusterName and NamespaceName fields lengths](https://github.com/apolloconfig/apollo/pull/5263)
 * [Feature: Added the value length limit function for AppId-level configuration items](https://github.com/apolloconfig/apollo/pull/5264)
+* [Feature: Added current limiting function to ConsumerToken](https://github.com/apolloconfig/apollo/pull/5267)
 
 ------------------
 All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1)
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
index dff359e4a81..2db7d11afbd 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
@@ -24,7 +24,7 @@
 import com.google.common.cache.CacheBuilder;
 import com.google.common.util.concurrent.RateLimiter;
 import java.io.IOException;
-
+import java.util.concurrent.TimeUnit;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
@@ -49,8 +49,12 @@ public class ConsumerAuthenticationFilter implements Filter {
   private final ConsumerAuditUtil consumerAuditUtil;
   private final PortalConfig portalConfig;
 
-  private static final Cache<String, ImmutablePair<Long, RateLimiter>> LIMITER = CacheBuilder.newBuilder().build();
   private static final int WARMUP_MILLIS = 1000; // ms
+  private static final int RATE_LIMITER_CACHE_MAX_SIZE = 50000;
+
+  private static final Cache<String, ImmutablePair<Long, RateLimiter>> LIMITER = CacheBuilder.newBuilder()
+      .expireAfterWrite(1, TimeUnit.DAYS)
+      .maximumSize(RATE_LIMITER_CACHE_MAX_SIZE).build();
 
   public ConsumerAuthenticationFilter(ConsumerAuthUtil consumerAuthUtil, ConsumerAuditUtil consumerAuditUtil, PortalConfig portalConfig) {
     this.consumerAuthUtil = consumerAuthUtil;
@@ -78,6 +82,9 @@ public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain
     }
 
     Integer limitCount = consumerToken.getLimitCount();
+    if (limitCount == null) {
+      limitCount = 0;
+    }
     if (portalConfig.isOpenApiLimitEnabled() && limitCount > 0) {
       try {
         ImmutablePair<Long, RateLimiter> rateLimiterPair = getOrCreateRateLimiterPair(token, limitCount);
@@ -88,6 +95,8 @@ public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain
         }
       } catch (Exception e) {
         logger.error("ConsumerAuthenticationFilter ratelimit error", e);
+        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Rate limiting failed");
+        return;
       }
     }
 
diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/SkipAuthorizationConfiguration.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/SkipAuthorizationConfiguration.java
index fa4e166b7a1..e767ea5d547 100644
--- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/SkipAuthorizationConfiguration.java
+++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/SkipAuthorizationConfiguration.java
@@ -17,6 +17,7 @@
 package com.ctrip.framework.apollo;
 
 import com.ctrip.framework.apollo.openapi.auth.ConsumerPermissionValidator;
+import com.ctrip.framework.apollo.openapi.entity.ConsumerToken;
 import com.ctrip.framework.apollo.openapi.util.ConsumerAuthUtil;
 import com.ctrip.framework.apollo.portal.component.PermissionValidator;
 import org.springframework.context.annotation.Bean;
@@ -50,6 +51,11 @@ public ConsumerPermissionValidator consumerPermissionValidator() {
   public ConsumerAuthUtil consumerAuthUtil() {
     final ConsumerAuthUtil mock = mock(ConsumerAuthUtil.class);
     when(mock.getConsumerId(any())).thenReturn(1L);
+
+    ConsumerToken someConsumerToken = new ConsumerToken();
+    someConsumerToken.setConsumerId(1L);
+    someConsumerToken.setLimitCount(20);
+    when(mock.getConsumerToken(any())).thenReturn(someConsumerToken);
     return mock;
   }
 
diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
index dcdaaec2f86..e9d8e16071c 100644
--- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
+++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
@@ -78,8 +78,11 @@ public void testAuthSuccessfully() throws Exception {
     String someToken = "someToken";
     Long someConsumerId = 1L;
 
+    ConsumerToken someConsumerToken = new ConsumerToken();
+    someConsumerToken.setConsumerId(someConsumerId);
+
     when(request.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(someToken);
-    when(consumerAuthUtil.getConsumerId(someToken)).thenReturn(someConsumerId);
+    when(consumerAuthUtil.getConsumerToken(someToken)).thenReturn(someConsumerToken);
 
     authenticationFilter.doFilter(request, response, filterChain);
 
@@ -93,7 +96,7 @@ public void testAuthFailed() throws Exception {
     String someInvalidToken = "someInvalidToken";
 
     when(request.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(someInvalidToken);
-    when(consumerAuthUtil.getConsumerId(someInvalidToken)).thenReturn(null);
+    when(consumerAuthUtil.getConsumerToken(someInvalidToken)).thenReturn(null);
 
     authenticationFilter.doFilter(request, response, filterChain);
 
@@ -176,7 +179,6 @@ private void setupRateLimitMocks(String someToken, Long someConsumerId, int qps)
     someConsumerToken.setLimitCount(qps);
 
     when(request.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(someToken);
-    when(consumerAuthUtil.getConsumerId(someToken)).thenReturn(someConsumerId);
     when(consumerAuthUtil.getConsumerToken(someToken)).thenReturn(someConsumerToken);
     when(portalConfig.isOpenApiLimitEnabled()).thenReturn(true);
   }
diff --git a/docs/en/deployment/distributed-deployment-guide.md b/docs/en/deployment/distributed-deployment-guide.md
index 91afbe7a4fb..9028ec2fd22 100644
--- a/docs/en/deployment/distributed-deployment-guide.md
+++ b/docs/en/deployment/distributed-deployment-guide.md
@@ -1455,6 +1455,18 @@ Default is 200, which means that each environment will return up to 200 results
 
 Modifying this parameter may affect the performance of the search function, so before modifying it, you should conduct sufficient testing and adjust the value of `apollo.portal.search.perEnvMaxResults` appropriately according to the actual business requirements and system resources to balance the performance and the number of search results.
 
+### 3.1.15 open.api.limit.count - Default value of ConsumerToken current limit value
+
+> For versions 2.4.0 and above
+
+The default value is 20. When creating a third-party application in the Open Platform Authorization Management, the default current limit value of the created ConsumerToken is this value.
+
+### 3.1.16 open.api.limit.enabled - Whether to enable the ConsumerToken current limiting function
+
+> For versions 2.4.0 and above
+
+The default value is `false`. When set to `true`, it means that each ConsumerToken request to the Apollo OpenAPI interface will be limited to a specific QPS, and the current limit value is the value of the `limitCount` field in the `ConsumerToken` table.
+
 ## 3.2 Adjusting ApolloConfigDB configuration
 
 Configuration items are uniformly stored in the ApolloConfigDB.ServerConfig table. It should be noted that each environment's ApolloConfigDB.ServerConfig needs to be configured separately, and the modification takes effect in real time for one minute afterwards.
diff --git a/docs/zh/deployment/distributed-deployment-guide.md b/docs/zh/deployment/distributed-deployment-guide.md
index 89473a8a0c2..e948ee88527 100644
--- a/docs/zh/deployment/distributed-deployment-guide.md
+++ b/docs/zh/deployment/distributed-deployment-guide.md
@@ -1400,6 +1400,17 @@ portal上“帮助”链接的地址,默认是Apollo github的wiki首页,可
 
 修改该参数可能会影响搜索功能的性能,因此在修改之前应该进行充分的测试,根据实际业务需求和系统资源情况,适当调整`apollo.portal.search.perEnvMaxResults`的值,以平衡性能和搜索结果的数量
 
+### 3.1.15 open.api.limit.count - ConsumerToken 限流值的默认值
+
+> 适用于2.4.0及以上版本
+
+默认为20,当在 `开放平台授权管理-创建第三方应用` 时,创建的 ConsumerToken 默认的限流值即为此值
+
+### 3.1.16 open.api.limit.enabled - 是否开启 ConsumerToken 限流功能
+
+> 适用于2.4.0及以上版本
+
+默认为`false`,当设置为`true`,意味着每个 ConsumerToken 请求Apollo OpenAPI 接口的时候,都会被限制到特定的QPS,限流值为`ConsumerToken`表中的`limitCount`字段的值
 
 ## 3.2 调整ApolloConfigDB配置
 配置项统一存储在ApolloConfigDB.ServerConfig表中,需要注意每个环境的ApolloConfigDB.ServerConfig都需要单独配置,修改完一分钟实时生效。

From 826ea74b766ac13346a5b27bc598b620d711330c Mon Sep 17 00:00:00 2001
From: yangzl <youngzil@163.com>
Date: Sun, 17 Nov 2024 23:25:25 +0800
Subject: [PATCH 03/15] =?UTF-8?q?feat(openapi):=20=E9=87=8D=E6=9E=84=20Con?=
 =?UTF-8?q?sumerToken=20=E9=99=90=E6=B5=81=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 .../common/exception/BadRequestException.java |  4 +++
 .../apollo/openapi/entity/ConsumerToken.java  | 16 +++++-----
 .../filter/ConsumerAuthenticationFilter.java  | 26 ++++++++---------
 .../openapi/service/ConsumerService.java      | 11 +++----
 .../portal/component/config/PortalConfig.java |  8 -----
 .../portal/controller/ConsumerController.java | 14 +++++++--
 .../vo/consumer/ConsumerCreateRequestVO.java  | 18 ++++++++++++
 .../src/main/resources/static/i18n/en.json    |  5 ++++
 .../src/main/resources/static/i18n/zh-CN.json |  5 ++++
 .../resources/static/open/add-consumer.html   | 29 ++++++++++++++++++-
 .../controller/open/OpenManageController.js   | 16 ++++++++++
 .../SkipAuthorizationConfiguration.java       |  2 +-
 .../ConsumerAuthenticationFilterTest.java     |  8 +++--
 .../controller/NamespaceControllerTest.java   | 13 +++++++--
 .../controller/ConsumerControllerTest.java    |  8 ++---
 ...sumerServiceIntegrationTest.commonData.sql |  4 +--
 ...t.testFindAppIdsAuthorizedByConsumerId.sql |  4 +--
 ...eControllerTest.testCreateAppNamespace.sql |  4 +--
 .../distributed-deployment-guide.md           | 12 --------
 .../distributed-deployment-guide.md           | 12 --------
 .../profiles/h2-default/apolloportaldb.sql    |  2 +-
 .../v230-v240/apolloportaldb-v230-v240.sql    |  2 +-
 .../apolloportaldb.sql                        |  2 +-
 .../v230-v240/apolloportaldb-v230-v240.sql    |  2 +-
 .../profiles/mysql-default/apolloportaldb.sql |  2 +-
 .../v230-v240/apolloportaldb-v230-v240.sql    |  2 +-
 scripts/sql/src/apolloportaldb.sql            |  2 +-
 .../v230-v240/apolloportaldb-v230-v240.sql    |  2 +-
 28 files changed, 148 insertions(+), 87 deletions(-)

diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/exception/BadRequestException.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/exception/BadRequestException.java
index 567fd6b375c..2aeb4c3e0f3 100644
--- a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/exception/BadRequestException.java
+++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/exception/BadRequestException.java
@@ -37,6 +37,10 @@ public static BadRequestException orgIdIsBlank() {
     return new BadRequestException("orgId can not be blank");
   }
 
+  public static BadRequestException rateLimitIsInvalid() {
+    return new BadRequestException("Ratelimit must be greater than 1");
+  }
+
   public static BadRequestException itemAlreadyExists(String itemKey) {
     return new BadRequestException("item already exists for itemKey:%s", itemKey);
   }
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/entity/ConsumerToken.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/entity/ConsumerToken.java
index ce4a8fa2396..75893cff567 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/entity/ConsumerToken.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/entity/ConsumerToken.java
@@ -18,6 +18,7 @@
 
 import com.ctrip.framework.apollo.common.entity.BaseEntity;
 
+import javax.validation.constraints.PositiveOrZero;
 import org.hibernate.annotations.SQLDelete;
 import org.hibernate.annotations.Where;
 
@@ -41,8 +42,9 @@ public class ConsumerToken extends BaseEntity {
   @Column(name = "`Token`", nullable = false)
   private String token;
 
-  @Column(name = "LimitCount")
-  private Integer limitCount;
+  @PositiveOrZero
+  @Column(name = "`RateLimit`", nullable = false)
+  private Integer rateLimit;
 
   @Column(name = "`Expires`", nullable = false)
   private Date expires;
@@ -63,12 +65,12 @@ public void setToken(String token) {
     this.token = token;
   }
 
-  public Integer getLimitCount() {
-    return limitCount;
+  public Integer getRateLimit() {
+    return rateLimit;
   }
 
-  public void setLimitCount(Integer limitCount) {
-    this.limitCount = limitCount;
+  public void setRateLimit(Integer rateLimit) {
+    this.rateLimit = rateLimit;
   }
 
   public Date getExpires() {
@@ -82,7 +84,7 @@ public void setExpires(Date expires) {
   @Override
   public String toString() {
     return toStringHelper().add("consumerId", consumerId).add("token", token)
-        .add("limitCount", limitCount)
+        .add("rateLimit", rateLimit)
         .add("expires", expires).toString();
   }
 }
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
index 2db7d11afbd..3e1ae49ae59 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
@@ -24,6 +24,7 @@
 import com.google.common.cache.CacheBuilder;
 import com.google.common.util.concurrent.RateLimiter;
 import java.io.IOException;
+import java.util.concurrent.ExecutionException;
 import java.util.concurrent.TimeUnit;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
@@ -52,6 +53,8 @@ public class ConsumerAuthenticationFilter implements Filter {
   private static final int WARMUP_MILLIS = 1000; // ms
   private static final int RATE_LIMITER_CACHE_MAX_SIZE = 50000;
 
+  private static final int TOO_MANY_REQUESTS = 429;
+
   private static final Cache<String, ImmutablePair<Long, RateLimiter>> LIMITER = CacheBuilder.newBuilder()
       .expireAfterWrite(1, TimeUnit.DAYS)
       .maximumSize(RATE_LIMITER_CACHE_MAX_SIZE).build();
@@ -76,21 +79,18 @@ public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain
     String token = request.getHeader(HttpHeaders.AUTHORIZATION);
     ConsumerToken consumerToken = consumerAuthUtil.getConsumerToken(token);
 
-    if (null == consumerToken || consumerToken.getConsumerId() <= 0) {
+    if (null == consumerToken) {
       response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
       return;
     }
 
-    Integer limitCount = consumerToken.getLimitCount();
-    if (limitCount == null) {
-      limitCount = 0;
-    }
-    if (portalConfig.isOpenApiLimitEnabled() && limitCount > 0) {
+    Integer rateLimit = consumerToken.getRateLimit();
+    if (null != rateLimit && rateLimit > 0) {
       try {
-        ImmutablePair<Long, RateLimiter> rateLimiterPair = getOrCreateRateLimiterPair(token, limitCount);
+        ImmutablePair<Long, RateLimiter> rateLimiterPair = getOrCreateRateLimiterPair(token, rateLimit);
         long warmupToMillis = rateLimiterPair.getLeft() + WARMUP_MILLIS;
         if (System.currentTimeMillis() > warmupToMillis && !rateLimiterPair.getRight().tryAcquire()) {
-          response.sendError(HttpServletResponse.SC_FORBIDDEN, "Too many call requests, the flow is limited");
+          response.sendError(TOO_MANY_REQUESTS, "Too Many Requests, the flow is limited");
           return;
         }
       } catch (Exception e) {
@@ -113,12 +113,12 @@ public void destroy() {
   }
 
   private ImmutablePair<Long, RateLimiter> getOrCreateRateLimiterPair(String key, Integer limitCount) {
-    ImmutablePair<Long, RateLimiter> rateLimiterPair = LIMITER.getIfPresent(key);
-    if (rateLimiterPair == null) {
-      rateLimiterPair = ImmutablePair.of(System.currentTimeMillis(), RateLimiter.create(limitCount));
-      LIMITER.put(key, rateLimiterPair);
+    try{
+      return LIMITER.get(key, () ->
+          ImmutablePair.of(System.currentTimeMillis(), RateLimiter.create(limitCount)));
+    } catch (ExecutionException e) {
+      throw new RuntimeException("Failed to create rate limiter", e);
     }
-    return rateLimiterPair;
   }
 
 }
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
index becaf48a7b2..ab5d25e65ae 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
@@ -120,10 +120,10 @@ public Consumer createConsumer(Consumer consumer) {
     return consumerRepository.save(consumer);
   }
 
-  public ConsumerToken generateAndSaveConsumerToken(Consumer consumer, Date expires) {
+  public ConsumerToken generateAndSaveConsumerToken(Consumer consumer, Integer rateLimit, Date expires) {
     Preconditions.checkArgument(consumer != null, "Consumer can not be null");
 
-    ConsumerToken consumerToken = generateConsumerToken(consumer, expires);
+    ConsumerToken consumerToken = generateConsumerToken(consumer, rateLimit, expires);
     consumerToken.setId(0);
 
     return consumerTokenRepository.save(consumerToken);
@@ -314,20 +314,17 @@ public void createConsumerAudits(Iterable<ConsumerAudit> consumerAudits) {
   @Transactional
   public ConsumerToken createConsumerToken(ConsumerToken entity) {
     entity.setId(0); //for protection
-    if (entity.getLimitCount() <= 0) {
-      entity.setLimitCount(portalConfig.openApiLimitCount());
-    }
     return consumerTokenRepository.save(entity);
   }
 
-  private ConsumerToken generateConsumerToken(Consumer consumer, Date expires) {
+  private ConsumerToken generateConsumerToken(Consumer consumer, Integer rateLimit, Date expires) {
     long consumerId = consumer.getId();
     String createdBy = userInfoHolder.getUser().getUserId();
     Date createdTime = new Date();
 
     ConsumerToken consumerToken = new ConsumerToken();
     consumerToken.setConsumerId(consumerId);
-    consumerToken.setLimitCount(portalConfig.openApiLimitCount());
+    consumerToken.setRateLimit(rateLimit);
     consumerToken.setExpires(expires);
     consumerToken.setDataChangeCreatedBy(createdBy);
     consumerToken.setDataChangeCreatedTime(createdTime);
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java
index f95be758038..982515be490 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java
@@ -242,14 +242,6 @@ public String consumerTokenSalt() {
     return getValue("consumer.token.salt", "apollo-portal");
   }
 
-  public int openApiLimitCount() {
-    return getIntProperty("open.api.limit.count", 20);
-  }
-
-  public boolean isOpenApiLimitEnabled() {
-    return getBooleanProperty("open.api.limit.enabled", false);
-  }
-
   public boolean isEmailEnabled() {
     return getBooleanProperty("email.enabled", false);
   }
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java
index 0c7a5141731..1e695b3159e 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java
@@ -81,13 +81,21 @@ public ConsumerInfo create(
       throw BadRequestException.orgIdIsBlank();
     }
 
+    if (requestVO.isRateLimitEenabled()) {
+      if (requestVO.getRateLimit() <= 0) {
+        throw BadRequestException.rateLimitIsInvalid();
+      }
+    } else {
+      requestVO.setRateLimit(0);
+    }
+
     Consumer createdConsumer = consumerService.createConsumer(convertToConsumer(requestVO));
 
     if (Objects.isNull(expires)) {
       expires = DEFAULT_EXPIRES;
     }
 
-    ConsumerToken consumerToken = consumerService.generateAndSaveConsumerToken(createdConsumer, expires);
+    ConsumerToken consumerToken = consumerService.generateAndSaveConsumerToken(createdConsumer, requestVO.getRateLimit(), expires);
     if (requestVO.isAllowCreateApplication()) {
       consumerService.assignCreateApplicationRoleToConsumer(consumerToken.getToken());
     }
@@ -127,7 +135,7 @@ public List<ConsumerRole> assignNamespaceRoleToConsumer(
     if (StringUtils.isEmpty(namespaceName)) {
       throw new BadRequestException("Params(NamespaceName) can not be empty.");
     }
-    if (null != envs){
+    if (null != envs) {
       String[] envArray = envs.split(",");
       List<String> envList = Lists.newArrayList();
       // validate env parameter
@@ -156,7 +164,7 @@ public List<ConsumerRole> assignNamespaceRoleToConsumer(
 
   @GetMapping("/consumers")
   @PreAuthorize(value = "@permissionValidator.isSuperAdmin()")
-  public List<ConsumerInfo> getConsumerList(Pageable page){
+  public List<ConsumerInfo> getConsumerList(Pageable page) {
     return consumerService.findConsumerInfoList(page);
   }
 
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerCreateRequestVO.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerCreateRequestVO.java
index 62bd2406fd2..248f8312516 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerCreateRequestVO.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerCreateRequestVO.java
@@ -26,6 +26,8 @@ public class ConsumerCreateRequestVO {
   private String orgId;
   private String orgName;
   private String ownerName;
+  private boolean rateLimitEenabled;
+  private int rateLimit;
 
   public String getAppId() {
     return appId;
@@ -75,4 +77,20 @@ public void setOwnerName(String ownerName) {
     this.ownerName = ownerName;
   }
 
+  public boolean isRateLimitEenabled() {
+    return rateLimitEenabled;
+  }
+
+  public void setRateLimitEenabled(boolean rateLimitEenabled) {
+    this.rateLimitEenabled = rateLimitEenabled;
+  }
+
+  public int getRateLimit() {
+    return rateLimit;
+  }
+
+  public void setRateLimit(int rateLimit) {
+    this.rateLimit = rateLimit;
+  }
+
 }
diff --git a/apollo-portal/src/main/resources/static/i18n/en.json b/apollo-portal/src/main/resources/static/i18n/en.json
index 69ae8e5e4cf..88d8b4ba91a 100644
--- a/apollo-portal/src/main/resources/static/i18n/en.json
+++ b/apollo-portal/src/main/resources/static/i18n/en.json
@@ -654,6 +654,11 @@
   "Open.Manage.Consumer.AllowCreateApplicationTips": "(Allow third-party applications to create apps and grant them app administrator privileges.",
   "Open.Manage.Consumer.AllowCreateApplication.No": "no",
   "Open.Manage.Consumer.AllowCreateApplication.Yes": "yes",
+  "Open.Manage.Consumer.RateLimit.Enabled": "Whether to enable current limit",
+  "Open.Manage.Consumer.RateLimit.Enabled.Tips": "(After enabling this feature, when third-party applications publish configurations on Apollo, their traffic will be controlled according to the configured QPS limit)",
+  "Open.Manage.Consumer.RateLimitValue": "Current limiting QPS",
+  "Open.Manage.Consumer.RateLimitValueTips": "(Unit: times/second, for example: 100 means that the configuration is published at most 100 times per second)",
+  "Open.Manage.Consumer.RateLimitValue.Error": "The minimum current limiting QPS is 1",
   "Namespace.Role.Title": "Permission Management",
   "Namespace.Role.GrantModifyTo": "Permission to edit",
   "Namespace.Role.GrantModifyTo2": "(Can edit the configuration)",
diff --git a/apollo-portal/src/main/resources/static/i18n/zh-CN.json b/apollo-portal/src/main/resources/static/i18n/zh-CN.json
index 8ae676eda59..3716457476d 100644
--- a/apollo-portal/src/main/resources/static/i18n/zh-CN.json
+++ b/apollo-portal/src/main/resources/static/i18n/zh-CN.json
@@ -654,6 +654,11 @@
   "Open.Manage.Consumer.AllowCreateApplicationTips": "(允许第三方应用创建app,并且对创建出的app,拥有应用管理员的权限)",
   "Open.Manage.Consumer.AllowCreateApplication.No": "否",
   "Open.Manage.Consumer.AllowCreateApplication.Yes": "是",
+  "Open.Manage.Consumer.RateLimit.Enabled": "是否启用限流",
+  "Open.Manage.Consumer.RateLimit.Enabled.Tips": "(开启后,第三方应用在 Apollo 上发布配置时,会根据配置的 QPS 限制,控制其流量)",
+  "Open.Manage.Consumer.RateLimitValue": "限流QPS",
+  "Open.Manage.Consumer.RateLimitValueTips": "(单位:次/秒,例如: 100 表示每秒最多发布 100 次配置)",
+  "Open.Manage.Consumer.RateLimitValue.Error": "限流QPS最小为1",
   "Namespace.Role.Title": "权限管理",
   "Namespace.Role.GrantModifyTo": "修改权",
   "Namespace.Role.GrantModifyTo2": "(可以修改配置)",
diff --git a/apollo-portal/src/main/resources/static/open/add-consumer.html b/apollo-portal/src/main/resources/static/open/add-consumer.html
index 2bbb4f3aa5b..14d066ad812 100644
--- a/apollo-portal/src/main/resources/static/open/add-consumer.html
+++ b/apollo-portal/src/main/resources/static/open/add-consumer.html
@@ -78,6 +78,33 @@ <h5>{{'Open.Manage.CreateThirdApp' | translate }}
                             </div>
                         </div>
 
+                        <div class="form-group">
+                            <label class="col-sm-2 control-label">
+                                {{ 'Open.Manage.Consumer.RateLimit.Enabled' | translate }}
+                            </label>
+                            <div class="col-sm-3">
+                                <input type="checkbox"
+                                       ng-model="consumer.rateLimitEenabled"
+                                       name="rateLimitEenabled"
+                                       ng-change="toggleRateLimitEenabledInput()"
+                                />
+                                <small>{{ 'Open.Manage.Consumer.RateLimit.Enabled.Tips' | translate }}</small>
+                            </div>
+                        </div>
+
+                        <div class="form-group" ng-show="consumer.rateLimitEenabled">
+                            <label class="col-sm-2 control-label">
+                                {{ 'Open.Manage.Consumer.RateLimitValue' | translate }}
+                            </label>
+                            <div class="col-sm-3">
+                                <input type="number"
+                                       ng-model="consumer.rateLimit"
+                                       name="rateLimit"
+                                />
+                                <small>{{'Open.Manage.Consumer.RateLimitValueTips' | translate }}</small>
+                            </div>
+                        </div>
+
                         <div class="form-group">
                             <label class="col-sm-2 control-label">
                                 <apollorequiredfield></apollorequiredfield>
@@ -252,4 +279,4 @@ <h4>{{'Common.IsRootUser' | translate }}</h4>
     <script type="application/javascript" src="../scripts/controller/open/OpenManageController.js"></script>
 </body>
 
-</html>
\ No newline at end of file
+</html>
diff --git a/apollo-portal/src/main/resources/static/scripts/controller/open/OpenManageController.js b/apollo-portal/src/main/resources/static/scripts/controller/open/OpenManageController.js
index abb76e1124e..4eb13e7b1d7 100644
--- a/apollo-portal/src/main/resources/static/scripts/controller/open/OpenManageController.js
+++ b/apollo-portal/src/main/resources/static/scripts/controller/open/OpenManageController.js
@@ -41,6 +41,11 @@ function OpenManageController($scope, $translate, toastr, AppUtil, OrganizationS
     $scope.preDeleteConsumer = preDeleteConsumer;
     $scope.deleteConsumer = deleteConsumer;
     $scope.preGrantPermission = preGrantPermission;
+    $scope.toggleRateLimitEenabledInput = function() {
+        if (!$scope.consumer.rateLimitEenabled) {
+            $scope.consumer.rateLimit = 0;
+        }
+    };
 
     function init() {
         initOrganization();
@@ -163,6 +168,17 @@ function OpenManageController($scope, $translate, toastr, AppUtil, OrganizationS
             $scope.submitBtnDisabled = false;
             return;
         }
+
+        if ($scope.consumer.rateLimitEenabled) {
+            if ($scope.consumer.rateLimit < 1) {
+                toastr.warning($translate.instant('Open.Manage.Consumer.RateLimitValue.Error'));
+                $scope.submitBtnDisabled = false;
+                return;
+            }
+        } else {
+            $scope.consumer.rateLimit = 0;
+        }
+
         var selectedOrg = $orgWidget.select2('data')[0];
 
         if (!selectedOrg.id) {
diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/SkipAuthorizationConfiguration.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/SkipAuthorizationConfiguration.java
index e767ea5d547..017c80b8649 100644
--- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/SkipAuthorizationConfiguration.java
+++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/SkipAuthorizationConfiguration.java
@@ -54,7 +54,7 @@ public ConsumerAuthUtil consumerAuthUtil() {
 
     ConsumerToken someConsumerToken = new ConsumerToken();
     someConsumerToken.setConsumerId(1L);
-    someConsumerToken.setLimitCount(20);
+    someConsumerToken.setRateLimit(20);
     when(mock.getConsumerToken(any())).thenReturn(someConsumerToken);
     return mock;
   }
diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
index e9d8e16071c..afa79572838 100644
--- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
+++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
@@ -53,6 +53,9 @@
  */
 @RunWith(MockitoJUnitRunner.class)
 public class ConsumerAuthenticationFilterTest {
+
+  private static final int TOO_MANY_REQUESTS = 429;
+
   private ConsumerAuthenticationFilter authenticationFilter;
   @Mock
   private ConsumerAuthUtil consumerAuthUtil;
@@ -161,7 +164,7 @@ public void testRateLimitPartFailure() throws Exception {
     int leastTimes = qps * durationInSeconds;
     int mostTimes = (qps + 1) * durationInSeconds;
 
-    verify(response, atLeastOnce()).sendError(eq(HttpServletResponse.SC_FORBIDDEN), anyString());
+    verify(response, atLeastOnce()).sendError(eq(TOO_MANY_REQUESTS), anyString());
 
     verify(consumerAuthUtil, atLeast(leastTimes)).storeConsumerId(request, someConsumerId);
     verify(consumerAuthUtil, atMost(mostTimes)).storeConsumerId(request, someConsumerId);
@@ -176,11 +179,10 @@ public void testRateLimitPartFailure() throws Exception {
   private void setupRateLimitMocks(String someToken, Long someConsumerId, int qps) {
     ConsumerToken someConsumerToken = new ConsumerToken();
     someConsumerToken.setConsumerId(someConsumerId);
-    someConsumerToken.setLimitCount(qps);
+    someConsumerToken.setRateLimit(qps);
 
     when(request.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(someToken);
     when(consumerAuthUtil.getConsumerToken(someToken)).thenReturn(someConsumerToken);
-    when(portalConfig.isOpenApiLimitEnabled()).thenReturn(true);
   }
 
 
diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceControllerTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceControllerTest.java
index 93c1b3a182e..1eb836431ef 100644
--- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceControllerTest.java
+++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceControllerTest.java
@@ -23,6 +23,9 @@
 import org.junit.Assert;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.http.HttpEntity;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.web.client.HttpClientErrorException;
 import static org.hamcrest.Matchers.containsString;
@@ -34,6 +37,10 @@
  */
 @ActiveProfiles("skipAuthorization")
 public class NamespaceControllerTest extends AbstractControllerTest {
+
+  static final HttpHeaders HTTP_HEADERS_WITH_TOKEN = new HttpHeaders() {{
+    set(HttpHeaders.AUTHORIZATION, "3c16bf5b1f44b465179253442460e8c0ad845289");
+  }};
   @Autowired
   private ConsumerPermissionValidator consumerPermissionValidator;
 
@@ -47,9 +54,11 @@ public void shouldFailWhenAppNamespaceNameIsInvalid() {
     dto.setFormat(ConfigFileFormat.Properties.getValue());
     dto.setDataChangeCreatedBy("apollo");
     try {
-      restTemplate.postForEntity(
+      restTemplate.exchange(
           url("/openapi/v1/apps/{appId}/appnamespaces"),
-          dto, OpenAppNamespaceDTO.class, dto.getAppId()
+          HttpMethod.POST,
+          new HttpEntity<>(dto, HTTP_HEADERS_WITH_TOKEN),
+          OpenAppNamespaceDTO.class, dto.getAppId()
       );
       Assert.fail("should throw");
     } catch (HttpClientErrorException e) {
diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ConsumerControllerTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ConsumerControllerTest.java
index a9c4ae2fb61..ba4ef7d0cf7 100644
--- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ConsumerControllerTest.java
+++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ConsumerControllerTest.java
@@ -63,7 +63,7 @@ void createWithCompatibility() {
 
     Mockito.verify(consumerService, Mockito.times(1)).createConsumer(Mockito.any());
     Mockito.verify(consumerService, Mockito.times(1))
-        .generateAndSaveConsumerToken(Mockito.any(), Mockito.any());
+        .generateAndSaveConsumerToken(Mockito.any(), Mockito.any(),Mockito.any());
     Mockito.verify(consumerService, Mockito.times(0))
         .assignCreateApplicationRoleToConsumer(Mockito.any());
     Mockito.verify(consumerService, Mockito.times(1)).getConsumerInfoByAppId(Mockito.any());
@@ -84,16 +84,16 @@ void createAndAssignCreateApplicationRoleToConsumer() {
     {
       ConsumerToken ConsumerToken = new ConsumerToken();
       ConsumerToken.setToken(token);
-      Mockito.when(consumerService.generateAndSaveConsumerToken(Mockito.any(), Mockito.any()))
+      Mockito.when(consumerService.generateAndSaveConsumerToken(Mockito.any(), Mockito.any(), Mockito.any()))
           .thenReturn(ConsumerToken);
     }
     consumerController.create(requestVO, null);
 
     Mockito.verify(consumerService, Mockito.times(1)).createConsumer(Mockito.any());
     Mockito.verify(consumerService, Mockito.times(1))
-        .generateAndSaveConsumerToken(Mockito.any(), Mockito.any());
+        .generateAndSaveConsumerToken(Mockito.any(), Mockito.any(), Mockito.any());
     Mockito.verify(consumerService, Mockito.times(1))
         .assignCreateApplicationRoleToConsumer(Mockito.eq(token));
     Mockito.verify(consumerService, Mockito.times(1)).getConsumerInfoByAppId(Mockito.any());
   }
-}
\ No newline at end of file
+}
diff --git a/apollo-portal/src/test/resources/sql/openapi/ConsumerServiceIntegrationTest.commonData.sql b/apollo-portal/src/test/resources/sql/openapi/ConsumerServiceIntegrationTest.commonData.sql
index a0d416c00ec..435739d4194 100644
--- a/apollo-portal/src/test/resources/sql/openapi/ConsumerServiceIntegrationTest.commonData.sql
+++ b/apollo-portal/src/test/resources/sql/openapi/ConsumerServiceIntegrationTest.commonData.sql
@@ -85,8 +85,8 @@ INSERT INTO "ConsumerRole" (`Id`, `ConsumerId`, `RoleId`, `DataChange_CreatedBy`
 /*!40000 ALTER TABLE `ConsumerRole` ENABLE KEYS */;
 
 /*!40000 ALTER TABLE `ConsumerToken` DISABLE KEYS */;
-INSERT INTO "ConsumerToken" (`Id`, `ConsumerId`, `Token`, `Expires`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`) VALUES
-(1000, 1000, '3c16bf5b1f44b465179253442460e8c0ad845289', '2098-12-31 10:00:00', 'apollo', 'apollo');
+INSERT INTO "ConsumerToken" (`Id`, `ConsumerId`, `Token`, `RateLimit`, `Expires`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`) VALUES
+(1000, 1000, '3c16bf5b1f44b465179253442460e8c0ad845289', 20, '2098-12-31 10:00:00', 'apollo', 'apollo');
 /*!40000 ALTER TABLE `ConsumerToken` ENABLE KEYS */;
 
 /*!40000 ALTER TABLE `Favorite` DISABLE KEYS */;
diff --git a/apollo-portal/src/test/resources/sql/openapi/ConsumerServiceIntegrationTest.testFindAppIdsAuthorizedByConsumerId.sql b/apollo-portal/src/test/resources/sql/openapi/ConsumerServiceIntegrationTest.testFindAppIdsAuthorizedByConsumerId.sql
index 74a9c338663..829c90e2de3 100644
--- a/apollo-portal/src/test/resources/sql/openapi/ConsumerServiceIntegrationTest.testFindAppIdsAuthorizedByConsumerId.sql
+++ b/apollo-portal/src/test/resources/sql/openapi/ConsumerServiceIntegrationTest.testFindAppIdsAuthorizedByConsumerId.sql
@@ -78,8 +78,8 @@ INSERT INTO "ConsumerRole" (`Id`, `ConsumerId`, `RoleId`, `DataChange_CreatedBy`
 /*!40000 ALTER TABLE `ConsumerRole` ENABLE KEYS */;
 
 /*!40000 ALTER TABLE `ConsumerToken` DISABLE KEYS */;
-INSERT INTO "ConsumerToken" (`Id`, `ConsumerId`, `Token`, `Expires`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`) VALUES
-(1000, 1000, '3c16bf5b1f44b465179253442460e8c0ad845289', '2098-12-31 10:00:00', 'apollo', 'apollo');
+INSERT INTO "ConsumerToken" (`Id`, `ConsumerId`, `Token`, `RateLimit`, `Expires`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`) VALUES
+(1000, 1000, '3c16bf5b1f44b465179253442460e8c0ad845289', 20, '2098-12-31 10:00:00', 'apollo', 'apollo');
 /*!40000 ALTER TABLE `ConsumerToken` ENABLE KEYS */;
 
 /*!40000 ALTER TABLE `Favorite` DISABLE KEYS */;
diff --git a/apollo-portal/src/test/resources/sql/openapi/NamespaceControllerTest.testCreateAppNamespace.sql b/apollo-portal/src/test/resources/sql/openapi/NamespaceControllerTest.testCreateAppNamespace.sql
index 9659993fe80..e8003526a9b 100644
--- a/apollo-portal/src/test/resources/sql/openapi/NamespaceControllerTest.testCreateAppNamespace.sql
+++ b/apollo-portal/src/test/resources/sql/openapi/NamespaceControllerTest.testCreateAppNamespace.sql
@@ -71,8 +71,8 @@ INSERT INTO "ConsumerRole" (`Id`, `ConsumerId`, `RoleId`, `DataChange_CreatedBy`
 /*!40000 ALTER TABLE `ConsumerRole` ENABLE KEYS */;
 
 /*!40000 ALTER TABLE `ConsumerToken` DISABLE KEYS */;
-INSERT INTO "ConsumerToken" (`Id`, `ConsumerId`, `Token`, `Expires`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`) VALUES
-(1000, 1000, '3c16bf5b1f44b465179253442460e8c0ad845289', '2098-12-31 10:00:00', 'apollo', 'apollo');
+INSERT INTO "ConsumerToken" (`Id`, `ConsumerId`, `Token`, `RateLimit`, `Expires`, `DataChange_CreatedBy`, `DataChange_LastModifiedBy`) VALUES
+(1000, 1000, '3c16bf5b1f44b465179253442460e8c0ad845289', 20, '2098-12-31 10:00:00', 'apollo', 'apollo');
 /*!40000 ALTER TABLE `ConsumerToken` ENABLE KEYS */;
 
 /*!40000 ALTER TABLE `Favorite` DISABLE KEYS */;
diff --git a/docs/en/deployment/distributed-deployment-guide.md b/docs/en/deployment/distributed-deployment-guide.md
index 9028ec2fd22..91afbe7a4fb 100644
--- a/docs/en/deployment/distributed-deployment-guide.md
+++ b/docs/en/deployment/distributed-deployment-guide.md
@@ -1455,18 +1455,6 @@ Default is 200, which means that each environment will return up to 200 results
 
 Modifying this parameter may affect the performance of the search function, so before modifying it, you should conduct sufficient testing and adjust the value of `apollo.portal.search.perEnvMaxResults` appropriately according to the actual business requirements and system resources to balance the performance and the number of search results.
 
-### 3.1.15 open.api.limit.count - Default value of ConsumerToken current limit value
-
-> For versions 2.4.0 and above
-
-The default value is 20. When creating a third-party application in the Open Platform Authorization Management, the default current limit value of the created ConsumerToken is this value.
-
-### 3.1.16 open.api.limit.enabled - Whether to enable the ConsumerToken current limiting function
-
-> For versions 2.4.0 and above
-
-The default value is `false`. When set to `true`, it means that each ConsumerToken request to the Apollo OpenAPI interface will be limited to a specific QPS, and the current limit value is the value of the `limitCount` field in the `ConsumerToken` table.
-
 ## 3.2 Adjusting ApolloConfigDB configuration
 
 Configuration items are uniformly stored in the ApolloConfigDB.ServerConfig table. It should be noted that each environment's ApolloConfigDB.ServerConfig needs to be configured separately, and the modification takes effect in real time for one minute afterwards.
diff --git a/docs/zh/deployment/distributed-deployment-guide.md b/docs/zh/deployment/distributed-deployment-guide.md
index e948ee88527..6e34a9f664e 100644
--- a/docs/zh/deployment/distributed-deployment-guide.md
+++ b/docs/zh/deployment/distributed-deployment-guide.md
@@ -1400,18 +1400,6 @@ portal上“帮助”链接的地址,默认是Apollo github的wiki首页,可
 
 修改该参数可能会影响搜索功能的性能,因此在修改之前应该进行充分的测试,根据实际业务需求和系统资源情况,适当调整`apollo.portal.search.perEnvMaxResults`的值,以平衡性能和搜索结果的数量
 
-### 3.1.15 open.api.limit.count - ConsumerToken 限流值的默认值
-
-> 适用于2.4.0及以上版本
-
-默认为20,当在 `开放平台授权管理-创建第三方应用` 时,创建的 ConsumerToken 默认的限流值即为此值
-
-### 3.1.16 open.api.limit.enabled - 是否开启 ConsumerToken 限流功能
-
-> 适用于2.4.0及以上版本
-
-默认为`false`,当设置为`true`,意味着每个 ConsumerToken 请求Apollo OpenAPI 接口的时候,都会被限制到特定的QPS,限流值为`ConsumerToken`表中的`limitCount`字段的值
-
 ## 3.2 调整ApolloConfigDB配置
 配置项统一存储在ApolloConfigDB.ServerConfig表中,需要注意每个环境的ApolloConfigDB.ServerConfig都需要单独配置,修改完一分钟实时生效。
 
diff --git a/scripts/sql/profiles/h2-default/apolloportaldb.sql b/scripts/sql/profiles/h2-default/apolloportaldb.sql
index 4ebf711c343..eb935078e85 100644
--- a/scripts/sql/profiles/h2-default/apolloportaldb.sql
+++ b/scripts/sql/profiles/h2-default/apolloportaldb.sql
@@ -161,7 +161,7 @@ CREATE TABLE `ConsumerToken` (
   `Id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增Id',
   `ConsumerId` int(11) unsigned DEFAULT NULL COMMENT 'ConsumerId',
   `Token` varchar(128) NOT NULL DEFAULT '' COMMENT 'token',
-  `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值',
+  `RateLimit` int NOT NULL DEFAULT '0' COMMENT '限流值',
   `Expires` datetime NOT NULL DEFAULT '2099-01-01 00:00:00' COMMENT 'token失效时间',
   `IsDeleted` boolean NOT NULL DEFAULT FALSE COMMENT '1: deleted, 0: normal',
   `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds',
diff --git a/scripts/sql/profiles/h2-default/delta/v230-v240/apolloportaldb-v230-v240.sql b/scripts/sql/profiles/h2-default/delta/v230-v240/apolloportaldb-v230-v240.sql
index 7bb1675a1f5..5c7230d6c68 100644
--- a/scripts/sql/profiles/h2-default/delta/v230-v240/apolloportaldb-v230-v240.sql
+++ b/scripts/sql/profiles/h2-default/delta/v230-v240/apolloportaldb-v230-v240.sql
@@ -31,7 +31,7 @@ CREATE ALIAS IF NOT EXISTS UNIX_TIMESTAMP FOR "com.ctrip.framework.apollo.common
 
 -- 
 
-ALTER TABLE `ConsumerToken` ADD COLUMN `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值' AFTER `Token`;
+ALTER TABLE `ConsumerToken` ADD COLUMN `RateLimit` int NOT NULL DEFAULT '0' COMMENT '限流值' AFTER `Token`;
 
 -- 
 -- ===============================================================================
diff --git a/scripts/sql/profiles/mysql-database-not-specified/apolloportaldb.sql b/scripts/sql/profiles/mysql-database-not-specified/apolloportaldb.sql
index 2649d658a75..e1806eb7d41 100644
--- a/scripts/sql/profiles/mysql-database-not-specified/apolloportaldb.sql
+++ b/scripts/sql/profiles/mysql-database-not-specified/apolloportaldb.sql
@@ -162,7 +162,7 @@ CREATE TABLE `ConsumerToken` (
   `Id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增Id',
   `ConsumerId` int(11) unsigned DEFAULT NULL COMMENT 'ConsumerId',
   `Token` varchar(128) NOT NULL DEFAULT '' COMMENT 'token',
-  `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值',
+  `RateLimit` int NOT NULL DEFAULT '0' COMMENT '限流值',
   `Expires` datetime NOT NULL DEFAULT '2099-01-01 00:00:00' COMMENT 'token失效时间',
   `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal',
   `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds',
diff --git a/scripts/sql/profiles/mysql-database-not-specified/delta/v230-v240/apolloportaldb-v230-v240.sql b/scripts/sql/profiles/mysql-database-not-specified/delta/v230-v240/apolloportaldb-v230-v240.sql
index e3dbbbea4e3..2acb52dde8d 100644
--- a/scripts/sql/profiles/mysql-database-not-specified/delta/v230-v240/apolloportaldb-v230-v240.sql
+++ b/scripts/sql/profiles/mysql-database-not-specified/delta/v230-v240/apolloportaldb-v230-v240.sql
@@ -27,7 +27,7 @@
 -- 
 
 ALTER TABLE `ConsumerToken`
-    ADD COLUMN `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值' AFTER `Token`;
+    ADD COLUMN `RateLimit` int NOT NULL DEFAULT '0' COMMENT '限流值' AFTER `Token`;
 
 -- 
 -- ===============================================================================
diff --git a/scripts/sql/profiles/mysql-default/apolloportaldb.sql b/scripts/sql/profiles/mysql-default/apolloportaldb.sql
index a0b01c9c413..778e8b8ea0f 100644
--- a/scripts/sql/profiles/mysql-default/apolloportaldb.sql
+++ b/scripts/sql/profiles/mysql-default/apolloportaldb.sql
@@ -167,7 +167,7 @@ CREATE TABLE `ConsumerToken` (
   `Id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增Id',
   `ConsumerId` int(11) unsigned DEFAULT NULL COMMENT 'ConsumerId',
   `Token` varchar(128) NOT NULL DEFAULT '' COMMENT 'token',
-  `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值',
+  `RateLimit` int NOT NULL DEFAULT '0' COMMENT '限流值',
   `Expires` datetime NOT NULL DEFAULT '2099-01-01 00:00:00' COMMENT 'token失效时间',
   `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal',
   `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds',
diff --git a/scripts/sql/profiles/mysql-default/delta/v230-v240/apolloportaldb-v230-v240.sql b/scripts/sql/profiles/mysql-default/delta/v230-v240/apolloportaldb-v230-v240.sql
index 51fcbd5f7ed..06c50f032f5 100644
--- a/scripts/sql/profiles/mysql-default/delta/v230-v240/apolloportaldb-v230-v240.sql
+++ b/scripts/sql/profiles/mysql-default/delta/v230-v240/apolloportaldb-v230-v240.sql
@@ -29,7 +29,7 @@
 Use ApolloPortalDB;
 
 ALTER TABLE `ConsumerToken`
-    ADD COLUMN `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值' AFTER `Token`;
+    ADD COLUMN `RateLimit` int NOT NULL DEFAULT '0' COMMENT '限流值' AFTER `Token`;
 
 -- 
 -- ===============================================================================
diff --git a/scripts/sql/src/apolloportaldb.sql b/scripts/sql/src/apolloportaldb.sql
index 3398e5db88e..c2c3e4979d4 100644
--- a/scripts/sql/src/apolloportaldb.sql
+++ b/scripts/sql/src/apolloportaldb.sql
@@ -155,7 +155,7 @@ CREATE TABLE `ConsumerToken` (
   `Id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增Id',
   `ConsumerId` int(11) unsigned DEFAULT NULL COMMENT 'ConsumerId',
   `Token` varchar(128) NOT NULL DEFAULT '' COMMENT 'token',
-  `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值',
+  `RateLimit` int NOT NULL DEFAULT '0' COMMENT '限流值',
   `Expires` datetime NOT NULL DEFAULT '2099-01-01 00:00:00' COMMENT 'token失效时间',
   `IsDeleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: deleted, 0: normal',
   `DeletedAt` BIGINT(20) NOT NULL DEFAULT '0' COMMENT 'Delete timestamp based on milliseconds',
diff --git a/scripts/sql/src/delta/v230-v240/apolloportaldb-v230-v240.sql b/scripts/sql/src/delta/v230-v240/apolloportaldb-v230-v240.sql
index b7fbb33ba0d..c018b766c3b 100644
--- a/scripts/sql/src/delta/v230-v240/apolloportaldb-v230-v240.sql
+++ b/scripts/sql/src/delta/v230-v240/apolloportaldb-v230-v240.sql
@@ -20,6 +20,6 @@
 -- ${gists.useDatabase}
 
 ALTER TABLE `ConsumerToken`
-    ADD COLUMN `LimitCount` int NOT NULL DEFAULT 20 COMMENT '限流值' AFTER `Token`;
+    ADD COLUMN `RateLimit` int NOT NULL DEFAULT '0' COMMENT '限流值' AFTER `Token`;
 
 -- ${gists.autoGeneratedDeclaration}

From 9e2c4e869d3013d39a7543c2de67c790b7b8768c Mon Sep 17 00:00:00 2001
From: yangzl <youngzil@163.com>
Date: Mon, 18 Nov 2024 00:34:36 +0800
Subject: [PATCH 04/15] refactor(Consumer): Spelling error in attribute

---
 .../apollo/common/exception/BadRequestException.java  |  2 +-
 .../apollo/openapi/service/ConsumerService.java       | 11 ++++++++---
 .../apollo/portal/controller/ConsumerController.java  |  2 +-
 .../entity/vo/consumer/ConsumerCreateRequestVO.java   | 10 +++++-----
 .../src/main/resources/static/open/add-consumer.html  |  8 ++++----
 .../scripts/controller/open/OpenManageController.js   |  6 +++---
 .../v1/controller/NamespaceControllerTest.java        |  2 +-
 7 files changed, 23 insertions(+), 18 deletions(-)

diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/exception/BadRequestException.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/exception/BadRequestException.java
index 2aeb4c3e0f3..22be8723f03 100644
--- a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/exception/BadRequestException.java
+++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/exception/BadRequestException.java
@@ -38,7 +38,7 @@ public static BadRequestException orgIdIsBlank() {
   }
 
   public static BadRequestException rateLimitIsInvalid() {
-    return new BadRequestException("Ratelimit must be greater than 1");
+    return new BadRequestException("rate limit must be greater than 1");
   }
 
   public static BadRequestException itemAlreadyExists(String itemKey) {
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
index ab5d25e65ae..cbc4b4b701a 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
@@ -122,6 +122,7 @@ public Consumer createConsumer(Consumer consumer) {
 
   public ConsumerToken generateAndSaveConsumerToken(Consumer consumer, Integer rateLimit, Date expires) {
     Preconditions.checkArgument(consumer != null, "Consumer can not be null");
+    Preconditions.checkArgument(rateLimit != null && rateLimit >= 0, "Rate limit must be non-negative");
 
     ConsumerToken consumerToken = generateConsumerToken(consumer, rateLimit, expires);
     consumerToken.setId(0);
@@ -322,6 +323,10 @@ private ConsumerToken generateConsumerToken(Consumer consumer, Integer rateLimit
     String createdBy = userInfoHolder.getUser().getUserId();
     Date createdTime = new Date();
 
+    if (rateLimit == null || rateLimit < 0) {
+      rateLimit = 0;
+    }
+
     ConsumerToken consumerToken = new ConsumerToken();
     consumerToken.setConsumerId(consumerId);
     consumerToken.setRateLimit(rateLimit);
@@ -353,7 +358,7 @@ String generateToken(String consumerAppId, Date generationTime, String consumerT
         (generationTime), consumerTokenSalt), Charsets.UTF_8).toString();
   }
 
-    ConsumerRole createConsumerRole(Long consumerId, Long roleId, String operator) {
+  ConsumerRole createConsumerRole(Long consumerId, Long roleId, String operator) {
     ConsumerRole consumerRole = new ConsumerRole();
 
     consumerRole.setConsumerId(consumerId);
@@ -392,7 +397,7 @@ private Set<String> findAppIdsByRoleIds(List<Long> roleIds) {
     return appIds;
   }
 
-  List<Consumer> findAllConsumer(Pageable page){
+  List<Consumer> findAllConsumer(Pageable page) {
     return this.consumerRepository.findAll(page).getContent();
   }
 
@@ -417,7 +422,7 @@ public List<ConsumerInfo> findConsumerInfoList(Pageable page) {
   }
 
   @Transactional
-  public void deleteConsumer(String appId){
+  public void deleteConsumer(String appId) {
     Consumer consumer = consumerRepository.findByAppId(appId);
     if (consumer == null) {
       throw new BadRequestException("ConsumerApp not exist");
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java
index 1e695b3159e..0bc1d08e56e 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/ConsumerController.java
@@ -81,7 +81,7 @@ public ConsumerInfo create(
       throw BadRequestException.orgIdIsBlank();
     }
 
-    if (requestVO.isRateLimitEenabled()) {
+    if (requestVO.isRateLimitEnabled()) {
       if (requestVO.getRateLimit() <= 0) {
         throw BadRequestException.rateLimitIsInvalid();
       }
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerCreateRequestVO.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerCreateRequestVO.java
index 248f8312516..65ad8e21454 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerCreateRequestVO.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerCreateRequestVO.java
@@ -26,7 +26,7 @@ public class ConsumerCreateRequestVO {
   private String orgId;
   private String orgName;
   private String ownerName;
-  private boolean rateLimitEenabled;
+  private boolean rateLimitEnabled;
   private int rateLimit;
 
   public String getAppId() {
@@ -77,12 +77,12 @@ public void setOwnerName(String ownerName) {
     this.ownerName = ownerName;
   }
 
-  public boolean isRateLimitEenabled() {
-    return rateLimitEenabled;
+  public boolean isRateLimitEnabled() {
+    return rateLimitEnabled;
   }
 
-  public void setRateLimitEenabled(boolean rateLimitEenabled) {
-    this.rateLimitEenabled = rateLimitEenabled;
+  public void setRateLimitEnabled(boolean rateLimitEnabled) {
+    this.rateLimitEnabled = rateLimitEnabled;
   }
 
   public int getRateLimit() {
diff --git a/apollo-portal/src/main/resources/static/open/add-consumer.html b/apollo-portal/src/main/resources/static/open/add-consumer.html
index 14d066ad812..94c490c2b35 100644
--- a/apollo-portal/src/main/resources/static/open/add-consumer.html
+++ b/apollo-portal/src/main/resources/static/open/add-consumer.html
@@ -84,15 +84,15 @@ <h5>{{'Open.Manage.CreateThirdApp' | translate }}
                             </label>
                             <div class="col-sm-3">
                                 <input type="checkbox"
-                                       ng-model="consumer.rateLimitEenabled"
-                                       name="rateLimitEenabled"
-                                       ng-change="toggleRateLimitEenabledInput()"
+                                       ng-model="consumer.rateLimitEnabled"
+                                       name="rateLimitEnabled"
+                                       ng-change="toggleRateLimitEnabledInput()"
                                 />
                                 <small>{{ 'Open.Manage.Consumer.RateLimit.Enabled.Tips' | translate }}</small>
                             </div>
                         </div>
 
-                        <div class="form-group" ng-show="consumer.rateLimitEenabled">
+                        <div class="form-group" ng-show="consumer.rateLimitEnabled">
                             <label class="col-sm-2 control-label">
                                 {{ 'Open.Manage.Consumer.RateLimitValue' | translate }}
                             </label>
diff --git a/apollo-portal/src/main/resources/static/scripts/controller/open/OpenManageController.js b/apollo-portal/src/main/resources/static/scripts/controller/open/OpenManageController.js
index 4eb13e7b1d7..dc1c9efc7a6 100644
--- a/apollo-portal/src/main/resources/static/scripts/controller/open/OpenManageController.js
+++ b/apollo-portal/src/main/resources/static/scripts/controller/open/OpenManageController.js
@@ -41,8 +41,8 @@ function OpenManageController($scope, $translate, toastr, AppUtil, OrganizationS
     $scope.preDeleteConsumer = preDeleteConsumer;
     $scope.deleteConsumer = deleteConsumer;
     $scope.preGrantPermission = preGrantPermission;
-    $scope.toggleRateLimitEenabledInput = function() {
-        if (!$scope.consumer.rateLimitEenabled) {
+    $scope.toggleRateLimitEnabledInput = function() {
+        if (!$scope.consumer.rateLimitEnabled) {
             $scope.consumer.rateLimit = 0;
         }
     };
@@ -169,7 +169,7 @@ function OpenManageController($scope, $translate, toastr, AppUtil, OrganizationS
             return;
         }
 
-        if ($scope.consumer.rateLimitEenabled) {
+        if ($scope.consumer.rateLimitEnabled) {
             if ($scope.consumer.rateLimit < 1) {
                 toastr.warning($translate.instant('Open.Manage.Consumer.RateLimitValue.Error'));
                 $scope.submitBtnDisabled = false;
diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceControllerTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceControllerTest.java
index 1eb836431ef..d7eb02d47da 100644
--- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceControllerTest.java
+++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceControllerTest.java
@@ -39,7 +39,7 @@
 public class NamespaceControllerTest extends AbstractControllerTest {
 
   static final HttpHeaders HTTP_HEADERS_WITH_TOKEN = new HttpHeaders() {{
-    set(HttpHeaders.AUTHORIZATION, "3c16bf5b1f44b465179253442460e8c0ad845289");
+    set(HttpHeaders.AUTHORIZATION, "test-token");
   }};
   @Autowired
   private ConsumerPermissionValidator consumerPermissionValidator;

From a622f565ed228333ec786897ffc5f387e0e4d98d Mon Sep 17 00:00:00 2001
From: yangzl <youngzil@163.com>
Date: Thu, 21 Nov 2024 08:33:39 +0800
Subject: [PATCH 05/15] refactor(openapi): Refactor consumer authentication
 filters and related services

---
 .../filter/ConsumerAuthenticationFilter.java      | 11 +++--------
 .../apollo/openapi/service/ConsumerService.java   |  1 -
 .../configuration/AuthFilterConfiguration.java    |  7 ++-----
 .../src/main/resources/static/i18n/en.json        |  6 +++---
 .../apollo/SkipAuthorizationConfiguration.java    |  1 +
 .../filter/ConsumerAuthenticationFilterTest.java  | 15 +++++++--------
 .../v1/controller/NamespaceControllerTest.java    | 13 ++-----------
 .../portal/controller/ConsumerControllerTest.java |  2 +-
 8 files changed, 19 insertions(+), 37 deletions(-)

diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
index 3e1ae49ae59..1a7b1f909cf 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
@@ -19,13 +19,11 @@
 import com.ctrip.framework.apollo.openapi.entity.ConsumerToken;
 import com.ctrip.framework.apollo.openapi.util.ConsumerAuditUtil;
 import com.ctrip.framework.apollo.openapi.util.ConsumerAuthUtil;
-import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
 import com.google.common.cache.Cache;
 import com.google.common.cache.CacheBuilder;
 import com.google.common.util.concurrent.RateLimiter;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
-import java.util.concurrent.TimeUnit;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
@@ -48,21 +46,18 @@ public class ConsumerAuthenticationFilter implements Filter {
 
   private final ConsumerAuthUtil consumerAuthUtil;
   private final ConsumerAuditUtil consumerAuditUtil;
-  private final PortalConfig portalConfig;
 
   private static final int WARMUP_MILLIS = 1000; // ms
-  private static final int RATE_LIMITER_CACHE_MAX_SIZE = 50000;
+  private static final int RATE_LIMITER_CACHE_MAX_SIZE = 20000;
 
   private static final int TOO_MANY_REQUESTS = 429;
 
   private static final Cache<String, ImmutablePair<Long, RateLimiter>> LIMITER = CacheBuilder.newBuilder()
-      .expireAfterWrite(1, TimeUnit.DAYS)
       .maximumSize(RATE_LIMITER_CACHE_MAX_SIZE).build();
 
-  public ConsumerAuthenticationFilter(ConsumerAuthUtil consumerAuthUtil, ConsumerAuditUtil consumerAuditUtil, PortalConfig portalConfig) {
+  public ConsumerAuthenticationFilter(ConsumerAuthUtil consumerAuthUtil, ConsumerAuditUtil consumerAuditUtil) {
     this.consumerAuthUtil = consumerAuthUtil;
     this.consumerAuditUtil = consumerAuditUtil;
-    this.portalConfig = portalConfig;
   }
 
   @Override
@@ -87,7 +82,7 @@ public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain
     Integer rateLimit = consumerToken.getRateLimit();
     if (null != rateLimit && rateLimit > 0) {
       try {
-        ImmutablePair<Long, RateLimiter> rateLimiterPair = getOrCreateRateLimiterPair(token, rateLimit);
+        ImmutablePair<Long, RateLimiter> rateLimiterPair = getOrCreateRateLimiterPair(consumerToken.getToken(), rateLimit);
         long warmupToMillis = rateLimiterPair.getLeft() + WARMUP_MILLIS;
         if (System.currentTimeMillis() > warmupToMillis && !rateLimiterPair.getRight().tryAcquire()) {
           response.sendError(TOO_MANY_REQUESTS, "Too Many Requests, the flow is limited");
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
index cbc4b4b701a..f9b8ed5882c 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
@@ -122,7 +122,6 @@ public Consumer createConsumer(Consumer consumer) {
 
   public ConsumerToken generateAndSaveConsumerToken(Consumer consumer, Integer rateLimit, Date expires) {
     Preconditions.checkArgument(consumer != null, "Consumer can not be null");
-    Preconditions.checkArgument(rateLimit != null && rateLimit >= 0, "Rate limit must be non-negative");
 
     ConsumerToken consumerToken = generateConsumerToken(consumer, rateLimit, expires);
     consumerToken.setId(0);
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/configuration/AuthFilterConfiguration.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/configuration/AuthFilterConfiguration.java
index 40eb0309029..38a662c6dae 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/configuration/AuthFilterConfiguration.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/configuration/AuthFilterConfiguration.java
@@ -19,7 +19,6 @@
 import com.ctrip.framework.apollo.openapi.filter.ConsumerAuthenticationFilter;
 import com.ctrip.framework.apollo.openapi.util.ConsumerAuditUtil;
 import com.ctrip.framework.apollo.openapi.util.ConsumerAuthUtil;
-import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
 import org.springframework.boot.web.servlet.FilterRegistrationBean;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -30,16 +29,14 @@ public class AuthFilterConfiguration {
   @Bean
   public FilterRegistrationBean<ConsumerAuthenticationFilter> openApiAuthenticationFilter(
       ConsumerAuthUtil consumerAuthUtil,
-      ConsumerAuditUtil consumerAuditUtil,
-      PortalConfig portalConfig) {
+      ConsumerAuditUtil consumerAuditUtil) {
 
     FilterRegistrationBean<ConsumerAuthenticationFilter> openApiFilter = new FilterRegistrationBean<>();
 
-    openApiFilter.setFilter(new ConsumerAuthenticationFilter(consumerAuthUtil, consumerAuditUtil, portalConfig));
+    openApiFilter.setFilter(new ConsumerAuthenticationFilter(consumerAuthUtil, consumerAuditUtil));
     openApiFilter.addUrlPatterns("/openapi/*");
 
     return openApiFilter;
   }
 
-
 }
diff --git a/apollo-portal/src/main/resources/static/i18n/en.json b/apollo-portal/src/main/resources/static/i18n/en.json
index 88d8b4ba91a..3e5ba047b2b 100644
--- a/apollo-portal/src/main/resources/static/i18n/en.json
+++ b/apollo-portal/src/main/resources/static/i18n/en.json
@@ -654,11 +654,11 @@
   "Open.Manage.Consumer.AllowCreateApplicationTips": "(Allow third-party applications to create apps and grant them app administrator privileges.",
   "Open.Manage.Consumer.AllowCreateApplication.No": "no",
   "Open.Manage.Consumer.AllowCreateApplication.Yes": "yes",
-  "Open.Manage.Consumer.RateLimit.Enabled": "Whether to enable current limit",
+  "Open.Manage.Consumer.RateLimit.Enabled": "Whether to enable rate limit",
   "Open.Manage.Consumer.RateLimit.Enabled.Tips": "(After enabling this feature, when third-party applications publish configurations on Apollo, their traffic will be controlled according to the configured QPS limit)",
-  "Open.Manage.Consumer.RateLimitValue": "Current limiting QPS",
+  "Open.Manage.Consumer.RateLimitValue": "Rate limiting QPS",
   "Open.Manage.Consumer.RateLimitValueTips": "(Unit: times/second, for example: 100 means that the configuration is published at most 100 times per second)",
-  "Open.Manage.Consumer.RateLimitValue.Error": "The minimum current limiting QPS is 1",
+  "Open.Manage.Consumer.RateLimitValue.Error": "The minimum rate limiting QPS is 1",
   "Namespace.Role.Title": "Permission Management",
   "Namespace.Role.GrantModifyTo": "Permission to edit",
   "Namespace.Role.GrantModifyTo2": "(Can edit the configuration)",
diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/SkipAuthorizationConfiguration.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/SkipAuthorizationConfiguration.java
index 017c80b8649..631f1b77b5b 100644
--- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/SkipAuthorizationConfiguration.java
+++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/SkipAuthorizationConfiguration.java
@@ -54,6 +54,7 @@ public ConsumerAuthUtil consumerAuthUtil() {
 
     ConsumerToken someConsumerToken = new ConsumerToken();
     someConsumerToken.setConsumerId(1L);
+    someConsumerToken.setToken("some-token");
     someConsumerToken.setRateLimit(20);
     when(mock.getConsumerToken(any())).thenReturn(someConsumerToken);
     return mock;
diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
index afa79572838..3ed0fc3e628 100644
--- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
+++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
@@ -20,7 +20,6 @@
 import com.ctrip.framework.apollo.openapi.util.ConsumerAuditUtil;
 import com.ctrip.framework.apollo.openapi.util.ConsumerAuthUtil;
 
-import com.ctrip.framework.apollo.portal.component.config.PortalConfig;
 import java.io.IOException;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
@@ -61,8 +60,6 @@ public class ConsumerAuthenticationFilterTest {
   private ConsumerAuthUtil consumerAuthUtil;
   @Mock
   private ConsumerAuditUtil consumerAuditUtil;
-  @Mock
-  private PortalConfig portalConfig;
 
   @Mock
   private HttpServletRequest request;
@@ -73,7 +70,7 @@ public class ConsumerAuthenticationFilterTest {
 
   @Before
   public void setUp() throws Exception {
-    authenticationFilter = new ConsumerAuthenticationFilter(consumerAuthUtil, consumerAuditUtil, portalConfig);
+    authenticationFilter = new ConsumerAuthenticationFilter(consumerAuthUtil, consumerAuditUtil);
   }
 
   @Test
@@ -115,7 +112,7 @@ public void testRateLimitSuccessfully() throws Exception {
     String someToken = "someToken";
     Long someConsumerId = 1L;
     int qps = 5;
-    int durationInSeconds = 10;
+    int durationInSeconds = 3;
 
     setupRateLimitMocks(someToken, someConsumerId, qps);
 
@@ -145,7 +142,7 @@ public void testRateLimitPartFailure() throws Exception {
     String someToken = "someToken";
     Long someConsumerId = 1L;
     int qps = 5;
-    int durationInSeconds = 10;
+    int durationInSeconds = 3;
 
     setupRateLimitMocks(someToken, someConsumerId, qps);
 
@@ -159,10 +156,11 @@ public void testRateLimitPartFailure() throws Exception {
       }
     };
 
-    executeWithQps(qps + 1, task, durationInSeconds);
+    int realQps = qps + 10;
+    executeWithQps(realQps, task, durationInSeconds);
 
     int leastTimes = qps * durationInSeconds;
-    int mostTimes = (qps + 1) * durationInSeconds;
+    int mostTimes = realQps * durationInSeconds;
 
     verify(response, atLeastOnce()).sendError(eq(TOO_MANY_REQUESTS), anyString());
 
@@ -180,6 +178,7 @@ private void setupRateLimitMocks(String someToken, Long someConsumerId, int qps)
     ConsumerToken someConsumerToken = new ConsumerToken();
     someConsumerToken.setConsumerId(someConsumerId);
     someConsumerToken.setRateLimit(qps);
+    someConsumerToken.setToken(someToken);
 
     when(request.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(someToken);
     when(consumerAuthUtil.getConsumerToken(someToken)).thenReturn(someConsumerToken);
diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceControllerTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceControllerTest.java
index d7eb02d47da..93c1b3a182e 100644
--- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceControllerTest.java
+++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/v1/controller/NamespaceControllerTest.java
@@ -23,9 +23,6 @@
 import org.junit.Assert;
 import org.junit.Test;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.http.HttpEntity;
-import org.springframework.http.HttpHeaders;
-import org.springframework.http.HttpMethod;
 import org.springframework.test.context.ActiveProfiles;
 import org.springframework.web.client.HttpClientErrorException;
 import static org.hamcrest.Matchers.containsString;
@@ -37,10 +34,6 @@
  */
 @ActiveProfiles("skipAuthorization")
 public class NamespaceControllerTest extends AbstractControllerTest {
-
-  static final HttpHeaders HTTP_HEADERS_WITH_TOKEN = new HttpHeaders() {{
-    set(HttpHeaders.AUTHORIZATION, "test-token");
-  }};
   @Autowired
   private ConsumerPermissionValidator consumerPermissionValidator;
 
@@ -54,11 +47,9 @@ public void shouldFailWhenAppNamespaceNameIsInvalid() {
     dto.setFormat(ConfigFileFormat.Properties.getValue());
     dto.setDataChangeCreatedBy("apollo");
     try {
-      restTemplate.exchange(
+      restTemplate.postForEntity(
           url("/openapi/v1/apps/{appId}/appnamespaces"),
-          HttpMethod.POST,
-          new HttpEntity<>(dto, HTTP_HEADERS_WITH_TOKEN),
-          OpenAppNamespaceDTO.class, dto.getAppId()
+          dto, OpenAppNamespaceDTO.class, dto.getAppId()
       );
       Assert.fail("should throw");
     } catch (HttpClientErrorException e) {
diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ConsumerControllerTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ConsumerControllerTest.java
index ba4ef7d0cf7..ea6a525ad82 100644
--- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ConsumerControllerTest.java
+++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/ConsumerControllerTest.java
@@ -63,7 +63,7 @@ void createWithCompatibility() {
 
     Mockito.verify(consumerService, Mockito.times(1)).createConsumer(Mockito.any());
     Mockito.verify(consumerService, Mockito.times(1))
-        .generateAndSaveConsumerToken(Mockito.any(), Mockito.any(),Mockito.any());
+        .generateAndSaveConsumerToken(Mockito.any(), Mockito.any(), Mockito.any());
     Mockito.verify(consumerService, Mockito.times(0))
         .assignCreateApplicationRoleToConsumer(Mockito.any());
     Mockito.verify(consumerService, Mockito.times(1)).getConsumerInfoByAppId(Mockito.any());

From 4939d70c7bdc4078d8794f1a7d686bd6a98b0691 Mon Sep 17 00:00:00 2001
From: yangzl <youngzil@163.com>
Date: Thu, 21 Nov 2024 23:09:13 +0800
Subject: [PATCH 06/15] test(apollo-portal): Optimize the rate limiting test of
 ConsumerAuthenticationFilter

---
 .../filter/ConsumerAuthenticationFilterTest.java      | 11 ++++++-----
 1 file changed, 6 insertions(+), 5 deletions(-)

diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
index 3ed0fc3e628..171242c2e6d 100644
--- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
+++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilterTest.java
@@ -109,7 +109,7 @@ public void testAuthFailed() throws Exception {
 
   @Test
   public void testRateLimitSuccessfully() throws Exception {
-    String someToken = "someToken";
+    String someToken = "some-ratelimit-success-token";
     Long someConsumerId = 1L;
     int qps = 5;
     int durationInSeconds = 3;
@@ -126,9 +126,10 @@ public void testRateLimitSuccessfully() throws Exception {
       }
     };
 
-    executeWithQps(qps, task, durationInSeconds);
+    int realQps = qps - 1;
+    executeWithQps(realQps, task, durationInSeconds);
 
-    int total = qps * durationInSeconds;
+    int total = realQps * durationInSeconds;
 
     verify(consumerAuthUtil, times(total)).storeConsumerId(request, someConsumerId);
     verify(consumerAuditUtil, times(total)).audit(request, someConsumerId);
@@ -139,7 +140,7 @@ public void testRateLimitSuccessfully() throws Exception {
 
   @Test
   public void testRateLimitPartFailure() throws Exception {
-    String someToken = "someToken";
+     String someToken = "some-ratelimit-fail-token";
     Long someConsumerId = 1L;
     int qps = 5;
     int durationInSeconds = 3;
@@ -156,7 +157,7 @@ public void testRateLimitPartFailure() throws Exception {
       }
     };
 
-    int realQps = qps + 10;
+    int realQps = qps + 3;
     executeWithQps(realQps, task, durationInSeconds);
 
     int leastTimes = qps * durationInSeconds;

From 22ebb4fb98456b1812fa6905a9096ba4066e5b72 Mon Sep 17 00:00:00 2001
From: yangzl <youngzil@163.com>
Date: Sat, 23 Nov 2024 20:37:22 +0800
Subject: [PATCH 07/15] featapi(open): Updated management page to show consumer
 rate limit information

---
 .../filter/ConsumerAuthenticationFilter.java  |  4 +++-
 .../openapi/service/ConsumerService.java      | 24 ++++++++++++++++---
 .../entity/vo/consumer/ConsumerInfo.java      | 11 +++++++++
 .../main/resources/static/open/manage.html    |  8 ++++---
 .../controller/open/OpenManageController.js   |  2 +-
 5 files changed, 41 insertions(+), 8 deletions(-)

diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
index 1a7b1f909cf..1aaa8f1d4ec 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/filter/ConsumerAuthenticationFilter.java
@@ -24,6 +24,7 @@
 import com.google.common.util.concurrent.RateLimiter;
 import java.io.IOException;
 import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
 import javax.servlet.Filter;
 import javax.servlet.FilterChain;
 import javax.servlet.FilterConfig;
@@ -53,6 +54,7 @@ public class ConsumerAuthenticationFilter implements Filter {
   private static final int TOO_MANY_REQUESTS = 429;
 
   private static final Cache<String, ImmutablePair<Long, RateLimiter>> LIMITER = CacheBuilder.newBuilder()
+      .expireAfterAccess(1, TimeUnit.HOURS)
       .maximumSize(RATE_LIMITER_CACHE_MAX_SIZE).build();
 
   public ConsumerAuthenticationFilter(ConsumerAuthUtil consumerAuthUtil, ConsumerAuditUtil consumerAuditUtil) {
@@ -108,7 +110,7 @@ public void destroy() {
   }
 
   private ImmutablePair<Long, RateLimiter> getOrCreateRateLimiterPair(String key, Integer limitCount) {
-    try{
+    try {
       return LIMITER.get(key, () ->
           ImmutablePair.of(System.currentTimeMillis(), RateLimiter.create(limitCount)));
     } catch (ExecutionException e) {
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
index f9b8ed5882c..fc44e76a69d 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
@@ -198,7 +198,8 @@ public List<ConsumerRole> assignNamespaceRoleToConsumer(String token, String app
   private ConsumerInfo convert(
       Consumer consumer,
       String token,
-      boolean allowCreateApplication
+      boolean allowCreateApplication,
+      Integer rateLimit
   ) {
     ConsumerInfo consumerInfo = new ConsumerInfo();
     consumerInfo.setConsumerId(consumer.getId());
@@ -208,6 +209,7 @@ private ConsumerInfo convert(
     consumerInfo.setOwnerEmail(consumer.getOwnerEmail());
     consumerInfo.setOrgId(consumer.getOrgId());
     consumerInfo.setOrgName(consumer.getOrgName());
+    consumerInfo.setRateLimit(rateLimit);
 
     consumerInfo.setToken(token);
     consumerInfo.setAllowCreateApplication(allowCreateApplication);
@@ -223,13 +225,17 @@ public ConsumerInfo getConsumerInfoByAppId(String appId) {
     if (consumer == null) {
       return null;
     }
-    return convert(consumer, consumerToken.getToken(), isAllowCreateApplication(consumer.getId()));
+    return convert(consumer, consumerToken.getToken(), isAllowCreateApplication(consumer.getId()), getRateLimit(consumer.getId()));
   }
 
   private boolean isAllowCreateApplication(Long consumerId) {
     return isAllowCreateApplication(Collections.singletonList(consumerId)).get(0);
   }
 
+  private Integer getRateLimit(Long consumerId) {
+    return getRateLimit(Collections.singletonList(consumerId)).get(0);
+  }
+
   private List<Boolean> isAllowCreateApplication(List<Long> consumerIdList) {
     Role createAppRole = getCreateAppRole();
     if (createAppRole == null) {
@@ -252,6 +258,17 @@ private List<Boolean> isAllowCreateApplication(List<Long> consumerIdList) {
     return list;
   }
 
+  private List<Integer> getRateLimit(List<Long> consumerIdList) {
+    List<Integer> list = new ArrayList<>(consumerIdList.size());
+    for (Long consumerId : consumerIdList) {
+      ConsumerToken consumerToken = consumerTokenRepository.findByConsumerId(consumerId);
+      Integer rateLimit = consumerToken.getRateLimit();
+      list.add(rateLimit);
+    }
+
+    return list;
+  }
+
   private Role getCreateAppRole() {
     return rolePermissionService.findRoleByRoleName(CREATE_APPLICATION_ROLE_NAME);
   }
@@ -405,6 +422,7 @@ public List<ConsumerInfo> findConsumerInfoList(Pageable page) {
     List<Long> consumerIdList = consumerList.stream()
         .map(Consumer::getId).collect(Collectors.toList());
     List<Boolean> allowCreateApplicationList = isAllowCreateApplication(consumerIdList);
+    List<Integer> rateLimitList = getRateLimit(consumerIdList);
 
     List<ConsumerInfo> consumerInfoList = new ArrayList<>(consumerList.size());
 
@@ -412,7 +430,7 @@ public List<ConsumerInfo> findConsumerInfoList(Pageable page) {
       Consumer consumer = consumerList.get(i);
       // without token
       ConsumerInfo consumerInfo = convert(
-          consumer, null, allowCreateApplicationList.get(i)
+          consumer, null, allowCreateApplicationList.get(i), rateLimitList.get(i)
       );
       consumerInfoList.add(consumerInfo);
     }
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerInfo.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerInfo.java
index f6779da140c..cb6c7a0cf62 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerInfo.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/consumer/ConsumerInfo.java
@@ -33,6 +33,8 @@ public class ConsumerInfo {
   private String token;
   private boolean allowCreateApplication;
 
+  private Integer rateLimit;
+
   public String getAppId() {
     return appId;
   }
@@ -104,4 +106,13 @@ public boolean isAllowCreateApplication() {
   public void setAllowCreateApplication(boolean allowCreateApplication) {
     this.allowCreateApplication = allowCreateApplication;
   }
+
+  public Integer getRateLimit() {
+    return rateLimit;
+  }
+
+  public void setRateLimit(Integer rateLimit) {
+    this.rateLimit = rateLimit;
+  }
+
 }
diff --git a/apollo-portal/src/main/resources/static/open/manage.html b/apollo-portal/src/main/resources/static/open/manage.html
index e1545dfa6b1..aba7a3da8d7 100644
--- a/apollo-portal/src/main/resources/static/open/manage.html
+++ b/apollo-portal/src/main/resources/static/open/manage.html
@@ -62,7 +62,8 @@ <h5>{{'Open.Manage.CreateThirdApp' | translate }}
                                 <th style="width: 10%">{{'Common.AppId' | translate }}</th>
                                 <th style="width: 15%">{{'Common.AppName' | translate }}</th>
                                 <th style="width: 10%">{{'Open.Manage.Consumer.AllowCreateApplication' | translate }}</th>
-                                <th style="width: 20%">{{'Common.Department' | translate }}</th>
+                                <th style="width: 10%">{{'Open.Manage.Consumer.RateLimitValue' | translate }}</th>
+                                <th style="width: 15%">{{'Common.Department' | translate }}</th>
                                 <th style="width: 20%">{{'Common.AppOwner' | translate }}/{{'Common.Email' | translate }}</th>
                                 <th style="width: 20%">{{'Common.Operation' | translate}}</th>
                             </tr>
@@ -78,8 +79,9 @@ <h5>{{'Open.Manage.CreateThirdApp' | translate }}
                                         {{'Open.Manage.Consumer.AllowCreateApplication.No' | translate}}
                                     </div>
                                 </td>
+                                <td style="width: 10%">{{ consumer.rateLimit }}</td>
 
-                                <td style="width: 20%">{{ consumer.orgName + '(' + consumer.orgId + ')' }}</td>
+                                <td style="width: 15%">{{ consumer.orgName + '(' + consumer.orgId + ')' }}</td>
                                 <td style="width: 20%"><b>{{ consumer.ownerName }}</b>/{{ consumer.ownerEmail }}</td>
                                 <td style="width: 20%;">
                                     <button class="btn btn-default btn-md" ng-click="preGrantPermission(consumer)">
@@ -172,4 +174,4 @@ <h4>{{'Common.IsRootUser' | translate }}</h4>
     <script type="application/javascript" src="../scripts/controller/open/OpenManageController.js"></script>
 </body>
 
-</html>
\ No newline at end of file
+</html>
diff --git a/apollo-portal/src/main/resources/static/scripts/controller/open/OpenManageController.js b/apollo-portal/src/main/resources/static/scripts/controller/open/OpenManageController.js
index dc1c9efc7a6..b0a79cf3333 100644
--- a/apollo-portal/src/main/resources/static/scripts/controller/open/OpenManageController.js
+++ b/apollo-portal/src/main/resources/static/scripts/controller/open/OpenManageController.js
@@ -170,7 +170,7 @@ function OpenManageController($scope, $translate, toastr, AppUtil, OrganizationS
         }
 
         if ($scope.consumer.rateLimitEnabled) {
-            if ($scope.consumer.rateLimit < 1) {
+            if (!$scope.consumer.rateLimit || $scope.consumer.rateLimit < 1) {
                 toastr.warning($translate.instant('Open.Manage.Consumer.RateLimitValue.Error'));
                 $scope.submitBtnDisabled = false;
                 return;

From edb9d8b3bd9ecbb373c6db9e7f3cd3306ccad98b Mon Sep 17 00:00:00 2001
From: yangzl <youngzil@163.com>
Date: Sat, 23 Nov 2024 21:39:39 +0800
Subject: [PATCH 08/15] fix(portal): Optimized the robustness of the code

---
 .../ctrip/framework/apollo/openapi/service/ConsumerService.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
index fc44e76a69d..100d3de9c7b 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
@@ -262,7 +262,7 @@ private List<Integer> getRateLimit(List<Long> consumerIdList) {
     List<Integer> list = new ArrayList<>(consumerIdList.size());
     for (Long consumerId : consumerIdList) {
       ConsumerToken consumerToken = consumerTokenRepository.findByConsumerId(consumerId);
-      Integer rateLimit = consumerToken.getRateLimit();
+      Integer rateLimit = consumerToken != null ? consumerToken.getRateLimit() : 0;
       list.add(rateLimit);
     }
 

From 6011aebddc533c9a36b43c351e0bfe21be1e428b Mon Sep 17 00:00:00 2001
From: yangzl <youngzil@163.com>
Date: Sat, 23 Nov 2024 22:02:03 +0800
Subject: [PATCH 09/15] fix(portal): Optimized the robustness of the code

---
 CHANGES.md | 1 -
 1 file changed, 1 deletion(-)

diff --git a/CHANGES.md b/CHANGES.md
index cd091257667..1541bde5df6 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -22,6 +22,5 @@ Apollo 2.4.0
 * [Fix: ensure clusters order in envClusters open api](https://github.com/apolloconfig/apollo/pull/5277)
 * [Fix: bump xstream from 1.4.20 to 1.4.21 to fix CVE-2024-47072](https://github.com/apolloconfig/apollo/pull/5280)
 * [Feature: Added current limiting function to ConsumerToken](https://github.com/apolloconfig/apollo/pull/5267)
-
 ------------------
 All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1)

From 8463974a2624b6c277d63dcd9fd4674aa25fdd16 Mon Sep 17 00:00:00 2001
From: yangzl <youngzil@163.com>
Date: Sat, 23 Nov 2024 22:19:29 +0800
Subject: [PATCH 10/15] fix(portal): fix unit test

---
 .../framework/apollo/openapi/service/ConsumerServiceTest.java   | 2 ++
 1 file changed, 2 insertions(+)

diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/service/ConsumerServiceTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/service/ConsumerServiceTest.java
index f83b935d681..6cd4e784dea 100644
--- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/service/ConsumerServiceTest.java
+++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/openapi/service/ConsumerServiceTest.java
@@ -251,6 +251,7 @@ void notAllowCreateApplication() {
 
       ConsumerToken consumerToken = new ConsumerToken();
       consumerToken.setToken(token);
+      consumerToken.setRateLimit(0);
       when(consumerTokenRepository.findByConsumerId(eq(consumerId)))
           .thenReturn(consumerToken);
     }
@@ -276,6 +277,7 @@ void allowCreateApplication() {
 
       ConsumerToken consumerToken = new ConsumerToken();
       consumerToken.setToken(token);
+      consumerToken.setRateLimit(0);
       when(consumerTokenRepository.findByConsumerId(eq(consumerId)))
           .thenReturn(consumerToken);
     }

From 5390f623bf65e33f8a9a1a610ecb9a3701b28e0a Mon Sep 17 00:00:00 2001
From: yangzl <youngzil@163.com>
Date: Tue, 26 Nov 2024 20:16:06 +0800
Subject: [PATCH 11/15] feat(openapi): Added consumer rate limit query function
 and optimized related display

---
 .../openapi/repository/ConsumerTokenRepository.java      | 4 ++++
 .../apollo/openapi/service/ConsumerService.java          | 9 ++++-----
 apollo-portal/src/main/resources/static/i18n/en.json     | 1 +
 apollo-portal/src/main/resources/static/i18n/zh-CN.json  | 1 +
 apollo-portal/src/main/resources/static/open/manage.html | 5 +++--
 5 files changed, 13 insertions(+), 7 deletions(-)

diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/repository/ConsumerTokenRepository.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/repository/ConsumerTokenRepository.java
index 456dee7717b..8519149ad15 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/repository/ConsumerTokenRepository.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/repository/ConsumerTokenRepository.java
@@ -18,6 +18,7 @@
 
 import com.ctrip.framework.apollo.openapi.entity.ConsumerToken;
 
+import java.util.List;
 import org.springframework.data.repository.PagingAndSortingRepository;
 
 import java.util.Date;
@@ -35,4 +36,7 @@ public interface ConsumerTokenRepository extends PagingAndSortingRepository<Cons
   ConsumerToken findTopByTokenAndExpiresAfter(String token, Date validDate);
 
   ConsumerToken findByConsumerId(Long consumerId);
+
+  List<ConsumerToken> findByConsumerIdIn(List<Long> consumerIds);
+
 }
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
index 100d3de9c7b..cea61b02d88 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
@@ -258,14 +258,13 @@ private List<Boolean> isAllowCreateApplication(List<Long> consumerIdList) {
     return list;
   }
 
-  private List<Integer> getRateLimit(List<Long> consumerIdList) {
-    List<Integer> list = new ArrayList<>(consumerIdList.size());
-    for (Long consumerId : consumerIdList) {
-      ConsumerToken consumerToken = consumerTokenRepository.findByConsumerId(consumerId);
+  private List<Integer> getRateLimit(List<Long> consumerIds) {
+    List<Integer> list = new ArrayList<>(consumerIds.size());
+    List<ConsumerToken> consumerTokens = consumerTokenRepository.findByConsumerIdIn(consumerIds);
+    for (ConsumerToken consumerToken : consumerTokens) {
       Integer rateLimit = consumerToken != null ? consumerToken.getRateLimit() : 0;
       list.add(rateLimit);
     }
-
     return list;
   }
 
diff --git a/apollo-portal/src/main/resources/static/i18n/en.json b/apollo-portal/src/main/resources/static/i18n/en.json
index 078096069d9..6be0df9228f 100644
--- a/apollo-portal/src/main/resources/static/i18n/en.json
+++ b/apollo-portal/src/main/resources/static/i18n/en.json
@@ -660,6 +660,7 @@
   "Open.Manage.Consumer.RateLimitValue": "Rate limiting QPS",
   "Open.Manage.Consumer.RateLimitValueTips": "(Unit: times/second, for example: 100 means that the configuration is published at most 100 times per second)",
   "Open.Manage.Consumer.RateLimitValue.Error": "The minimum rate limiting QPS is 1",
+  "Open.Manage.Consumer.RateLimitValue.Display": "Unlimited",
   "Namespace.Role.Title": "Permission Management",
   "Namespace.Role.GrantModifyTo": "Permission to edit",
   "Namespace.Role.GrantModifyTo2": "(Can edit the configuration)",
diff --git a/apollo-portal/src/main/resources/static/i18n/zh-CN.json b/apollo-portal/src/main/resources/static/i18n/zh-CN.json
index ebe29df7ac7..d3706b41e9e 100644
--- a/apollo-portal/src/main/resources/static/i18n/zh-CN.json
+++ b/apollo-portal/src/main/resources/static/i18n/zh-CN.json
@@ -660,6 +660,7 @@
   "Open.Manage.Consumer.RateLimitValue": "限流QPS",
   "Open.Manage.Consumer.RateLimitValueTips": "(单位:次/秒,例如: 100 表示每秒最多发布 100 次配置)",
   "Open.Manage.Consumer.RateLimitValue.Error": "限流QPS最小为1",
+  "Open.Manage.Consumer.RateLimitValue.Display": "无限制",
   "Namespace.Role.Title": "权限管理",
   "Namespace.Role.GrantModifyTo": "修改权",
   "Namespace.Role.GrantModifyTo2": "(可以修改配置)",
diff --git a/apollo-portal/src/main/resources/static/open/manage.html b/apollo-portal/src/main/resources/static/open/manage.html
index aba7a3da8d7..dfe0db042fd 100644
--- a/apollo-portal/src/main/resources/static/open/manage.html
+++ b/apollo-portal/src/main/resources/static/open/manage.html
@@ -79,8 +79,9 @@ <h5>{{'Open.Manage.CreateThirdApp' | translate }}
                                         {{'Open.Manage.Consumer.AllowCreateApplication.No' | translate}}
                                     </div>
                                 </td>
-                                <td style="width: 10%">{{ consumer.rateLimit }}</td>
-
+                                <td style="width: 10%">
+                                    {{ consumer.rateLimit && consumer.rateLimit > 0 ? consumer.rateLimit : 'Open.Manage.Consumer.RateLimitValue.Display' | translate }}
+                                </td>
                                 <td style="width: 15%">{{ consumer.orgName + '(' + consumer.orgId + ')' }}</td>
                                 <td style="width: 20%"><b>{{ consumer.ownerName }}</b>/{{ consumer.ownerEmail }}</td>
                                 <td style="width: 20%;">

From 9126bb34681719daab758e0013fdba2bcc3b2c4c Mon Sep 17 00:00:00 2001
From: yangzl <youngzil@163.com>
Date: Wed, 27 Nov 2024 10:12:48 +0800
Subject: [PATCH 12/15] fix(portal): Fix the processing logic when the consumer
 obtains an empty quota

---
 .../framework/apollo/openapi/service/ConsumerService.java  | 7 ++++++-
 1 file changed, 6 insertions(+), 1 deletion(-)

diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
index cea61b02d88..9ab9e9d686d 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
@@ -56,6 +56,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.stream.Collectors;
+import org.springframework.util.CollectionUtils;
 
 /**
  * @author Jason Song(song_s@ctrip.com)
@@ -233,7 +234,11 @@ private boolean isAllowCreateApplication(Long consumerId) {
   }
 
   private Integer getRateLimit(Long consumerId) {
-    return getRateLimit(Collections.singletonList(consumerId)).get(0);
+    List<Integer> list = getRateLimit(Collections.singletonList(consumerId));
+    if(CollectionUtils.isEmpty(list)){
+      return 0;
+    }
+    return list.get(0);
   }
 
   private List<Boolean> isAllowCreateApplication(List<Long> consumerIdList) {

From ddfd8b38105c74900a0eb9766392f4509cb7ee75 Mon Sep 17 00:00:00 2001
From: yangzl <youngzil@163.com>
Date: Wed, 27 Nov 2024 10:29:18 +0800
Subject: [PATCH 13/15] refactor(ConsumerService): Optimize the implementation
 of getRateLimit method

---
 .../openapi/service/ConsumerService.java       | 18 +++++++++++-------
 1 file changed, 11 insertions(+), 7 deletions(-)

diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
index 9ab9e9d686d..e0676a18655 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/openapi/service/ConsumerService.java
@@ -44,6 +44,7 @@
 import com.google.common.hash.Hashing;
 import java.util.ArrayList;
 import java.util.Collections;
+import java.util.Map;
 import java.util.Objects;
 import org.apache.commons.lang3.time.FastDateFormat;
 import org.springframework.data.domain.Pageable;
@@ -235,7 +236,7 @@ private boolean isAllowCreateApplication(Long consumerId) {
 
   private Integer getRateLimit(Long consumerId) {
     List<Integer> list = getRateLimit(Collections.singletonList(consumerId));
-    if(CollectionUtils.isEmpty(list)){
+    if (CollectionUtils.isEmpty(list)) {
       return 0;
     }
     return list.get(0);
@@ -264,13 +265,16 @@ private List<Boolean> isAllowCreateApplication(List<Long> consumerIdList) {
   }
 
   private List<Integer> getRateLimit(List<Long> consumerIds) {
-    List<Integer> list = new ArrayList<>(consumerIds.size());
     List<ConsumerToken> consumerTokens = consumerTokenRepository.findByConsumerIdIn(consumerIds);
-    for (ConsumerToken consumerToken : consumerTokens) {
-      Integer rateLimit = consumerToken != null ? consumerToken.getRateLimit() : 0;
-      list.add(rateLimit);
-    }
-    return list;
+    Map<Long, Integer> consumerRateLimits = consumerTokens.stream()
+        .collect(Collectors.toMap(
+            ConsumerToken::getConsumerId,
+            consumerToken -> consumerToken.getRateLimit() != null ? consumerToken.getRateLimit() : 0
+        ));
+
+    return consumerIds.stream()
+        .map(id -> consumerRateLimits.getOrDefault(id, 0))
+        .collect(Collectors.toList());
   }
 
   private Role getCreateAppRole() {

From 46df20c05364f2a855d2a76e54c2a3676ce76dce Mon Sep 17 00:00:00 2001
From: Jason Song <nobodyiam@gmail.com>
Date: Thu, 28 Nov 2024 19:24:36 +0800
Subject: [PATCH 14/15] Update CHANGES.md

---
 CHANGES.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGES.md b/CHANGES.md
index 0310662660f..0ad2ab0bed9 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -22,7 +22,7 @@ Apollo 2.4.0
 * [Fix: ensure clusters order in envClusters open api](https://github.com/apolloconfig/apollo/pull/5277)
 * [Fix: bump xstream from 1.4.20 to 1.4.21 to fix CVE-2024-47072](https://github.com/apolloconfig/apollo/pull/5280)
 * [Feature: highlight diffs for properties](https://github.com/apolloconfig/apollo/pull/5282)
-
 * [Feature: Added current limiting function to ConsumerToken](https://github.com/apolloconfig/apollo/pull/5267)
+
 ------------------
 All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1)

From e07de197a928e43fe744daa075d491d05734fd70 Mon Sep 17 00:00:00 2001
From: Jason Song <nobodyiam@gmail.com>
Date: Thu, 28 Nov 2024 19:25:08 +0800
Subject: [PATCH 15/15] Update CHANGES.md

---
 CHANGES.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/CHANGES.md b/CHANGES.md
index 0ad2ab0bed9..d0296dbee35 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -22,7 +22,7 @@ Apollo 2.4.0
 * [Fix: ensure clusters order in envClusters open api](https://github.com/apolloconfig/apollo/pull/5277)
 * [Fix: bump xstream from 1.4.20 to 1.4.21 to fix CVE-2024-47072](https://github.com/apolloconfig/apollo/pull/5280)
 * [Feature: highlight diffs for properties](https://github.com/apolloconfig/apollo/pull/5282)
-* [Feature: Added current limiting function to ConsumerToken](https://github.com/apolloconfig/apollo/pull/5267)
+* [Feature: Add rate limiting function to ConsumerToken](https://github.com/apolloconfig/apollo/pull/5267)
 
 ------------------
 All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1)