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)"> + <!-- 格式化按钮 --> + <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)"> + <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}