diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index 38ce14daf9c..9c4c983eb62 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -37,11 +37,12 @@ jobs:
       with:
         java-version: ${{ matrix.jdk }}
     - name: Cache Maven packages
-      uses: actions/cache@v1
+      uses: actions/cache@v4
       with:
-        path: ~/.m2
-        key: ${{ runner.os }}-m2-${{ hashFiles('**/pom.xml') }}
-        restore-keys: ${{ runner.os }}-m2
+        path: ~/.m2/repository
+        key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
+        restore-keys: |
+          ${{ runner.os }}-maven-
     - name: JDK 8
       if: matrix.jdk == '8'
       run: mvn -B clean package jacoco:report -Dmaven.gitcommitid.skip=true
diff --git a/CHANGES.md b/CHANGES.md
index c584794bdc7..3591e1fd14f 100644
--- a/CHANGES.md
+++ b/CHANGES.md
@@ -22,6 +22,10 @@ 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: Add rate limiting function to ConsumerToken](https://github.com/apolloconfig/apollo/pull/5267)
+* [Feature: add JSON formatting function in apollo-portal](https://github.com/apolloconfig/apollo/pull/5287)
+* [Fix: add missing url patterns for AdminServiceAuthenticationFilter](https://github.com/apolloconfig/apollo/pull/5291)
+* [Fix: support java.time.Instant serialization with gson](https://github.com/apolloconfig/apollo/pull/5298)
 
 ------------------
 All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1)
diff --git a/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/AdminServiceAutoConfiguration.java b/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/AdminServiceAutoConfiguration.java
index 84453d6cf1d..1018de14824 100644
--- a/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/AdminServiceAutoConfiguration.java
+++ b/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/AdminServiceAutoConfiguration.java
@@ -36,12 +36,15 @@ public FilterRegistrationBean<AdminServiceAuthenticationFilter> adminServiceAuth
     FilterRegistrationBean<AdminServiceAuthenticationFilter> filterRegistrationBean = new FilterRegistrationBean<>();
 
     filterRegistrationBean.setFilter(new AdminServiceAuthenticationFilter(bizConfig));
+    filterRegistrationBean.addUrlPatterns("/apollo/audit/*");
     filterRegistrationBean.addUrlPatterns("/apps/*");
     filterRegistrationBean.addUrlPatterns("/appnamespaces/*");
     filterRegistrationBean.addUrlPatterns("/instances/*");
     filterRegistrationBean.addUrlPatterns("/items/*");
+    filterRegistrationBean.addUrlPatterns("/items-search/*");
     filterRegistrationBean.addUrlPatterns("/namespaces/*");
     filterRegistrationBean.addUrlPatterns("/releases/*");
+    filterRegistrationBean.addUrlPatterns("/server/*");
 
     return filterRegistrationBean;
   }
diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiJpaImpl.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiJpaImpl.java
index d8489c3b2bd..0ff85c3e300 100644
--- a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiJpaImpl.java
+++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiJpaImpl.java
@@ -95,7 +95,7 @@ public void appendDataInfluence(String entityName, String entityId, String field
   @Override
   public void appendDataInfluences(List<Object> entities, Class<?> beanDefinition) {
     String tableName = ApolloAuditUtil.getApolloAuditLogTableName(beanDefinition);
-    if (Objects.isNull(tableName) || tableName.equals("")) {
+    if (Objects.isNull(tableName) || Objects.equals(tableName, "")) {
       return;
     }
     List<Field> dataInfluenceFields = ApolloAuditUtil.getAnnotatedFields(
diff --git a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiNoOpImpl.java b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiNoOpImpl.java
index 6de5f0a018e..e12e780939b 100644
--- a/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiNoOpImpl.java
+++ b/apollo-audit/apollo-audit-impl/src/main/java/com/ctrip/framework/apollo/audit/component/ApolloAuditLogApiNoOpImpl.java
@@ -21,6 +21,7 @@
 import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDataInfluenceDTO;
 import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDetailsDTO;
 import com.ctrip.framework.apollo.audit.dto.ApolloAuditLogDTO;
+import java.util.Collections;
 import java.util.Date;
 import java.util.List;
 
@@ -50,28 +51,28 @@ public void appendDataInfluences(List<Object> entities, Class<?> beanDefinition)
 
   @Override
   public List<ApolloAuditLogDTO> queryLogs(int page, int size) {
-    return null;
+    return Collections.emptyList();
   }
 
   @Override
   public List<ApolloAuditLogDTO> queryLogsByOpName(String opName, Date startDate,
       Date endDate, int page, int size) {
-    return null;
+    return Collections.emptyList();
   }
 
   @Override
   public List<ApolloAuditLogDetailsDTO> queryTraceDetails(String traceId) {
-    return null;
+    return Collections.emptyList();
   }
 
   @Override
   public List<ApolloAuditLogDataInfluenceDTO> queryDataInfluencesByField(String entityName,
       String entityId, String fieldName, int page, int size) {
-    return null;
+    return Collections.emptyList();
   }
 
   @Override
   public List<ApolloAuditLogDTO> searchLogByNameOrTypeOrOperator(String query, int page, int size) {
-    return null;
+    return Collections.emptyList();
   }
 }
diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Audit.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Audit.java
index 4f2ac566b41..9ec2965d726 100644
--- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Audit.java
+++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Audit.java
@@ -79,6 +79,7 @@ public void setOpName(String opName) {
     this.opName = opName;
   }
 
+  @Override
   public String toString() {
     return toStringHelper().add("entityName", entityName).add("entityId", entityId)
         .add("opName", opName).add("comment", comment).toString();
diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Cluster.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Cluster.java
index 3bcb626b8f8..20573407ccc 100644
--- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Cluster.java
+++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Cluster.java
@@ -78,6 +78,7 @@ public void setComment(String comment) {
     this.comment = comment;
   }
 
+  @Override
   public String toString() {
     return toStringHelper().add("name", name).add("appId", appId)
         .add("parentClusterId", parentClusterId).add("comment", comment).toString();
diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Item.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Item.java
index f18218b1725..9c5a6802256 100644
--- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Item.java
+++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Item.java
@@ -99,6 +99,7 @@ public void setType(int type) {
     this.type = type;
   }
 
+  @Override
   public String toString() {
     return toStringHelper().add("namespaceId", namespaceId).add("key", key)
             .add("type", type).add("value", value)
diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Namespace.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Namespace.java
index bc966c0d66a..846803aeab0 100644
--- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Namespace.java
+++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Namespace.java
@@ -74,6 +74,7 @@ public void setNamespaceName(String namespaceName) {
     this.namespaceName = namespaceName;
   }
 
+  @Override
   public String toString() {
     return toStringHelper().add("appId", appId).add("clusterName", clusterName)
         .add("namespaceName", namespaceName).toString();
diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Privilege.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Privilege.java
index d86095f45e8..d3db68574ca 100644
--- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Privilege.java
+++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Privilege.java
@@ -64,6 +64,7 @@ public void setPrivilType(String privilType) {
     this.privilType = privilType;
   }
 
+  @Override
   public String toString() {
     return toStringHelper().add("namespaceId", namespaceId).add("privilType", privilType)
         .add("name", name).toString();
diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Release.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Release.java
index 2f33516e3e3..1484d0201f5 100644
--- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Release.java
+++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/Release.java
@@ -123,6 +123,7 @@ public void setAbandoned(boolean abandoned) {
     isAbandoned = abandoned;
   }
 
+  @Override
   public String toString() {
     return toStringHelper().add("name", name).add("appId", appId).add("clusterName", clusterName)
         .add("namespaceName", namespaceName).add("configurations", configurations)
diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/ReleaseHistory.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/ReleaseHistory.java
index d387f1cb0ec..e5583432d88 100644
--- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/ReleaseHistory.java
+++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/ReleaseHistory.java
@@ -121,6 +121,7 @@ public void setOperationContext(String operationContext) {
     this.operationContext = operationContext;
   }
 
+  @Override
   public String toString() {
     return toStringHelper().add("appId", appId).add("clusterName", clusterName)
         .add("namespaceName", namespaceName).add("branchName", branchName)
diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/ServerConfig.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/ServerConfig.java
index 8da6913022f..d7a55d53959 100644
--- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/ServerConfig.java
+++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/ServerConfig.java
@@ -77,7 +77,8 @@ public void setCluster(String cluster) {
     this.cluster = cluster;
   }
 
+  @Override
   public String toString() {
-    return toStringHelper().add("key", key).add("value", value).add("comment", comment).toString();
+    return toStringHelper().add("key", key).add("value", value).add("cluster", cluster).add("comment", comment).toString();
   }
 }
diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/registry/DatabaseServiceRegistryImpl.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/registry/DatabaseServiceRegistryImpl.java
index 454f779174e..269ee819cab 100644
--- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/registry/DatabaseServiceRegistryImpl.java
+++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/registry/DatabaseServiceRegistryImpl.java
@@ -37,11 +37,13 @@ static ServiceRegistry convert(ServiceInstance instance) {
     return serviceRegistry;
   }
 
+  @Override
   public void register(ServiceInstance instance) {
     ServiceRegistry serviceRegistry = convert(instance);
     this.serviceRegistryService.saveIfNotExistByServiceNameAndUri(serviceRegistry);
   }
 
+  @Override
   public void deregister(ServiceInstance instance) {
     ServiceRegistry serviceRegistry = convert(instance);
     this.serviceRegistryService.delete(serviceRegistry);
diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/registry/configuration/support/ApolloServiceRegistryProperties.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/registry/configuration/support/ApolloServiceRegistryProperties.java
index 10e285e238f..9238492f1c6 100644
--- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/registry/configuration/support/ApolloServiceRegistryProperties.java
+++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/registry/configuration/support/ApolloServiceRegistryProperties.java
@@ -97,6 +97,7 @@ public void setEnabled(boolean enabled) {
     this.enabled = enabled;
   }
 
+  @Override
   public String getServiceName() {
     return serviceName;
   }
diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/utils/ReleaseMessageKeyGenerator.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/utils/ReleaseMessageKeyGenerator.java
index b3f7cc22ca6..640ad4ee2e2 100644
--- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/utils/ReleaseMessageKeyGenerator.java
+++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/utils/ReleaseMessageKeyGenerator.java
@@ -21,6 +21,7 @@
 
 import com.ctrip.framework.apollo.core.ConfigConsts;
 import com.google.common.base.Splitter;
+import java.util.Collections;
 import java.util.List;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -42,7 +43,7 @@ public static List<String> messageToList(String message) {
     //message should be appId+cluster+namespace
     if (keys.size() != 3) {
       logger.error("message format invalid - {}", message);
-      return null;
+      return Collections.emptyList();
     }
     return keys;
   }
diff --git a/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/utils/ReleaseKeyGeneratorTest.java b/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/utils/ReleaseKeyGeneratorTest.java
index a36a75a2013..a074623e7f6 100644
--- a/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/utils/ReleaseKeyGeneratorTest.java
+++ b/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/utils/ReleaseKeyGeneratorTest.java
@@ -23,8 +23,6 @@
 
 import java.util.List;
 import org.junit.Test;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
 
 import java.util.Set;
 import java.util.concurrent.CountDownLatch;
@@ -33,7 +31,6 @@
 import java.util.concurrent.TimeUnit;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
 
 /**
  * @author Jason Song(song_s@ctrip.com)
@@ -80,7 +77,8 @@ public void testMessageToList() {
 
     message = "appId+cluster";
     keys = ReleaseMessageKeyGenerator.messageToList(message);
-    assertNull(keys);
+    assert keys != null;
+    assertEquals(0, keys.size());
   }
 
   private Runnable generateReleaseKeysTask(Namespace namespace, Set<String> releaseKeys,
diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/controller/HttpMessageConverterConfiguration.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/controller/HttpMessageConverterConfiguration.java
index 87ef82e11bc..5b2b5136bda 100644
--- a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/controller/HttpMessageConverterConfiguration.java
+++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/controller/HttpMessageConverterConfiguration.java
@@ -19,6 +19,11 @@
 import com.google.common.collect.Lists;
 import com.google.gson.GsonBuilder;
 
+import com.google.gson.JsonDeserializer;
+import com.google.gson.JsonNull;
+import com.google.gson.JsonPrimitive;
+import com.google.gson.JsonSerializer;
+import java.time.Instant;
 import org.springframework.boot.autoconfigure.http.HttpMessageConverters;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
@@ -37,9 +42,23 @@
 public class HttpMessageConverterConfiguration {
   @Bean
   public HttpMessageConverters messageConverters() {
+    // Custom Gson TypeAdapter for Instant
+    JsonSerializer<Instant> instantJsonSerializer = (src, typeOfSrc, context) ->
+        src == null ? JsonNull.INSTANCE : new JsonPrimitive(src.toString()); // Serialize Instant as ISO-8601 string
+
+    JsonDeserializer<Instant> instantJsonDeserializer = (json, typeOfT, context) -> {
+      if (json == null || json.isJsonNull()) {
+        return null;
+      }
+      return Instant.parse(json.getAsString()); // Deserialize from ISO-8601 string
+    };
+
     GsonHttpMessageConverter gsonHttpMessageConverter = new GsonHttpMessageConverter();
     gsonHttpMessageConverter.setGson(
-            new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ").create());
+            new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
+                .registerTypeAdapter(Instant.class, instantJsonSerializer)
+                .registerTypeAdapter(Instant.class, instantJsonDeserializer)
+                .create());
     final List<HttpMessageConverter<?>> converters = Lists.newArrayList(
             new ByteArrayHttpMessageConverter(), new StringHttpMessageConverter(),
             new AllEncompassingFormHttpMessageConverter(), gsonHttpMessageConverter);
diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/datasource/ApolloDataSourceScriptDatabaseInitializerFactory.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/datasource/ApolloDataSourceScriptDatabaseInitializerFactory.java
index 74106334df7..ad3a1b8cbef 100644
--- a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/datasource/ApolloDataSourceScriptDatabaseInitializerFactory.java
+++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/datasource/ApolloDataSourceScriptDatabaseInitializerFactory.java
@@ -20,6 +20,7 @@
 import java.security.CodeSource;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import javax.sql.DataSource;
 import org.springframework.boot.jdbc.DataSourceBuilder;
@@ -101,7 +102,7 @@ private static List<String> resolveLocations(Collection<String> locations,
   private static Collection<String> convertRepositoryLocations(Collection<String> locations,
       DataSource dataSource) {
     if (CollectionUtils.isEmpty(locations)) {
-      return null;
+      return Collections.emptyList();
     }
     String repositoryDir = findRepositoryDirectory();
     String suffix = findSuffix(dataSource);
diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/App.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/App.java
index cef034aac5e..d487ef2f8f3 100644
--- a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/App.java
+++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/App.java
@@ -111,6 +111,7 @@ public void setOwnerName(String ownerName) {
     this.ownerName = ownerName;
   }
 
+  @Override
   public String toString() {
     return toStringHelper().add("name", name).add("appId", appId)
         .add("orgId", orgId)
diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/AppNamespace.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/AppNamespace.java
index 251c3737b90..b85edebeecc 100644
--- a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/AppNamespace.java
+++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/AppNamespace.java
@@ -107,6 +107,7 @@ public void setFormat(String format) {
     this.format = format;
   }
 
+  @Override
   public String toString() {
     return toStringHelper().add("name", name).add("appId", appId).add("comment", comment)
         .add("format", format).add("isPublic", isPublic).toString();
diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/BaseEntity.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/BaseEntity.java
index 75b3ba460a3..9c8ecef3b35 100644
--- a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/BaseEntity.java
+++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/entity/BaseEntity.java
@@ -149,6 +149,7 @@ protected ToStringHelper toStringHelper() {
         .add("dataChangeLastModifiedTime", dataChangeLastModifiedTime);
   }
 
+  @Override
   public String toString(){
     return toStringHelper().toString();
   }
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..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
@@ -37,6 +37,10 @@ public static BadRequestException orgIdIsBlank() {
     return new BadRequestException("orgId can not be blank");
   }
 
+  public static BadRequestException rateLimitIsInvalid() {
+    return new BadRequestException("rate limit 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 baa1c0dc7c7..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,6 +42,10 @@ public class ConsumerToken extends BaseEntity {
   @Column(name = "`Token`", nullable = false)
   private String token;
 
+  @PositiveOrZero
+  @Column(name = "`RateLimit`", nullable = false)
+  private Integer rateLimit;
+
   @Column(name = "`Expires`", nullable = false)
   private Date expires;
 
@@ -60,6 +65,14 @@ public void setToken(String token) {
     this.token = token;
   }
 
+  public Integer getRateLimit() {
+    return rateLimit;
+  }
+
+  public void setRateLimit(Integer rateLimit) {
+    this.rateLimit = rateLimit;
+  }
+
   public Date getExpires() {
     return expires;
   }
@@ -71,6 +84,7 @@ public void setExpires(Date expires) {
   @Override
   public String toString() {
     return toStringHelper().add("consumerId", consumerId).add("token", token)
+        .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 c08de6dd505..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
@@ -16,11 +16,15 @@
  */
 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.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;
@@ -29,15 +33,30 @@
 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 static final int WARMUP_MILLIS = 1000; // ms
+  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()
+      .expireAfterAccess(1, TimeUnit.HOURS)
+      .maximumSize(RATE_LIMITER_CACHE_MAX_SIZE).build();
+
   public ConsumerAuthenticationFilter(ConsumerAuthUtil consumerAuthUtil, ConsumerAuditUtil consumerAuditUtil) {
     this.consumerAuthUtil = consumerAuthUtil;
     this.consumerAuditUtil = consumerAuditUtil;
@@ -55,14 +74,30 @@ 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) {
       response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized");
       return;
     }
 
+    Integer rateLimit = consumerToken.getRateLimit();
+    if (null != rateLimit && rateLimit > 0) {
+      try {
+        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");
+          return;
+        }
+      } catch (Exception e) {
+        logger.error("ConsumerAuthenticationFilter ratelimit error", e);
+        response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Rate limiting failed");
+        return;
+      }
+    }
+
+    long consumerId = consumerToken.getConsumerId();
     consumerAuthUtil.storeConsumerId(request, consumerId);
     consumerAuditUtil.audit(request, consumerId);
 
@@ -73,4 +108,14 @@ public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain
   public void destroy() {
     //nothing
   }
+
+  private ImmutablePair<Long, RateLimiter> getOrCreateRateLimiterPair(String key, Integer limitCount) {
+    try {
+      return LIMITER.get(key, () ->
+          ImmutablePair.of(System.currentTimeMillis(), RateLimiter.create(limitCount)));
+    } catch (ExecutionException e) {
+      throw new RuntimeException("Failed to create rate limiter", e);
+    }
+  }
+
 }
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 90164feea7f..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;
@@ -56,6 +57,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)
@@ -120,10 +122,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);
@@ -138,12 +140,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();
   }
 
@@ -195,7 +200,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());
@@ -205,6 +211,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);
@@ -220,13 +227,21 @@ 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) {
+    List<Integer> list = getRateLimit(Collections.singletonList(consumerId));
+    if (CollectionUtils.isEmpty(list)) {
+      return 0;
+    }
+    return list.get(0);
+  }
+
   private List<Boolean> isAllowCreateApplication(List<Long> consumerIdList) {
     Role createAppRole = getCreateAppRole();
     if (createAppRole == null) {
@@ -249,6 +264,19 @@ private List<Boolean> isAllowCreateApplication(List<Long> consumerIdList) {
     return list;
   }
 
+  private List<Integer> getRateLimit(List<Long> consumerIds) {
+    List<ConsumerToken> consumerTokens = consumerTokenRepository.findByConsumerIdIn(consumerIds);
+    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() {
     return rolePermissionService.findRoleByRoleName(CREATE_APPLICATION_ROLE_NAME);
   }
@@ -311,17 +339,21 @@ public void createConsumerAudits(Iterable<ConsumerAudit> consumerAudits) {
   @Transactional
   public ConsumerToken createConsumerToken(ConsumerToken entity) {
     entity.setId(0); //for protection
-
     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();
 
+    if (rateLimit == null || rateLimit < 0) {
+      rateLimit = 0;
+    }
+
     ConsumerToken consumerToken = new ConsumerToken();
     consumerToken.setConsumerId(consumerId);
+    consumerToken.setRateLimit(rateLimit);
     consumerToken.setExpires(expires);
     consumerToken.setDataChangeCreatedBy(createdBy);
     consumerToken.setDataChangeCreatedTime(createdTime);
@@ -350,7 +382,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);
@@ -389,7 +421,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();
   }
 
@@ -398,6 +430,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());
 
@@ -405,7 +438,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);
     }
@@ -414,7 +447,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/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/PortalSettings.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/PortalSettings.java
index 230be5275a6..e8725c50462 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/PortalSettings.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/PortalSettings.java
@@ -117,6 +117,7 @@ public HealthCheckTask(ApplicationContext context) {
       }
     }
 
+    @Override
     public void run() {
 
       for (Env env : allEnvs) {
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/RestTemplateFactory.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/RestTemplateFactory.java
index 85611f74452..a99ac98a718 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/RestTemplateFactory.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/RestTemplateFactory.java
@@ -47,18 +47,22 @@ public RestTemplateFactory(final HttpMessageConverters httpMessageConverters,
     this.apolloAuditHttpInterceptor = apolloAuditHttpInterceptor;
   }
 
+  @Override
   public RestTemplate getObject() {
     return restTemplate;
   }
 
+  @Override
   public Class<RestTemplate> getObjectType() {
     return RestTemplate.class;
   }
 
+  @Override
   public boolean isSingleton() {
     return true;
   }
 
+  @Override
   public void afterPropertiesSet() throws UnsupportedEncodingException {
 
     PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
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..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,13 +81,21 @@ public ConsumerInfo create(
       throw BadRequestException.orgIdIsBlank();
     }
 
+    if (requestVO.isRateLimitEnabled()) {
+      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/controller/SystemInfoController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/SystemInfoController.java
index dc30c21c561..a58c26e4359 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/SystemInfoController.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/SystemInfoController.java
@@ -25,6 +25,7 @@
 import com.ctrip.framework.apollo.portal.environment.Env;
 import com.ctrip.framework.apollo.portal.environment.PortalMetaDomainService;
 import java.util.List;
+import java.util.Objects;
 import javax.annotation.PostConstruct;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -145,6 +146,6 @@ private ServiceDTO[] getServerAddress(String metaServerAddress, String path) {
   }
 
   private boolean isValidVersion(String version) {
-    return !version.equals("java-null");
+    return !Objects.equals(version, "java-null");
   }
 }
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/po/ServerConfig.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/po/ServerConfig.java
index 13bf161105d..1e78e29672d 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/po/ServerConfig.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/po/ServerConfig.java
@@ -74,6 +74,7 @@ public void setComment(String comment) {
     this.comment = comment;
   }
 
+  @Override
   public String toString() {
     return toStringHelper().add("key", key).add("value", value).add("comment", comment).toString();
   }
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..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,6 +26,8 @@ public class ConsumerCreateRequestVO {
   private String orgId;
   private String orgName;
   private String ownerName;
+  private boolean rateLimitEnabled;
+  private int rateLimit;
 
   public String getAppId() {
     return appId;
@@ -75,4 +77,20 @@ public void setOwnerName(String ownerName) {
     this.ownerName = ownerName;
   }
 
+  public boolean isRateLimitEnabled() {
+    return rateLimitEnabled;
+  }
+
+  public void setRateLimitEnabled(boolean rateLimitEnabled) {
+    this.rateLimitEnabled = rateLimitEnabled;
+  }
+
+  public int getRateLimit() {
+    return rateLimit;
+  }
+
+  public void setRateLimit(int rateLimit) {
+    this.rateLimit = rateLimit;
+  }
+
 }
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/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..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
@@ -28,8 +28,8 @@ public class AuthFilterConfiguration {
 
   @Bean
   public FilterRegistrationBean<ConsumerAuthenticationFilter> openApiAuthenticationFilter(
-          ConsumerAuthUtil consumerAuthUtil,
-          ConsumerAuditUtil consumerAuditUtil) {
+      ConsumerAuthUtil consumerAuthUtil,
+      ConsumerAuditUtil consumerAuditUtil) {
 
     FilterRegistrationBean<ConsumerAuthenticationFilter> openApiFilter = new FilterRegistrationBean<>();
 
@@ -39,5 +39,4 @@ public FilterRegistrationBean<ConsumerAuthenticationFilter> openApiAuthenticatio
     return openApiFilter;
   }
 
-
 }
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultRoleInitializationService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultRoleInitializationService.java
index 3c18b9dc0bd..b54d20b50ab 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultRoleInitializationService.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultRoleInitializationService.java
@@ -57,6 +57,7 @@ public DefaultRoleInitializationService(final RolePermissionService rolePermissi
   }
 
   @Transactional
+  @Override
   public void initAppRoles(App app) {
     String appId = app.getAppId();
 
@@ -91,6 +92,7 @@ public void initAppRoles(App app) {
   }
 
   @Transactional
+  @Override
   public void initNamespaceRoles(String appId, String namespaceName, String operator) {
 
     String modifyNamespaceRoleName = RoleUtils.buildModifyNamespaceRoleName(appId, namespaceName);
@@ -107,6 +109,7 @@ public void initNamespaceRoles(String appId, String namespaceName, String operat
   }
 
   @Transactional
+  @Override
   public void initNamespaceEnvRoles(String appId, String namespaceName, String operator) {
     List<Env> portalEnvs = portalConfig.portalSupportedEnvs();
 
@@ -116,6 +119,7 @@ public void initNamespaceEnvRoles(String appId, String namespaceName, String ope
   }
 
   @Transactional
+  @Override
   public void initNamespaceSpecificEnvRoles(String appId, String namespaceName, String env, String operator) {
     String modifyNamespaceEnvRoleName = RoleUtils.buildModifyNamespaceRoleName(appId, namespaceName, env);
     if (rolePermissionService.findRoleByRoleName(modifyNamespaceEnvRoleName) == null) {
@@ -131,6 +135,7 @@ public void initNamespaceSpecificEnvRoles(String appId, String namespaceName, St
   }
 
   @Transactional
+  @Override
   public void initCreateAppRole() {
     if (rolePermissionService.findRoleByRoleName(SystemRoleManagerService.CREATE_APPLICATION_ROLE_NAME) != null) {
       return;
@@ -158,6 +163,7 @@ public void createManageAppMasterRole(String appId, String operator) {
 
   // fix historical data
   @Transactional
+  @Override
   public void initManageAppMasterRole(String appId, String operator) {
     String manageAppMasterRoleName = RoleUtils.buildAppRoleName(appId, PermissionType.MANAGE_APP_MASTER);
     if (rolePermissionService.findRoleByRoleName(manageAppMasterRoleName) != null) {
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultRolePermissionService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultRolePermissionService.java
index fc10aacfaa8..dfa057ef36e 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultRolePermissionService.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultRolePermissionService.java
@@ -86,6 +86,7 @@ public DefaultRolePermissionService(final RoleRepository roleRepository,
      * Create role with permissions, note that role name should be unique
      */
     @Transactional
+    @Override
     public Role createRoleWithPermissions(Role role, Set<Long> permissionIds) {
         Role current = findRoleByRoleName(role.getRoleName());
         Preconditions.checkState(current == null, "Role %s already exists!", role.getRoleName());
@@ -114,6 +115,7 @@ public Role createRoleWithPermissions(Role role, Set<Long> permissionIds) {
      */
     @Transactional
     @ApolloAuditLog(type = OpType.CREATE, name = "Auth.assignRoleToUsers")
+    @Override
     public Set<String> assignRoleToUsers(String roleName, Set<String> userIds,
                                          String operatorUserId) {
         Role role = findRoleByRoleName(roleName);
@@ -144,6 +146,7 @@ public Set<String> assignRoleToUsers(String roleName, Set<String> userIds,
      */
     @Transactional
     @ApolloAuditLog(type = OpType.DELETE, name = "Auth.removeRoleFromUsers")
+    @Override
     public void removeRoleFromUsers(
         @ApolloAuditLogDataInfluence
         @ApolloAuditLogDataInfluenceTable(tableName = "UserRole")
@@ -169,6 +172,7 @@ public void removeRoleFromUsers(
     /**
      * Query users with role
      */
+    @Override
     public Set<UserInfo> queryUsersWithRole(String roleName) {
         Role role = findRoleByRoleName(roleName);
 
@@ -179,7 +183,7 @@ public Set<UserInfo> queryUsersWithRole(String roleName) {
         List<UserRole> userRoles = userRoleRepository.findByRoleId(role.getId());
         List<UserInfo> userInfos = userService.findByUserIds(userRoles.stream().map(UserRole::getUserId).collect(Collectors.toList()));
 
-        if(userInfos == null){
+        if (CollectionUtils.isEmpty(userInfos)) {
             return Collections.emptySet();
         }
 
@@ -191,6 +195,7 @@ public Set<UserInfo> queryUsersWithRole(String roleName) {
     /**
      * Find role by role name, note that roleName should be unique
      */
+    @Override
     public Role findRoleByRoleName(String roleName) {
         return roleRepository.findTopByRoleName(roleName);
     }
@@ -198,6 +203,7 @@ public Role findRoleByRoleName(String roleName) {
     /**
      * Check whether user has the permission
      */
+    @Override
     public boolean userHasPermission(String userId, String permissionType, String targetId) {
         Permission permission =
                 permissionRepository.findTopByPermissionTypeAndTargetId(permissionType, targetId);
@@ -242,6 +248,7 @@ public List<Role> findUserRoles(String userId) {
         return Lists.newLinkedList(roleRepository.findAllById(roleIds));
     }
 
+    @Override
     public boolean isSuperAdmin(String userId) {
         return portalConfig.superAdmins().contains(userId);
     }
@@ -250,6 +257,7 @@ public boolean isSuperAdmin(String userId) {
      * Create permission, note that permissionType + targetId should be unique
      */
     @Transactional
+    @Override
     public Permission createPermission(Permission permission) {
         String permissionType = permission.getPermissionType();
         String targetId = permission.getTargetId();
@@ -265,6 +273,7 @@ public Permission createPermission(Permission permission) {
      * Create permissions, note that permissionType + targetId should be unique
      */
     @Transactional
+    @Override
     public Set<Permission> createPermissions(Set<Permission> permissions) {
         Multimap<String, String> targetIdPermissionTypes = HashMultimap.create();
         for (Permission permission : permissions) {
diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultUserService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultUserService.java
index 47bcb7a2a18..b5b95374e8b 100644
--- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultUserService.java
+++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/spi/defaultimpl/DefaultUserService.java
@@ -23,12 +23,16 @@
 
 import java.util.Collections;
 import java.util.List;
+import java.util.Objects;
+import org.springframework.util.CollectionUtils;
 
 /**
  * @author Jason Song(song_s@ctrip.com)
  */
 public class DefaultUserService implements UserService {
 
+  private static final String DEFAULT_USER_ID = "apollo";
+
   @Override
   public List<UserInfo> searchUsers(String keyword, int offset, int limit, boolean includeInactiveUsers) {
     return Collections.singletonList(assembleDefaultUser());
@@ -36,7 +40,7 @@ public List<UserInfo> searchUsers(String keyword, int offset, int limit, boolean
 
   @Override
   public UserInfo findByUserId(String userId) {
-    if (userId.equals("apollo")) {
+    if (Objects.equals(userId, DEFAULT_USER_ID)) {
       return assembleDefaultUser();
     }
     return null;
@@ -44,16 +48,20 @@ public UserInfo findByUserId(String userId) {
 
   @Override
   public List<UserInfo> findByUserIds(List<String> userIds) {
-    if (userIds.contains("apollo")) {
+    if (CollectionUtils.isEmpty(userIds)) {
+      return Collections.emptyList();
+    }
+
+    if (userIds.contains(DEFAULT_USER_ID)) {
       return Lists.newArrayList(assembleDefaultUser());
     }
-    return null;
+    return Collections.emptyList();
   }
 
   private UserInfo assembleDefaultUser() {
     UserInfo defaultUser = new UserInfo();
-    defaultUser.setUserId("apollo");
-    defaultUser.setName("apollo");
+    defaultUser.setUserId(DEFAULT_USER_ID);
+    defaultUser.setName(DEFAULT_USER_ID);
     defaultUser.setEmail("apollo@acme.com");
 
     return defaultUser;
diff --git a/apollo-portal/src/main/resources/static/i18n/en.json b/apollo-portal/src/main/resources/static/i18n/en.json
index 7cab764e0c6..f1dfca0b78a 100644
--- a/apollo-portal/src/main/resources/static/i18n/en.json
+++ b/apollo-portal/src/main/resources/static/i18n/en.json
@@ -72,6 +72,7 @@
   "Component.ConfigItem.ItemValueTips": "Note: Special characters (Spaces, Newline, Tab, Chinese comma) easily cause configuration errors. If you want to check special characters in Value, please click",
   "Component.ConfigItem.ItemValueShowDetection": "Check Special Characters",
   "Component.ConfigItem.ItemValueNotHiddenChars": "No Special Characters",
+  "Component.ConfigItem.FormatItemValue": "Format Content",
   "Component.ConfigItem.ItemComment": "Comment",
   "Component.ConfigItem.ChooseCluster": "Select Cluster",
   "Component.ConfigItem.ItemTypeName": "Type",
@@ -655,6 +656,12 @@
   "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 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": "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 8c00fee807f..d6fbd3ce87b 100644
--- a/apollo-portal/src/main/resources/static/i18n/zh-CN.json
+++ b/apollo-portal/src/main/resources/static/i18n/zh-CN.json
@@ -72,6 +72,7 @@
   "Component.ConfigItem.ItemValueTips": "注意: 特殊字符(空格、换行符、制表符Tab、中文逗号)容易导致配置出错,如果需要检测 Value 中特殊字符,请点击",
   "Component.ConfigItem.ItemValueShowDetection": "检测特殊字符",
   "Component.ConfigItem.ItemValueNotHiddenChars": "无特殊字符",
+  "Component.ConfigItem.FormatItemValue": "格式化",
   "Component.ConfigItem.ItemComment": "Comment",
   "Component.ConfigItem.ChooseCluster": "选择集群",
   "Component.ConfigItem.ItemTypeName": "类型",
@@ -655,6 +656,12 @@
   "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",
+  "Open.Manage.Consumer.RateLimitValue.Display": "无限制",
   "Namespace.Role.Title": "权限管理",
   "Namespace.Role.GrantModifyTo": "修改权",
   "Namespace.Role.GrantModifyTo2": "(可以修改配置)",
diff --git a/apollo-portal/src/main/resources/static/img/brush.png b/apollo-portal/src/main/resources/static/img/brush.png
new file mode 100644
index 00000000000..e40cab82ded
Binary files /dev/null and b/apollo-portal/src/main/resources/static/img/brush.png differ
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..94c490c2b35 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.rateLimitEnabled"
+                                       name="rateLimitEnabled"
+                                       ng-change="toggleRateLimitEnabledInput()"
+                                />
+                                <small>{{ 'Open.Manage.Consumer.RateLimit.Enabled.Tips' | translate }}</small>
+                            </div>
+                        </div>
+
+                        <div class="form-group" ng-show="consumer.rateLimitEnabled">
+                            <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/open/manage.html b/apollo-portal/src/main/resources/static/open/manage.html
index e1545dfa6b1..dfe0db042fd 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,10 @@ <h5>{{'Open.Manage.CreateThirdApp' | translate }}
                                         {{'Open.Manage.Consumer.AllowCreateApplication.No' | translate}}
                                     </div>
                                 </td>
-
-                                <td style="width: 20%">{{ consumer.orgName + '(' + consumer.orgId + ')' }}</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%;">
                                     <button class="btn btn-default btn-md" ng-click="preGrantPermission(consumer)">
@@ -172,4 +175,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..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
@@ -41,6 +41,11 @@ function OpenManageController($scope, $translate, toastr, AppUtil, OrganizationS
     $scope.preDeleteConsumer = preDeleteConsumer;
     $scope.deleteConsumer = deleteConsumer;
     $scope.preGrantPermission = preGrantPermission;
+    $scope.toggleRateLimitEnabledInput = function() {
+        if (!$scope.consumer.rateLimitEnabled) {
+            $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.rateLimitEnabled) {
+            if (!$scope.consumer.rateLimit || $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/main/resources/static/scripts/directive/item-modal-directive.js b/apollo-portal/src/main/resources/static/scripts/directive/item-modal-directive.js
index 8176acce542..a7752ec301c 100644
--- a/apollo-portal/src/main/resources/static/scripts/directive/item-modal-directive.js
+++ b/apollo-portal/src/main/resources/static/scripts/directive/item-modal-directive.js
@@ -41,6 +41,7 @@ function itemModalDirective($translate, toastr, $sce, AppUtil, EventManager, Con
             scope.showHiddenChars = showHiddenChars;
             scope.changeType = changeType;
             scope.validateItemValue = validateItemValue;
+            scope.formatContent = formatContent;
 
             $('#itemModal').on('show.bs.modal', function (e) {
                 scope.showHiddenCharsContext = false;
@@ -243,6 +244,17 @@ function itemModalDirective($translate, toastr, $sce, AppUtil, EventManager, Con
                 }
 
             }
+
+            // 格式化
+            function formatContent() {
+                if (scope.showJsonError) {
+                    return;
+                }
+                var raw = scope.item.value;
+                if (scope.item.type === '3') {
+                    scope.item.value = JSON.stringify(JSON.parse(raw), null, 4);
+                }
+            }
         }
     }
 }
diff --git a/apollo-portal/src/main/resources/static/scripts/directive/namespace-panel-directive.js b/apollo-portal/src/main/resources/static/scripts/directive/namespace-panel-directive.js
index ce47f7f681a..ea811ce34a7 100644
--- a/apollo-portal/src/main/resources/static/scripts/directive/namespace-panel-directive.js
+++ b/apollo-portal/src/main/resources/static/scripts/directive/namespace-panel-directive.js
@@ -86,6 +86,7 @@ function directive($window, $translate, toastr, AppUtil, EventManager, Permissio
             scope.mergeAndPublish = mergeAndPublish;
             scope.addRuleItem = addRuleItem;
             scope.editRuleItem = editRuleItem;
+            scope.formatContent = formatContent;
 
             scope.deleteNamespace = deleteNamespace;
             scope.exportNamespace = exportNamespace;
@@ -734,6 +735,17 @@ function directive($window, $translate, toastr, AppUtil, EventManager, Permissio
                 }
             }
 
+            // 格式化
+            function formatContent(namespace) {
+                try {
+                    if (namespace.format === 'json') {
+                        namespace.editText = JSON.stringify(JSON.parse(namespace.editText), null, 4);
+                    }
+                } catch (e) {
+                    toastr.error('format content failed: ' + e.message);
+                }
+            }
+
             function goToSyncPage(namespace) {
                 if (!scope.lockCheck(namespace)) {
                     return false;
diff --git a/apollo-portal/src/main/resources/static/views/component/item-modal.html b/apollo-portal/src/main/resources/static/views/component/item-modal.html
index 1ed8b0ee7ef..b5d210a75f3 100644
--- a/apollo-portal/src/main/resources/static/views/component/item-modal.html
+++ b/apollo-portal/src/main/resources/static/views/component/item-modal.html
@@ -65,6 +65,9 @@ <h4 class="modal-title">
                         {{'Component.ConfigItem.ItemValueTips' | translate }} <a
                             ng-click="showHiddenChars()">{{'Component.ConfigItem.ItemValueShowDetection' | translate }}</a>
                         <br>
+                        <!-- 格式化 -->
+                        <a ng-show="item.type == '3'" ng-click="formatContent()">{{'Component.ConfigItem.FormatItemValue' | translate }}</a>
+                        <br>
                         <div ng-show="showNumberError" ng-model="showNumberError" style="color:red">
                             {{'Component.ConfigItem.ItemNumberError' | translate }}
                         </div>
diff --git a/apollo-portal/src/main/resources/static/views/component/namespace-panel-master-tab.html b/apollo-portal/src/main/resources/static/views/component/namespace-panel-master-tab.html
index c93bd8f90a4..e028c9ed02e 100644
--- a/apollo-portal/src/main/resources/static/views/component/namespace-panel-master-tab.html
+++ b/apollo-portal/src/main/resources/static/views/component/namespace-panel-master-tab.html
@@ -190,6 +190,13 @@
                         ng-show="namespace.isTextEditing && namespace.viewType == 'text' && namespace.isSyntaxCheckable"
                         ng-click="syntaxCheck(namespace)">
                     &nbsp;
+                    <!-- 格式化按钮 -->
+                    <img src="img/brush.png" class="ns_btn cursor-pointer" data-tooltip="tooltip"
+                         data-placement="bottom"
+                         title="{{'Component.ConfigItem.FormatItemValue' | translate }}"
+                         ng-show="namespace.isTextEditing && namespace.viewType == 'text' && namespace.format == 'json'"
+                         ng-click="formatContent(namespace)">
+                    &nbsp;
                     <img src="img/cancel.png" class="ns_btn cursor-pointer" data-tooltip="tooltip"
                         data-placement="bottom"
                         title="{{'Component.Namespace.Master.Items.CancelChanged' | translate }}"
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..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
@@ -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,12 @@ 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.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 876562849bb..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
@@ -16,9 +16,15 @@
  */
 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 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 +39,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;
@@ -43,11 +52,15 @@
  */
 @RunWith(MockitoJUnitRunner.class)
 public class ConsumerAuthenticationFilterTest {
+
+  private static final int TOO_MANY_REQUESTS = 429;
+
   private ConsumerAuthenticationFilter authenticationFilter;
   @Mock
   private ConsumerAuthUtil consumerAuthUtil;
   @Mock
   private ConsumerAuditUtil consumerAuditUtil;
+
   @Mock
   private HttpServletRequest request;
   @Mock
@@ -65,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);
 
@@ -80,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);
 
@@ -89,4 +105,102 @@ 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 = "some-ratelimit-success-token";
+    Long someConsumerId = 1L;
+    int qps = 5;
+    int durationInSeconds = 3;
+
+    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);
+      }
+    };
+
+    int realQps = qps - 1;
+    executeWithQps(realQps, task, durationInSeconds);
+
+    int total = realQps * 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 = "some-ratelimit-fail-token";
+    Long someConsumerId = 1L;
+    int qps = 5;
+    int durationInSeconds = 3;
+
+    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);
+      }
+    };
+
+    int realQps = qps + 3;
+    executeWithQps(realQps, task, durationInSeconds);
+
+    int leastTimes = qps * durationInSeconds;
+    int mostTimes = realQps * durationInSeconds;
+
+    verify(response, atLeastOnce()).sendError(eq(TOO_MANY_REQUESTS), 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.setRateLimit(qps);
+    someConsumerToken.setToken(someToken);
+
+    when(request.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(someToken);
+    when(consumerAuthUtil.getConsumerToken(someToken)).thenReturn(someConsumerToken);
+  }
+
+
+  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/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);
     }
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..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());
+        .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/client/java-sdk-user-guide.md b/docs/en/client/java-sdk-user-guide.md
index eb8e11dbb44..276d59a27c7 100644
--- a/docs/en/client/java-sdk-user-guide.md
+++ b/docs/en/client/java-sdk-user-guide.md
@@ -611,6 +611,17 @@ ConfigFile configFile = ConfigService.getConfigFile("test", ConfigFileFormat.XML
 String content = configFile.getContent();
 ```
 
+### 3.1.5 Read the configuration corresponding to multiple appid and their namespaces.(added in version 2.4.0)
+Specify the corresponding appid and namespace to retrieve the config, and then obtain the properties.
+```java
+String someAppId = "Animal";
+String somePublicNamespace = "CAT";
+Config config = ConfigService.getConfig(someAppId, somePublicNamespace);
+String someKey = "someKeyFromPublicNamespace";
+String someDefaultValue = "someDefaultValueForTheKey";
+String value = config.getProperty(someKey, someDefaultValue);
+```
+
 ## 3.2 Spring integration approach
 
 ### 3.2.1 Configuration
@@ -749,6 +760,18 @@ public class SomeAppConfig {
 public class AnotherAppConfig {}
 ```
 
+4.Support for multiple appid (added in version 2.4.0)
+```java
+// Added support for loading multiple appid their corresponding namespaces. 
+// Note that when using multiple appid, if there are keys that are the same, 
+// only the key from the prioritized loaded appid will be retrieved
+@Configuration
+@EnableApolloConfig(value = {"FX.apollo", "application.yml"},
+        multipleConfigs = {@MultipleConfig(appid = "ORDER_SERVICE", namespaces = {"ORDER.apollo"})}
+)
+public class SomeAppConfig {}
+```
+
 #### 3.2.1.3 Spring Boot integration methods (recommended)
 
 Spring Boot supports the above two integration methods in addition to configuration via application.properties/bootstrap.properties, which enables configuration to be injected at an earlier stage, such as scenarios that use `@ConditionalOnProperty` or have some spring-boot-starter needs to read the configuration to do something in the startup phase (e.g. [dubbo-spring-boot-project](https://github.com/apache/incubator-dubbo-spring-boot-project)). So for Spring Boot environment it is recommended to access Apollo (requires version 0.10.0 and above) by the following way.
diff --git a/docs/zh/client/java-sdk-user-guide.md b/docs/zh/client/java-sdk-user-guide.md
index fa12873ad89..f3314696228 100644
--- a/docs/zh/client/java-sdk-user-guide.md
+++ b/docs/zh/client/java-sdk-user-guide.md
@@ -583,6 +583,17 @@ ConfigFile configFile = ConfigService.getConfigFile("test", ConfigFileFormat.XML
 String content = configFile.getContent();
 ```
 
+### 3.1.5 读取多AppId对应namespace的配置
+指定对应的AppId和namespace来获取Config,再获取属性
+```java
+String someAppId = "Animal";
+String somePublicNamespace = "CAT";
+Config config = ConfigService.getConfig(someAppId, somePublicNamespace);
+String someKey = "someKeyFromPublicNamespace";
+String someDefaultValue = "someDefaultValueForTheKey";
+String value = config.getProperty(someKey, someDefaultValue);
+```
+
 ## 3.2 Spring整合方式
 
 ### 3.2.1 配置
@@ -719,6 +730,17 @@ public class SomeAppConfig {
 public class AnotherAppConfig {}
 ```
 
+4.多appId的支持(新增于2.4.0版本)
+```java
+// 新增支持了多appId和对应namespace的加载,注意使用多appId的情况下,key相同的情况,只会取优先加载appId的那一个key
+@Configuration
+@EnableApolloConfig(value = {"FX.apollo", "application.yml"},
+        multipleConfigs = {@MultipleConfig(appid = "ORDER_SERVICE", namespaces = {"ORDER.apollo"})}
+)
+public class SomeAppConfig {}
+```
+
+
 #### 3.2.1.3 Spring Boot集成方式(推荐)
 
 Spring Boot除了支持上述两种集成方式以外,还支持通过application.properties/bootstrap.properties来配置,该方式能使配置在更早的阶段注入,比如使用`@ConditionalOnProperty`的场景或者是有一些spring-boot-starter在启动阶段就需要读取配置做一些事情(如[dubbo-spring-boot-project](https://github.com/apache/incubator-dubbo-spring-boot-project)),所以对于Spring Boot环境建议通过以下方式来接入Apollo(需要0.10.0及以上版本)。
diff --git a/docs/zh/deployment/distributed-deployment-guide.md b/docs/zh/deployment/distributed-deployment-guide.md
index ebad0ca85b3..b6f74a1cd77 100644
--- a/docs/zh/deployment/distributed-deployment-guide.md
+++ b/docs/zh/deployment/distributed-deployment-guide.md
@@ -1400,7 +1400,6 @@ portal上“帮助”链接的地址,默认是Apollo github的wiki首页,可
 
 修改该参数可能会影响搜索功能的性能,因此在修改之前应该进行充分的测试,根据实际业务需求和系统资源情况,适当调整`apollo.portal.search.perEnvMaxResults`的值,以平衡性能和搜索结果的数量
 
-
 ## 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 e1be67d3d6e..eb935078e85 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',
+  `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
new file mode 100644
index 00000000000..5c7230d6c68
--- /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 `RateLimit` int NOT NULL DEFAULT '0' 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..e1806eb7d41 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',
+  `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
new file mode 100644
index 00000000000..2acb52dde8d
--- /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 `RateLimit` int NOT NULL DEFAULT '0' 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..778e8b8ea0f 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',
+  `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
new file mode 100644
index 00000000000..06c50f032f5
--- /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 `RateLimit` int NOT NULL DEFAULT '0' 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..c2c3e4979d4 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',
+  `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
new file mode 100644
index 00000000000..c018b766c3b
--- /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 `RateLimit` int NOT NULL DEFAULT '0' COMMENT '限流值' AFTER `Token`;
+
+-- ${gists.autoGeneratedDeclaration}