From 321055831dd7d77a9d6ce54c58513c652fde4e64 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 30 Aug 2024 07:43:44 +0800 Subject: [PATCH 1/8] build(deps): bump com.mysql:mysql-connector-j from 8.0.32 to 8.2.0 (#5217) Bumps [com.mysql:mysql-connector-j](https://github.com/mysql/mysql-connector-j) from 8.0.32 to 8.2.0. - [Changelog](https://github.com/mysql/mysql-connector-j/blob/release/9.x/CHANGES) - [Commits](https://github.com/mysql/mysql-connector-j/compare/8.0.32...8.2.0) --- updated-dependencies: - dependency-name: com.mysql:mysql-connector-j dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 7ffab97f930..465a1c01343 100644 --- a/pom.xml +++ b/pom.xml @@ -78,7 +78,7 @@ 5.9.2 1.4.0 - 8.0.32 + 8.2.0 42.7.2 3.3.0 From 31e6486791de3ad60f059f34957b343839aa5ebb Mon Sep 17 00:00:00 2001 From: SnowOnion Date: Fri, 30 Aug 2024 20:28:28 +0800 Subject: [PATCH 2/8] chore: beautify footer: github -> GitHub (#5220) --- .../src/main/resources/static/views/common/footer.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apollo-portal/src/main/resources/static/views/common/footer.html b/apollo-portal/src/main/resources/static/views/common/footer.html index aa316cf251b..69c32b97050 100644 --- a/apollo-portal/src/main/resources/static/views/common/footer.html +++ b/apollo-portal/src/main/resources/static/views/common/footer.html @@ -20,7 +20,7 @@ Copyright 2024 Apollo Authors - github + GitHub

From 7e7b0902f09c0b9d9ea06ccc3e62d6b91b5147a2 Mon Sep 17 00:00:00 2001 From: xiaoxianhjy <2413421030@qq.com> Date: Fri, 20 Sep 2024 19:45:51 +0800 Subject: [PATCH 3/8] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E4=BA=86=E7=AE=A1?= =?UTF-8?q?=E7=90=86=E5=91=98=E5=85=A8=E5=B1=80=E6=90=9C=E7=B4=A2Value?= =?UTF-8?q?=E5=80=BC=E7=9A=84=E5=8A=9F=E8=83=BD=20(#5182)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * 添加了管理员全局搜索Value值的功能 * Update ItemControllerTest.java * Update GlobalSearchValueController.js * Improved some project codes * Optimized some issues * Added some relevant documents * Update docs/zh/portal/apollo-user-guide.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update docs/zh/portal/apollo-user-guide.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update README.md * Added usage documentation * Update docs/zh/README.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Fixed some issues * Fixed the front-end * Update GlobalSearchValueController.js * Optimized some of the logic * Added some unit tests --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- CHANGES.md | 4 +- README.md | 11 +- .../controller/ItemController.java | 9 + .../controller/ItemControllerTest.java | 65 +++-- .../apollo/biz/repository/ItemRepository.java | 17 ++ .../apollo/biz/service/ItemService.java | 18 +- .../apollo/biz/service/ItemServiceTest.java | 25 ++ .../apollo/common/dto/ItemInfoDTO.java | 88 ++++++ .../common/http/SearchResponseEntity.java | 67 +++++ .../apollo/common/dto/ItemInfoDTOTest.java | 61 ++++ .../common/http/SearchResponseEntityTest.java | 62 ++++ .../apollo/portal/api/AdminServiceAPI.java | 12 + .../portal/component/config/PortalConfig.java | 2 + .../controller/GlobalSearchController.java | 54 ++++ .../apollo/portal/entity/vo/ItemInfo.java | 100 +++++++ .../portal/service/GlobalSearchService.java | 77 +++++ .../resources/static/global_search_value.html | 196 +++++++++++++ .../src/main/resources/static/i18n/en.json | 25 +- .../src/main/resources/static/i18n/zh-CN.json | 25 +- .../src/main/resources/static/img/nodata.png | Bin 0 -> 21120 bytes .../src/main/resources/static/scripts/app.js | 2 + .../controller/GlobalSearchValueController.js | 273 ++++++++++++++++++ .../services/GlobalSearchValueService.js | 40 +++ .../resources/static/views/common/nav.html | 1 + .../GlobalSearchControllerTest.java | 135 +++++++++ .../service/GlobalSearchServiceTest.java | 127 ++++++++ changes/changes-2.4.0.md | 13 + docs/en/README.md | 4 + .../distributed-deployment-guide.md | 14 +- docs/en/design/apollo-design.md | 2 +- docs/en/design/apollo-introduction.md | 6 + .../Configuration query-Non properties.png | Bin 0 -> 35238 bytes .../images/Configuration query-properties.png | Bin 0 -> 35102 bytes ...n-of-global-search-configuration-items.png | Bin 0 -> 27530 bytes docs/en/portal/apollo-user-guide.md | 27 ++ docs/zh/README.md | 4 + .../distributed-deployment-guide.md | 8 + docs/zh/design/apollo-design.md | 2 +- docs/zh/design/apollo-introduction.md | 26 +- .../Configuration query-Non properties.png | Bin 0 -> 35238 bytes .../images/Configuration query-properties.png | Bin 0 -> 35102 bytes ...n-of-global-search-configuration-items.png | Bin 0 -> 27530 bytes docs/zh/portal/apollo-user-guide.md | 35 ++- 43 files changed, 1581 insertions(+), 56 deletions(-) create mode 100644 apollo-common/src/main/java/com/ctrip/framework/apollo/common/dto/ItemInfoDTO.java create mode 100644 apollo-common/src/main/java/com/ctrip/framework/apollo/common/http/SearchResponseEntity.java create mode 100644 apollo-common/src/test/java/com/ctrip/framework/apollo/common/dto/ItemInfoDTOTest.java create mode 100644 apollo-common/src/test/java/com/ctrip/framework/apollo/common/http/SearchResponseEntityTest.java create mode 100644 apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/GlobalSearchController.java create mode 100644 apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/ItemInfo.java create mode 100644 apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/GlobalSearchService.java create mode 100644 apollo-portal/src/main/resources/static/global_search_value.html create mode 100644 apollo-portal/src/main/resources/static/img/nodata.png create mode 100644 apollo-portal/src/main/resources/static/scripts/controller/GlobalSearchValueController.js create mode 100644 apollo-portal/src/main/resources/static/scripts/services/GlobalSearchValueService.js create mode 100644 apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/GlobalSearchControllerTest.java create mode 100644 apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/service/GlobalSearchServiceTest.java create mode 100644 changes/changes-2.4.0.md create mode 100644 docs/en/images/Configuration query-Non properties.png create mode 100644 docs/en/images/Configuration query-properties.png create mode 100644 docs/en/images/System-parameterization-of-global-search-configuration-items.png create mode 100644 docs/zh/images/Configuration query-Non properties.png create mode 100644 docs/zh/images/Configuration query-properties.png create mode 100644 docs/zh/images/System-parameterization-of-global-search-configuration-items.png diff --git a/CHANGES.md b/CHANGES.md index e67b0b78c40..ed756c3f80e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,7 +7,7 @@ Apollo 2.4.0 ------------------ * [Update the server config link in system info page](https://github.com/apolloconfig/apollo/pull/5204) * [Feature support portal restTemplate Client connection pool config](https://github.com/apolloconfig/apollo/pull/5200) - +* [Feature added the ability for administrators to globally search for Value](https://github.com/apolloconfig/apollo/pull/5182) ------------------ -All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1) +All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1) \ No newline at end of file diff --git a/README.md b/README.md index 2dbe99867c3..3b1a1f7d7ce 100644 --- a/README.md +++ b/README.md @@ -37,36 +37,31 @@ Demo Environment: * The same codebase could have different configurations when deployed in different clusters * With the namespace concept, it is easy to support multiple applications to share the same configurations, while also allowing them to customize the configurations * Multiple languages is provided in user interface(currently Chinese and English) - * **Configuration changes takes effect in real time (hot release)** * After the user modified the configuration and released it in Apollo, the sdk will receive the latest configurations in real time (1 second) and notify the application - * **Release version management** * Every configuration releases are versioned, which is friendly to support configuration rollback - * **Grayscale release** * Support grayscale configuration release, for example, after clicking release, it will only take effect for some application instances. After a period of observation, we could push the configurations to all application instances if there is no problem - +* **Global Search Configuration Items** + * A fuzzy search of the key and value of a configuration item finds in which application, environment, cluster, namespace the configuration item with the corresponding value is used + * It is easy for administrators and SRE roles to quickly and easily find and change the configuration values of resources by highlighting, paging and jumping through configurations * **Authorization management, release approval and operation audit** * Great authorization mechanism is designed for applications and configurations management, and the management of configurations is divided into two operations: editing and publishing, therefore greatly reducing human errors * All operations have audit logs for easy tracking of problems - * **Client side configuration information monitoring** * It's very easy to see which instances are using the configurations and what versions they are using - * **Rich SDKs available** * Provides native sdks of Java and .Net to facilitate application integration * Support Spring Placeholder, Annotation and Spring Boot ConfigurationProperties for easy application use (requires Spring 3.1.1+) * Http APIs are provided, so non-Java and .Net applications can integrate conveniently * Rich third party sdks are also available, e.g. Golang, Python, NodeJS, PHP, C, etc - * **Open platform API** * Apollo itself provides a unified configuration management interface, which supports features such as multi-environment, multi-data center configuration management, permissions, and process governance * However, for the sake of versatility, Apollo will not put too many restrictions on the modification of the configuration, as long as it conforms to the basic format, it can be saved. * In our research, we found that for some users, their configurations may have more complicated formats, such as xml, json, and the format needs to be verified * There are also some users such as DAL, which not only have a specific format, but also need to verify the entered value before saving, such as checking whether the database, username and password match * For this type of application, Apollo allows the application to modify and release configurations through open APIs, which has great authorization and permission control mechanism built in - * **Simple deployment** * As an infrastructure service, the configuration center has very high availability requirements, which forces Apollo to rely on external dependencies as little as possible * Currently, the only external dependency is MySQL, so the deployment is very simple. Apollo can run as long as Java and MySQL are installed diff --git a/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ItemController.java b/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ItemController.java index 2e7e25d5912..f628748d2fa 100644 --- a/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ItemController.java +++ b/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/ItemController.java @@ -27,6 +27,7 @@ import com.ctrip.framework.apollo.biz.service.ReleaseService; import com.ctrip.framework.apollo.biz.utils.ConfigChangeContentBuilder; import com.ctrip.framework.apollo.common.dto.ItemDTO; +import com.ctrip.framework.apollo.common.dto.ItemInfoDTO; import com.ctrip.framework.apollo.common.dto.PageDTO; import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.exception.NotFoundException; @@ -201,6 +202,14 @@ public List findDeletedItems(@PathVariable("appId") String appId, return Collections.emptyList(); } + @GetMapping("/items-search/key-and-value") + public PageDTO getItemInfoBySearch(@RequestParam(value = "key", required = false) String key, + @RequestParam(value = "value", required = false) String value, + Pageable limit) { + Page pageItemInfoDTO = itemService.getItemInfoBySearch(key, value, limit); + return new PageDTO<>(pageItemInfoDTO.getContent(), limit, pageItemInfoDTO.getTotalElements()); + } + @GetMapping("/items/{itemId}") public ItemDTO get(@PathVariable("itemId") long itemId) { Item item = itemService.findOne(itemId); diff --git a/apollo-adminservice/src/test/java/com/ctrip/framework/apollo/adminservice/controller/ItemControllerTest.java b/apollo-adminservice/src/test/java/com/ctrip/framework/apollo/adminservice/controller/ItemControllerTest.java index f406d01e0f9..84e791f6c1d 100644 --- a/apollo-adminservice/src/test/java/com/ctrip/framework/apollo/adminservice/controller/ItemControllerTest.java +++ b/apollo-adminservice/src/test/java/com/ctrip/framework/apollo/adminservice/controller/ItemControllerTest.java @@ -21,18 +21,19 @@ import com.ctrip.framework.apollo.biz.entity.Commit; import com.ctrip.framework.apollo.biz.repository.CommitRepository; import com.ctrip.framework.apollo.biz.repository.ItemRepository; -import com.ctrip.framework.apollo.common.dto.AppDTO; -import com.ctrip.framework.apollo.common.dto.ClusterDTO; -import com.ctrip.framework.apollo.common.dto.ItemDTO; -import com.ctrip.framework.apollo.common.dto.NamespaceDTO; +import com.ctrip.framework.apollo.biz.service.ItemService; +import com.ctrip.framework.apollo.common.dto.*; + import java.util.List; import java.util.Objects; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; +import org.springframework.http.*; import org.springframework.test.context.jdbc.Sql; import org.springframework.test.context.jdbc.Sql.ExecutionPhase; @@ -48,6 +49,9 @@ public class ItemControllerTest extends AbstractControllerTest { @Autowired private ItemRepository itemRepository; + @Autowired + private ItemService itemService; + @Test @Sql(scripts = "/controller/test-itemset.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) @Sql(scripts = "/controller/cleanup.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD) @@ -58,7 +62,7 @@ public void testCreate() { ClusterDTO cluster = restTemplate.getForObject(clusterBaseUrl(), ClusterDTO.class, app.getAppId(), "default"); assert cluster != null; NamespaceDTO namespace = restTemplate.getForObject(namespaceBaseUrl(), - NamespaceDTO.class, app.getAppId(), cluster.getName(), "application"); + NamespaceDTO.class, app.getAppId(), cluster.getName(), "application"); String itemKey = "test-key"; String itemValue = "test-value"; @@ -68,12 +72,12 @@ public void testCreate() { item.setDataChangeLastModifiedBy("apollo"); ResponseEntity response = restTemplate.postForEntity(itemBaseUrl(), - item, ItemDTO.class, app.getAppId(), cluster.getName(), namespace.getNamespaceName()); + item, ItemDTO.class, app.getAppId(), cluster.getName(), namespace.getNamespaceName()); Assert.assertEquals(HttpStatus.OK, response.getStatusCode()); Assert.assertEquals(itemKey, Objects.requireNonNull(response.getBody()).getKey()); List commitList = commitRepository.findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(app.getAppId(), cluster.getName(), namespace.getNamespaceName(), - Pageable.ofSize(10)); + Pageable.ofSize(10)); Assert.assertEquals(1, commitList.size()); Commit commit = commitList.get(0); @@ -93,15 +97,15 @@ public void testUpdate() { ClusterDTO cluster = restTemplate.getForObject(clusterBaseUrl(), ClusterDTO.class, app.getAppId(), "default"); assert cluster != null; NamespaceDTO namespace = restTemplate.getForObject(namespaceBaseUrl(), - NamespaceDTO.class, app.getAppId(), cluster.getName(), "application"); + NamespaceDTO.class, app.getAppId(), cluster.getName(), "application"); String itemKey = "test-key"; String itemValue = "test-value-updated"; long itemId = itemRepository.findByKey(itemKey, Pageable.ofSize(1)) - .getContent() - .get(0) - .getId(); + .getContent() + .get(0) + .getId(); ItemDTO item = new ItemDTO(itemKey, itemValue, "", 1); item.setDataChangeLastModifiedBy("apollo"); @@ -115,7 +119,7 @@ public void testUpdate() { }); List commitList = commitRepository.findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(app.getAppId(), cluster.getName(), namespace.getNamespaceName(), - Pageable.ofSize(10)); + Pageable.ofSize(10)); assertThat(commitList).hasSize(2); } @@ -131,23 +135,44 @@ public void testDelete() { ClusterDTO cluster = restTemplate.getForObject(clusterBaseUrl(), ClusterDTO.class, app.getAppId(), "default"); assert cluster != null; NamespaceDTO namespace = restTemplate.getForObject(namespaceBaseUrl(), - NamespaceDTO.class, app.getAppId(), cluster.getName(), "application"); + NamespaceDTO.class, app.getAppId(), cluster.getName(), "application"); String itemKey = "test-key"; long itemId = itemRepository.findByKey(itemKey, Pageable.ofSize(1)) - .getContent() - .get(0) - .getId(); + .getContent() + .get(0) + .getId(); String deleteUrl = url( "/items/{itemId}?operator=apollo"); restTemplate.delete(deleteUrl, itemId); assertThat(itemRepository.findById(itemId).isPresent()) - .isFalse(); + .isFalse(); assert namespace != null; List commitList = commitRepository.findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(app.getAppId(), cluster.getName(), namespace.getNamespaceName(), - Pageable.ofSize(10)); + Pageable.ofSize(10)); assertThat(commitList).hasSize(2); } + + @Test + @Sql(scripts = "/controller/test-itemset.sql", executionPhase = ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = "/controller/cleanup.sql", executionPhase = ExecutionPhase.AFTER_TEST_METHOD) + public void testSearch() { + this.testCreate(); + + String itemKey = "test-key"; + String itemValue = "test-value"; + Page itemInfoDTOS = itemService.getItemInfoBySearch(itemKey, itemValue, PageRequest.of(0, 200)); + HttpHeaders headers = new HttpHeaders(); + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity> response = restTemplate.exchange( + url("/items-search/key-and-value?key={key}&value={value}&page={page}&size={size}"), + HttpMethod.GET, + entity, + new ParameterizedTypeReference>() {}, + itemKey, itemValue, 0, 200 + ); + assertThat(itemInfoDTOS.getContent().toString()).isEqualTo(response.getBody().getContent().toString()); + } } diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ItemRepository.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ItemRepository.java index d482ce63e97..c866b902814 100644 --- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ItemRepository.java +++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/ItemRepository.java @@ -18,11 +18,13 @@ import com.ctrip.framework.apollo.biz.entity.Item; +import com.ctrip.framework.apollo.common.dto.ItemInfoDTO; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.Modifying; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.query.Param; import java.util.Date; import java.util.List; @@ -43,6 +45,21 @@ public interface ItemRepository extends PagingAndSortingRepository { Item findFirst1ByNamespaceIdOrderByLineNumDesc(Long namespaceId); + @Query("SELECT new com.ctrip.framework.apollo.common.dto.ItemInfoDTO(n.appId, n.clusterName, n.namespaceName, i.key, i.value) " + + "FROM Item i RIGHT JOIN Namespace n ON i.namespaceId = n.id " + + "WHERE i.key LIKE %:key% AND i.value LIKE %:value% AND i.isDeleted = 0") + Page findItemsByKeyAndValueLike(@Param("key") String key, @Param("value") String value, Pageable pageable); + + @Query("SELECT new com.ctrip.framework.apollo.common.dto.ItemInfoDTO(n.appId, n.clusterName, n.namespaceName, i.key, i.value) " + + "FROM Item i RIGHT JOIN Namespace n ON i.namespaceId = n.id " + + "WHERE i.key LIKE %:key% AND i.isDeleted = 0") + Page findItemsByKeyLike(@Param("key") String key, Pageable pageable); + + @Query("SELECT new com.ctrip.framework.apollo.common.dto.ItemInfoDTO(n.appId, n.clusterName, n.namespaceName, i.key, i.value) " + + "FROM Item i RIGHT JOIN Namespace n ON i.namespaceId = n.id " + + "WHERE i.value LIKE %:value% AND i.isDeleted = 0") + Page findItemsByValueLike(@Param("value") String value, Pageable pageable); + @Modifying @Query("update Item set IsDeleted = true, DeletedAt = ROUND(UNIX_TIMESTAMP(NOW(4))*1000), DataChange_LastModifiedBy = ?2 where NamespaceId = ?1 and IsDeleted = false") int deleteByNamespaceId(long namespaceId, String operator); diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/ItemService.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/ItemService.java index 7dffa366c5e..3c0d23c1b62 100644 --- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/ItemService.java +++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/ItemService.java @@ -22,6 +22,7 @@ import com.ctrip.framework.apollo.biz.entity.Item; import com.ctrip.framework.apollo.biz.entity.Namespace; import com.ctrip.framework.apollo.biz.repository.ItemRepository; +import com.ctrip.framework.apollo.common.dto.ItemInfoDTO; import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.exception.NotFoundException; import com.ctrip.framework.apollo.common.utils.BeanUtils; @@ -33,10 +34,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.util.Collections; -import java.util.Date; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -146,6 +144,18 @@ public Page findItemsByNamespace(String appId, String clusterName, String return itemRepository.findByNamespaceId(namespace.getId(), pageable); } + public Page getItemInfoBySearch(String key, String value, Pageable limit) { + Page itemInfoDTOs; + if (key.isEmpty() && !value.isEmpty()) { + itemInfoDTOs = itemRepository.findItemsByValueLike(value, limit); + } else if (value.isEmpty() && !key.isEmpty()) { + itemInfoDTOs = itemRepository.findItemsByKeyLike(key, limit); + } else { + itemInfoDTOs = itemRepository.findItemsByKeyAndValueLike(key, value, limit); + } + return itemInfoDTOs; + } + @Transactional public Item save(Item entity) { checkItemKeyLength(entity.getKey()); diff --git a/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/service/ItemServiceTest.java b/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/service/ItemServiceTest.java index 3b0ed5f6a4b..6d8003f1d58 100644 --- a/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/service/ItemServiceTest.java +++ b/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/service/ItemServiceTest.java @@ -18,10 +18,13 @@ import com.ctrip.framework.apollo.biz.AbstractIntegrationTest; import com.ctrip.framework.apollo.biz.entity.Item; +import com.ctrip.framework.apollo.common.dto.ItemInfoDTO; import com.ctrip.framework.apollo.common.exception.BadRequestException; import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; import org.springframework.test.context.jdbc.Sql; public class ItemServiceTest extends AbstractIntegrationTest { @@ -71,4 +74,26 @@ public void testUpdateItem() { Assert.assertEquals("v1-new", dbItem.getValue()); } + @Test + @Sql(scripts = {"/sql/namespace-test.sql","/sql/item-test.sql"}, executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) + public void testSearchItem() { + ItemInfoDTO itemInfoDTO = new ItemInfoDTO(); + itemInfoDTO.setAppId("testApp"); + itemInfoDTO.setClusterName("default"); + itemInfoDTO.setNamespaceName("application"); + itemInfoDTO.setKey("k1"); + itemInfoDTO.setValue("v1"); + + String itemKey = "k1"; + String itemValue = "v1"; + Page ExpectedItemInfoDTOSByKeyAndValue = itemService.getItemInfoBySearch(itemKey, itemValue, PageRequest.of(0,200)); + Page ExpectedItemInfoDTOSByKey = itemService.getItemInfoBySearch(itemKey,"", PageRequest.of(0,200)); + Page ExpectedItemInfoDTOSByValue = itemService.getItemInfoBySearch("", itemValue, PageRequest.of(0,200)); + Assert.assertEquals(itemInfoDTO.toString(), ExpectedItemInfoDTOSByKeyAndValue.getContent().get(0).toString()); + Assert.assertEquals(itemInfoDTO.toString(), ExpectedItemInfoDTOSByKey.getContent().get(0).toString()); + Assert.assertEquals(itemInfoDTO.toString(), ExpectedItemInfoDTOSByValue.getContent().get(0).toString()); + + } + } diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/dto/ItemInfoDTO.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/dto/ItemInfoDTO.java new file mode 100644 index 00000000000..a28794ed656 --- /dev/null +++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/dto/ItemInfoDTO.java @@ -0,0 +1,88 @@ +/* + * 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. + * + */ +package com.ctrip.framework.apollo.common.dto; + + +public class ItemInfoDTO extends BaseDTO{ + private String appId; + private String clusterName; + private String namespaceName; + private String key; + private String value; + + public ItemInfoDTO() { + } + + public ItemInfoDTO(String appId, String clusterName, String namespaceName, String key, String value) { + this.appId = appId; + this.clusterName = clusterName; + this.namespaceName = namespaceName; + this.key = key; + this.value = value; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getClusterName() { + return clusterName; + } + + public void setClusterName(String clusterName) { + this.clusterName = clusterName; + } + + public String getNamespaceName() { + return namespaceName; + } + + public void setNamespaceName(String namespaceName) { + this.namespaceName = namespaceName; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return "ItemInfoDTO{" + + "appId='" + appId + '\'' + + ", clusterName='" + clusterName + '\'' + + ", namespaceName='" + namespaceName + '\'' + + ", key='" + key + '\'' + + ", value='" + value + '\'' + + '}'; + } +} diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/http/SearchResponseEntity.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/http/SearchResponseEntity.java new file mode 100644 index 00000000000..0eb6cf446da --- /dev/null +++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/http/SearchResponseEntity.java @@ -0,0 +1,67 @@ +/* + * 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. + * + */ +package com.ctrip.framework.apollo.common.http; + +import org.springframework.http.HttpStatus; + +public class SearchResponseEntity { + + private T body; + private boolean hasMoreData; + private Object message; + private int code; + + public static SearchResponseEntity ok(T body){ + SearchResponseEntity SearchResponseEntity = new SearchResponseEntity<>(); + SearchResponseEntity.message = HttpStatus.OK.getReasonPhrase(); + SearchResponseEntity.code = HttpStatus.OK.value(); + SearchResponseEntity.body = body; + SearchResponseEntity.hasMoreData = false; + return SearchResponseEntity; + } + + public static SearchResponseEntity okWithMessage(T body, Object message){ + SearchResponseEntity SearchResponseEntity = new SearchResponseEntity<>(); + SearchResponseEntity.message = message; + SearchResponseEntity.code = HttpStatus.OK.value(); + SearchResponseEntity.body = body; + SearchResponseEntity.hasMoreData = true; + return SearchResponseEntity; + } + + public static SearchResponseEntity error(HttpStatus httpCode, Object message){ + SearchResponseEntity SearchResponseEntity = new SearchResponseEntity<>(); + SearchResponseEntity.message = message; + SearchResponseEntity.code = httpCode.value(); + return SearchResponseEntity; + } + + public int getCode() { + return code; + } + + public Object getMessage() { + return message; + } + + public T getBody() { + return body; + } + + public boolean isHasMoreData() {return hasMoreData;} + +} \ No newline at end of file diff --git a/apollo-common/src/test/java/com/ctrip/framework/apollo/common/dto/ItemInfoDTOTest.java b/apollo-common/src/test/java/com/ctrip/framework/apollo/common/dto/ItemInfoDTOTest.java new file mode 100644 index 00000000000..5a138b691cf --- /dev/null +++ b/apollo-common/src/test/java/com/ctrip/framework/apollo/common/dto/ItemInfoDTOTest.java @@ -0,0 +1,61 @@ +/* + * 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. + * + */ +package com.ctrip.framework.apollo.common.dto; + + +import org.junit.Before; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +public class ItemInfoDTOTest { + + private ItemInfoDTO itemInfoDTO; + + @Before + public void setUp() { + itemInfoDTO = new ItemInfoDTO("testAppId", "testClusterName", "testNamespaceName", "testKey", "testValue"); + } + + @Test + public void testGetAppId_ShouldReturnCorrectAppId() { + assertEquals("testAppId", itemInfoDTO.getAppId()); + } + + @Test + public void testGetClusterName_ShouldReturnCorrectClusterName() { + assertEquals("testClusterName", itemInfoDTO.getClusterName()); + } + + @Test + public void testGetNamespaceName_ShouldReturnCorrectNamespaceName() { + assertEquals("testNamespaceName", itemInfoDTO.getNamespaceName()); + } + + @Test + public void testGetKey_ShouldReturnCorrectKey() { + assertEquals("testKey", itemInfoDTO.getKey()); + } + + @Test + public void testGetValue_ShouldReturnCorrectValue() { + assertEquals("testValue", itemInfoDTO.getValue()); + } + + @Test + public void testToString_ShouldReturnExpectedString() { + assertEquals("ItemInfoDTO{appId='testAppId', clusterName='testClusterName', namespaceName='testNamespaceName', key='testKey', value='testValue'}", itemInfoDTO.toString()); + } +} diff --git a/apollo-common/src/test/java/com/ctrip/framework/apollo/common/http/SearchResponseEntityTest.java b/apollo-common/src/test/java/com/ctrip/framework/apollo/common/http/SearchResponseEntityTest.java new file mode 100644 index 00000000000..14e250004eb --- /dev/null +++ b/apollo-common/src/test/java/com/ctrip/framework/apollo/common/http/SearchResponseEntityTest.java @@ -0,0 +1,62 @@ +/* + * 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. + * + */ +package com.ctrip.framework.apollo.common.http; + + +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.http.HttpStatus; + +import static org.junit.Assert.*; + +@RunWith(MockitoJUnitRunner.class) +public class SearchResponseEntityTest { + + @Test + public void testOk_WithValidBody_ShouldReturnOkResponse() { + String body = "test body"; + SearchResponseEntity response = SearchResponseEntity.ok(body); + + assertEquals(HttpStatus.OK.value(), response.getCode()); + assertEquals(HttpStatus.OK.getReasonPhrase(), response.getMessage()); + assertEquals(body, response.getBody()); + assertFalse(response.isHasMoreData()); + } + + @Test + public void testOkWithMessage_WithValidBodyAndMessage_ShouldReturnOkResponseWithMessage() { + String body = "test body"; + String message = "test message"; + SearchResponseEntity response = SearchResponseEntity.okWithMessage(body, message); + + assertEquals(HttpStatus.OK.value(), response.getCode()); + assertEquals(message, response.getMessage()); + assertEquals(body, response.getBody()); + assertTrue(response.isHasMoreData()); + } + + @Test + public void testError_WithValidCodeAndMessage_ShouldReturnErrorResponse() { + HttpStatus httpCode = HttpStatus.BAD_REQUEST; + String message = "error message"; + SearchResponseEntity response = SearchResponseEntity.error(httpCode, message); + + assertEquals(httpCode.value(), response.getCode()); + assertEquals(message, response.getMessage()); + assertEquals(null, response.getBody()); + assertFalse(response.isHasMoreData()); + } +} diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java index 3d3f23bff4a..b8035aae7a2 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java @@ -184,6 +184,9 @@ public static class ItemAPI extends API { private final ParameterizedTypeReference> openItemPageDTO = new ParameterizedTypeReference>() {}; + private final ParameterizedTypeReference> pageItemInfoDTO = + new ParameterizedTypeReference>() {}; + public List findItems(String appId, Env env, String clusterName, String namespaceName) { ItemDTO[] itemDTOs = restTemplate.get(env, "apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items", @@ -198,6 +201,15 @@ public List findDeletedItems(String appId, Env env, String clusterName, return Arrays.asList(itemDTOs); } + public PageDTO getPerEnvItemInfoBySearch(Env env, String key, String value, int page, int size){ + ResponseEntity> + entity = + restTemplate.get(env, + "items-search/key-and-value?key={key}&value={value}&page={page}&size={size}", + pageItemInfoDTO, key, value, page, size); + return entity.getBody(); + } + public ItemDTO loadItem(Env env, String appId, String clusterName, String namespaceName, String key) { return restTemplate.get(env, "apps/{appId}/clusters/{clusterName}/namespaces/{namespaceName}/items/{key}", ItemDTO.class, appId, clusterName, namespaceName, key); diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java index 93b9e93daf2..a5a4a9d81ea 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/config/PortalConfig.java @@ -86,6 +86,8 @@ public List portalSupportedEnvs() { return envs; } + public int getPerEnvSearchMaxResults() {return getIntProperty("apollo.portal.search.perEnvMaxResults", 200);} + /** * @return the relationship between environment and its meta server. empty if meet exception */ diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/GlobalSearchController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/GlobalSearchController.java new file mode 100644 index 00000000000..bb44b22932f --- /dev/null +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/GlobalSearchController.java @@ -0,0 +1,54 @@ +/* + * 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. + * + */ +package com.ctrip.framework.apollo.portal.controller; + + +import com.ctrip.framework.apollo.common.exception.BadRequestException; +import com.ctrip.framework.apollo.common.http.SearchResponseEntity; +import com.ctrip.framework.apollo.portal.component.config.PortalConfig; +import com.ctrip.framework.apollo.portal.entity.vo.ItemInfo; +import com.ctrip.framework.apollo.portal.service.GlobalSearchService; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +public class GlobalSearchController { + private final GlobalSearchService globalSearchService; + private final PortalConfig portalConfig; + + public GlobalSearchController(final GlobalSearchService globalSearchService, final PortalConfig portalConfig) { + this.globalSearchService = globalSearchService; + this.portalConfig = portalConfig; + } + + @PreAuthorize(value = "@permissionValidator.isSuperAdmin()") + @GetMapping("/global-search/item-info/by-key-or-value") + public SearchResponseEntity> getItemInfoBySearch(@RequestParam(value = "key", required = false, defaultValue = "") String key, + @RequestParam(value = "value", required = false , defaultValue = "") String value) { + + if(key.isEmpty() && value.isEmpty()) { + throw new BadRequestException("Please enter at least one search criterion in either key or value."); + } + + return globalSearchService.getAllEnvItemInfoBySearch(key, value, 0, portalConfig.getPerEnvSearchMaxResults()); + } + +} diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/ItemInfo.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/ItemInfo.java new file mode 100644 index 00000000000..f4d8dbf3974 --- /dev/null +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/entity/vo/ItemInfo.java @@ -0,0 +1,100 @@ +/* + * 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. + * + */ +package com.ctrip.framework.apollo.portal.entity.vo; + +public class ItemInfo { + + private String appId; + private String envName; + private String clusterName; + private String namespaceName; + private String key; + private String value; + + public ItemInfo() { + } + + public ItemInfo(String appId, String envName, String clusterName, + String namespaceName, String key, String value) { + this.appId = appId; + this.envName = envName; + this.clusterName = clusterName; + this.namespaceName = namespaceName; + this.key = key; + this.value = value; + } + + public String getAppId() { + return appId; + } + + public void setAppId(String appId) { + this.appId = appId; + } + + public String getEnvName() { + return envName; + } + + public void setEnvName(String envName) { + this.envName = envName; + } + + public String getClusterName() { + return clusterName; + } + + public void setClusterName(String clusterName) { + this.clusterName = clusterName; + } + + public String getNamespaceName() { + return namespaceName; + } + + public void setNamespaceName(String namespaceName) { + this.namespaceName = namespaceName; + } + + public String getKey() { + return key; + } + + public void setKey(String key) { + this.key = key; + } + + public String getValue() { + return value; + } + + public void setValue(String value) { + this.value = value; + } + + @Override + public String toString() { + return "ItemInfo{" + + "appId='" + appId + '\'' + + ", envName='" + envName + '\'' + + ", clusterName='" + clusterName + '\'' + + ", namespaceName='" + namespaceName + '\'' + + ", key='" + key + '\'' + + ", value='" + value + '\'' + + '}'; + } +} diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/GlobalSearchService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/GlobalSearchService.java new file mode 100644 index 00000000000..5c7cbdcf5ba --- /dev/null +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/GlobalSearchService.java @@ -0,0 +1,77 @@ +/* + * 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. + * + */ +package com.ctrip.framework.apollo.portal.service; + +import com.ctrip.framework.apollo.common.dto.ItemInfoDTO; +import com.ctrip.framework.apollo.common.dto.PageDTO; +import com.ctrip.framework.apollo.common.http.SearchResponseEntity; +import com.ctrip.framework.apollo.portal.api.AdminServiceAPI; +import com.ctrip.framework.apollo.portal.component.PortalSettings; +import com.ctrip.framework.apollo.portal.entity.vo.ItemInfo; +import com.ctrip.framework.apollo.portal.environment.Env; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; + +@Service +public class GlobalSearchService { + + private static final Logger LOGGER = LoggerFactory.getLogger(GlobalSearchService.class); + private final AdminServiceAPI.ItemAPI itemAPI; + private final PortalSettings portalSettings; + + public GlobalSearchService(AdminServiceAPI.ItemAPI itemAPI, PortalSettings portalSettings) { + this.itemAPI = itemAPI; + this.portalSettings = portalSettings; + } + + public SearchResponseEntity> getAllEnvItemInfoBySearch(String key, String value, int page, int size) { + List activeEnvs = portalSettings.getActiveEnvs(); + List envBeyondLimit = new ArrayList<>(); + AtomicBoolean hasMoreData = new AtomicBoolean(false); + List allEnvItemInfos = new ArrayList<>(); + activeEnvs.forEach(env -> { + PageDTO perEnvItemInfoDTOs = itemAPI.getPerEnvItemInfoBySearch(env, key, value, page, size); + if (!perEnvItemInfoDTOs.hasContent()) { + return; + } + perEnvItemInfoDTOs.getContent().forEach(itemInfoDTO -> { + try { + ItemInfo itemInfo = new ItemInfo(itemInfoDTO.getAppId(),env.getName(),itemInfoDTO.getClusterName(),itemInfoDTO.getNamespaceName(),itemInfoDTO.getKey(),itemInfoDTO.getValue()); + allEnvItemInfos.add(itemInfo); + } catch (Exception e) { + LOGGER.error("Error converting ItemInfoDTO to ItemInfo for item: {}", itemInfoDTO, e); + } + }); + if(perEnvItemInfoDTOs.getTotal() > size){ + envBeyondLimit.add(env.getName()); + hasMoreData.set(true); + } + }); + if(hasMoreData.get()){ + return SearchResponseEntity.okWithMessage(allEnvItemInfos,String.format( + "In %s , more than %d items found (Exceeded the maximum search quantity for a single environment). Please enter more precise criteria to narrow down the search scope.", + String.join(" , ", envBeyondLimit), size)); + } + return SearchResponseEntity.ok(allEnvItemInfos); + } + +} diff --git a/apollo-portal/src/main/resources/static/global_search_value.html b/apollo-portal/src/main/resources/static/global_search_value.html new file mode 100644 index 00000000000..1d59578914b --- /dev/null +++ b/apollo-portal/src/main/resources/static/global_search_value.html @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + {{'Global.Title' | translate }} + + + + +
+
+
+ +
+
+
+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apollo-portal/src/main/resources/static/i18n/en.json b/apollo-portal/src/main/resources/static/i18n/en.json index dbe1d698520..81c3bded0a6 100644 --- a/apollo-portal/src/main/resources/static/i18n/en.json +++ b/apollo-portal/src/main/resources/static/i18n/en.json @@ -893,5 +893,28 @@ "ApolloAuditLog.ParentSpan": "parent operation", "ApolloAuditLog.FollowsFromSpan": "last operation", "ApolloAuditLog.FieldChangeHistory": "Field Change History", - "ApolloAuditLog.InfluenceEntity": "Audit entity influenced" + "ApolloAuditLog.InfluenceEntity": "Audit entity influenced", + "Global.Title": "Global Search for Value", + "Global.App": "App ID", + "Global.Env": "Env Name", + "Global.Cluster": "Cluster Name", + "Global.NameSpace": "NameSpace Name", + "Global.Key": "Key", + "Global.Value": "Value", + "Global.ValueSearch.Tips" : "(Fuzzy search, key can be the name or content of the configuration item, value is the value of the configuration item.)", + "Global.Operate" : "Operate", + "Global.Expand" : "Expand", + "Global.Abbreviate" : "Abbreviate", + "Global.JumpToEditPage" : "Jump to edit page", + "Item.GlobalSearchByKey": "Search by Key", + "Item.GlobalSearchByValue": "Search by Value", + "Item.GlobalSearch": "Search", + "Item.GlobalSearchSystemError": "System error, please try again or contact the system administrator", + "Item.GlobalSearch.Tips": "Search hint", + "ApolloGlobalSearch.NoData" : "No data yet, please search or add", + "Paging.TotalItems.part1" : "Total of", + "Paging.TotalItems.part2" : "records", + "Paging.DisplayNumber" : "per/Page", + "Paging.PageNumberOne" : "First", + "Paging.PageNumberLast" : "Last" } 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 e166237c306..2ef309b46dc 100644 --- a/apollo-portal/src/main/resources/static/i18n/zh-CN.json +++ b/apollo-portal/src/main/resources/static/i18n/zh-CN.json @@ -893,5 +893,28 @@ "ApolloAuditLog.ParentSpan": "父操作", "ApolloAuditLog.FollowsFromSpan": "前操作", "ApolloAuditLog.FieldChangeHistory": "属性变动历史", - "ApolloAuditLog.InfluenceEntity": "影响的审计实体" + "ApolloAuditLog.InfluenceEntity": "影响的审计实体", + "Global.Title": "Value的全局搜索", + "Global.App": "应用ID", + "Global.Env": "环境", + "Global.Cluster": "集群名", + "Global.NameSpace": "命名空间", + "Global.Key": "Key", + "Global.Value": "Value", + "Global.ValueSearch.Tips" : "(模糊搜索,key可为配置项名称或content,value为配置项值)", + "Global.Operate" : "操作", + "Global.Expand" : "展开", + "Global.Abbreviate" : "缩略", + "Global.JumpToEditPage" : "跳转到编辑页面", + "Item.GlobalSearchByKey": "按照Key值检索", + "Item.GlobalSearchByValue": "按照Value值检索", + "Item.GlobalSearch": "查询", + "Item.GlobalSearchSystemError": "系统出错,请重试或联系系统负责人", + "Item.GlobalSearch.Tips": "搜索提示", + "ApolloGlobalSearch.NoData" : "暂无数据,请进行检索或者添加", + "Paging.TotalItems.part1" : "共", + "Paging.TotalItems.part2" : "条记录", + "Paging.DisplayNumber" : "条/页", + "Paging.PageNumberOne" : "首页", + "Paging.PageNumberLast" : "尾页" } diff --git a/apollo-portal/src/main/resources/static/img/nodata.png b/apollo-portal/src/main/resources/static/img/nodata.png new file mode 100644 index 0000000000000000000000000000000000000000..1cb236546eab3aec07dc619b493f125dedbe4fb9 GIT binary patch literal 21120 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 L+LT~?_v!xt2^mxi literal 0 HcmV?d00001 diff --git a/apollo-portal/src/main/resources/static/scripts/app.js b/apollo-portal/src/main/resources/static/scripts/app.js index bac2dd5fde8..88ecb81e522 100644 --- a/apollo-portal/src/main/resources/static/scripts/app.js +++ b/apollo-portal/src/main/resources/static/scripts/app.js @@ -64,6 +64,8 @@ var diff_item_module = angular.module('diff_item', ['app.service', 'apollo.direc var namespace_module = angular.module('namespace', ['app.service', 'apollo.directive', 'app.util', 'toastr', 'angular-loading-bar', 'valdr']); //server config var server_config_manage_module = angular.module('server_config_manage', ['app.service', 'apollo.directive', 'app.util', 'toastr', 'angular-loading-bar']); +// Value的全局检索 +var global_search_value_module = angular.module('global_search_value', ['app.service', 'apollo.directive', 'app.util', 'toastr', 'angular-loading-bar', 'ngSanitize']); //setting var setting_module = angular.module('setting', ['app.service', 'apollo.directive', 'app.util', 'toastr', 'angular-loading-bar', 'valdr']); //role diff --git a/apollo-portal/src/main/resources/static/scripts/controller/GlobalSearchValueController.js b/apollo-portal/src/main/resources/static/scripts/controller/GlobalSearchValueController.js new file mode 100644 index 00000000000..ee720aacc58 --- /dev/null +++ b/apollo-portal/src/main/resources/static/scripts/controller/GlobalSearchValueController.js @@ -0,0 +1,273 @@ +/* + * 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. + * + */ +global_search_value_module.controller('GlobalSearchValueController', + ['$scope', '$window', '$translate', 'toastr', 'AppUtil', 'GlobalSearchValueService', 'PermissionService', GlobalSearchValueController]); + +function GlobalSearchValueController($scope, $window, $translate, toastr, AppUtil, GlobalSearchValueService, PermissionService) { + + $scope.allItemInfo = []; + $scope.pageItemInfo = []; + $scope.itemInfoSearchKey = ''; + $scope.itemInfoSearchValue = ''; + $scope.needToBeHighlightedKey = ''; + $scope.needToBeHighlightedValue = ''; + $scope.isShowHighlightKeyword = []; + $scope.isAllItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword = []; + $scope.isAllItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword = []; + $scope.isAllItemInfoDisplayValueInARow = []; + $scope.isPageItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword = []; + $scope.isPageItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword = []; + $scope.isPageItemInfoDisplayValueInARow = []; + $scope.currentPage = 1; + $scope.pageSize = '10'; + $scope.totalItems = 0; + $scope.totalPages = 0; + $scope.pagesArray = []; + $scope.tempKey = ''; + $scope.tempValue = ''; + + $scope.getItemInfoByKeyAndValue = getItemInfoByKeyAndValue; + $scope.highlightKeyword = highlightKeyword; + $scope.jumpToTheEditingPage = jumpToTheEditingPage; + $scope.isShowAllValue = isShowAllValue; + $scope.convertPageSizeToInt = convertPageSizeToInt; + $scope.changePage = changePage; + $scope.getPagesArray = getPagesArray; + $scope.determineDisplayKeyOrValueWithoutShowHighlightKeyword = determineDisplayKeyOrValueWithoutShowHighlightKeyword; + $scope.determineDisplayValueInARow = determineDisplayValueInARow; + + init(); + function init() { + initPermission(); + } + + function initPermission() { + PermissionService.has_root_permission() + .then(function (result) { + $scope.isRootUser = result.hasPermission; + }); + } + + function getItemInfoByKeyAndValue(itemInfoSearchKey, itemInfoSearchValue) { + $scope.currentPage = 1; + $scope.itemInfoSearchKey = itemInfoSearchKey || ''; + $scope.itemInfoSearchValue = itemInfoSearchValue || ''; + $scope.allItemInfo = []; + $scope.pageItemInfo = []; + $scope.isAllItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword = []; + $scope.isAllItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword = []; + $scope.isAllItemInfoDisplayValueInARow = []; + $scope.isPageItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword = []; + $scope.isPageItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword = []; + $scope.isPageItemInfoDisplayValueInARow = []; + $scope.tempKey = itemInfoSearchKey || ''; + $scope.tempValue = itemInfoSearchValue || ''; + $scope.isShowHighlightKeyword = []; + GlobalSearchValueService.findItemInfoByKeyAndValue($scope.itemInfoSearchKey, $scope.itemInfoSearchValue) + .then(handleSuccess).catch(handleError); + function handleSuccess(result) { + let allItemInfo = []; + let isAllItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword = []; + let isAllItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword = []; + let isAllItemInfoDisplayValueInARow = []; + if(($scope.itemInfoSearchKey === '') && !($scope.itemInfoSearchValue === '')){ + $scope.needToBeHighlightedValue = $scope.itemInfoSearchValue; + $scope.needToBeHighlightedKey = ''; + result.body.forEach((itemInfo, index) => { + allItemInfo.push(itemInfo); + isAllItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword[index] = '0'; + isAllItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword[index] = determineDisplayKeyOrValueWithoutShowHighlightKeyword(itemInfo.value, itemInfoSearchValue); + isAllItemInfoDisplayValueInARow[index] = determineDisplayValueInARow(itemInfo.value, itemInfoSearchValue); + }); + }else if(!($scope.itemInfoSearchKey === '') && ($scope.itemInfoSearchValue === '')){ + $scope.needToBeHighlightedKey = $scope.itemInfoSearchKey; + $scope.needToBeHighlightedValue = ''; + result.body.forEach((itemInfo, index) => { + allItemInfo.push(itemInfo); + isAllItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword[index] = determineDisplayKeyOrValueWithoutShowHighlightKeyword(itemInfo.key, itemInfoSearchKey); + isAllItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword[index] = '0'; + }); + }else{ + $scope.needToBeHighlightedKey = $scope.itemInfoSearchKey; + $scope.needToBeHighlightedValue = $scope.itemInfoSearchValue; + result.body.forEach((itemInfo, index) => { + allItemInfo.push(itemInfo); + isAllItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword[index] = determineDisplayKeyOrValueWithoutShowHighlightKeyword(itemInfo.value, itemInfoSearchValue); + isAllItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword[index] = determineDisplayKeyOrValueWithoutShowHighlightKeyword(itemInfo.key, itemInfoSearchKey); + isAllItemInfoDisplayValueInARow[index] = determineDisplayValueInARow(itemInfo.value, itemInfoSearchValue); + }); + } + $scope.totalItems = allItemInfo.length; + $scope.allItemInfo = allItemInfo; + $scope.totalPages = Math.ceil($scope.totalItems / parseInt($scope.pageSize, 10)); + const startIndex = ($scope.currentPage - 1) * parseInt($scope.pageSize, 10); + const endIndex = Math.min(startIndex + parseInt($scope.pageSize, 10), allItemInfo.length); + $scope.pageItemInfo = allItemInfo.slice(startIndex, endIndex); + $scope.isAllItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword = isAllItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword; + $scope.isAllItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword = isAllItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword; + $scope.isAllItemInfoDisplayValueInARow = isAllItemInfoDisplayValueInARow; + $scope.isPageItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword = isAllItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword.slice(startIndex, endIndex); + $scope.isPageItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword = isAllItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword.slice(startIndex, endIndex); + $scope.isPageItemInfoDisplayValueInARow = isAllItemInfoDisplayValueInARow.slice(startIndex, endIndex); + getPagesArray(); + if(result.hasMoreData){ + toastr.warning(result.message, $translate.instant('Item.GlobalSearch.Tips')); + } + } + + function handleError(error) { + $scope.itemInfo = []; + toastr.error(AppUtil.errorMsg(error), $translate.instant('Item.GlobalSearchSystemError')); + } + } + + function convertPageSizeToInt() { + getItemInfoByKeyAndValue($scope.tempKey, $scope.tempValue); + } + + function changePage(page) { + if (page >= 1 && page <= $scope.totalPages) { + $scope.currentPage = page; + $scope.isShowHighlightKeyword = []; + $scope.isPageItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword = []; + $scope.isPageItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword = []; + $scope.isPageItemInfoDisplayValueInARow = []; + $scope.itemInfoSearchKey = $scope.tempKey; + $scope.itemInfoSearchValue = $scope.tempValue; + const startIndex = ($scope.currentPage - 1)* parseInt($scope.pageSize, 10); + const endIndex = Math.min(startIndex + parseInt($scope.pageSize, 10), $scope.totalItems); + $scope.pageItemInfo = $scope.allItemInfo.slice(startIndex, endIndex); + $scope.isPageItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword = $scope.isAllItemInfoDirectlyDisplayValueWithoutShowHighlightKeyword.slice(startIndex, endIndex); + $scope.isPageItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword = $scope.isAllItemInfoDirectlyDisplayKeyWithoutShowHighlightKeyword.slice(startIndex, endIndex); + $scope.isPageItemInfoDisplayValueInARow = $scope.isAllItemInfoDisplayValueInARow.slice(startIndex, endIndex); + getPagesArray(); + } + } + + function getPagesArray() { + const pageRange = 2; + let pagesArray = []; + let currentPage = $scope.currentPage; + let totalPages = $scope.totalPages; + if (totalPages <= (pageRange * 2) + 4) { + for (let i = 1; i <= totalPages; i++) { + pagesArray.push(i); + } + } else { + if (currentPage <= (pageRange + 2)) { + for (let i = 1; i <= pageRange * 2 + 2; i++) { + pagesArray.push(i); + } + pagesArray.push('...'); + pagesArray.push(totalPages); + } else if (currentPage >= (totalPages - (pageRange + 1))) { + for (let i = totalPages - pageRange * 2 - 1 ; i <= totalPages; i++) { + pagesArray.push(i); + } + pagesArray.unshift('...'); + pagesArray.unshift(1); + } else { + for (let i = (currentPage - pageRange); i <= currentPage + pageRange; i++) { + pagesArray.push(i); + } + pagesArray.unshift('...'); + pagesArray.unshift(1); + pagesArray.push('...'); + pagesArray.push(totalPages); + } + } + $scope.pagesArray = pagesArray; + } + + function determineDisplayValueInARow(value, highlight) { + var valueColumn = document.getElementById('valueColumn'); + var testElement = document.createElement('span'); + setupTestElement(testElement, valueColumn); + testElement.innerText = value; + document.body.appendChild(testElement); + const position = determinePosition(value, highlight); + let displayValue = '0'; + if (testElement.scrollWidth > testElement.offsetWidth) { + displayValue = position; + } else { + if (testElement.scrollWidth === testElement.offsetWidth) { + return '0'; + } + switch (position) { + case '1': + testElement.innerText = value + '...' + '| ' + $translate.instant('Global.Expand'); + break; + case '2': + testElement.innerText = '...' + value + '| ' + $translate.instant('Global.Expand'); + break; + case '3': + testElement.innerText = '...' + value + '...' + '| ' + $translate.instant('Global.Expand'); + break; + default: + return '0'; + } + if (testElement.scrollWidth === testElement.offsetWidth) { + displayValue = '0'; + } else { + displayValue = position; + } + } + document.body.removeChild(testElement); + return displayValue; + } + + function setupTestElement(element, valueColumn) { + element.style.visibility = 'hidden'; + element.style.position = 'absolute'; + element.style.whiteSpace = 'nowrap'; + element.style.display = 'inline-block'; + element.style.fontFamily = '"Open Sans", sans-serif'; + const devicePixelRatio = window.devicePixelRatio; + const zoomLevel = Math.round((window.outerWidth / window.innerWidth) * 100) / 100; + element.style.fontSize = 13 * devicePixelRatio * zoomLevel + 'px'; + element.style.padding = 8 * devicePixelRatio * zoomLevel + 'px'; + element.style.width = valueColumn.offsetWidth * devicePixelRatio * zoomLevel + 'px'; + } + + function determinePosition(value, highlight) { + const position = value.indexOf(highlight); + if (position === -1) return '-1'; + if (position === 0) return '1'; + if (position + highlight.length === value.length) return '2'; + return "3"; + } + + function determineDisplayKeyOrValueWithoutShowHighlightKeyword(keyorvalue, highlight) { + return keyorvalue === highlight ? '0' : '-1'; + } + + function jumpToTheEditingPage(appid,env,cluster){ + let url = AppUtil.prefixPath() + "/config.html#/appid=" + appid + "&" +"env=" + env + "&" + "cluster=" + cluster; + window.open(url, '_blank'); + } + + function highlightKeyword(fulltext,keyword) { + if (!keyword || keyword.length === 0) return fulltext; + let regex = new RegExp("(" + keyword + ")", "g"); + return fulltext.replace(regex, '$1'); + } + + function isShowAllValue(index){ + $scope.isShowHighlightKeyword[index] = !$scope.isShowHighlightKeyword[index]; + } + +} diff --git a/apollo-portal/src/main/resources/static/scripts/services/GlobalSearchValueService.js b/apollo-portal/src/main/resources/static/scripts/services/GlobalSearchValueService.js new file mode 100644 index 00000000000..52a345449b7 --- /dev/null +++ b/apollo-portal/src/main/resources/static/scripts/services/GlobalSearchValueService.js @@ -0,0 +1,40 @@ +/* + * 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. + * + */ +appService.service('GlobalSearchValueService', ['$resource', '$q', 'AppUtil', function ($resource, $q, AppUtil) { + let global_search_resource = $resource('', {}, { + get_item_Info_by_key_and_Value: { + isArray: false, + method: 'GET', + url: AppUtil.prefixPath() + '/global-search/item-info/by-key-or-value', + params: { + key: 'key', + value: 'value' + } + } + }); + return { + findItemInfoByKeyAndValue:function (key,value){ + let d = $q.defer(); + global_search_resource.get_item_Info_by_key_and_Value({key: key,value: value},function (result) { + d.resolve(result); + }, function (error) { + d.reject(error); + }); + return d.promise; + } + } +}]); diff --git a/apollo-portal/src/main/resources/static/views/common/nav.html b/apollo-portal/src/main/resources/static/views/common/nav.html index c2f73b213dd..a78f2b68dd0 100644 --- a/apollo-portal/src/main/resources/static/views/common/nav.html +++ b/apollo-portal/src/main/resources/static/views/common/nav.html @@ -66,6 +66,7 @@
  • {{'Common.Nav.SystemInfo' | translate }}
  • {{'Common.Nav.ConfigExport' | translate }}
  • {{'ApolloAuditLog.Title' | translate }}
  • +
  • {{'Global.Title' | translate }}
  • diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/GlobalSearchControllerTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/GlobalSearchControllerTest.java new file mode 100644 index 00000000000..03231dcc59e --- /dev/null +++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/controller/GlobalSearchControllerTest.java @@ -0,0 +1,135 @@ +/* + * 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. + * + */ +package com.ctrip.framework.apollo.portal.controller; + +/** + * @author hujiyuan 2024-08-10 + */ + +import com.ctrip.framework.apollo.common.http.SearchResponseEntity; +import com.ctrip.framework.apollo.portal.component.config.PortalConfig; +import com.ctrip.framework.apollo.portal.entity.vo.ItemInfo; +import com.ctrip.framework.apollo.portal.service.GlobalSearchService; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import java.util.*; + +import static org.mockito.Mockito.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@RunWith(MockitoJUnitRunner.class) +public class GlobalSearchControllerTest { + + private MockMvc mockMvc; + + @Mock + private PortalConfig portalConfig; + + @Mock + private GlobalSearchService globalSearchService; + + @InjectMocks + private GlobalSearchController globalSearchController; + + private final int perEnvSearchMaxResults = 200; + + @Before + public void setUp() { + when(portalConfig.getPerEnvSearchMaxResults()).thenReturn(perEnvSearchMaxResults); + mockMvc = MockMvcBuilders.standaloneSetup(globalSearchController).build(); + } + + @Test + public void testGet_ItemInfo_BySearch_WithKeyAndValueAndActiveEnvs_ReturnEmptyItemInfos() throws Exception { + when(globalSearchService.getAllEnvItemInfoBySearch(anyString(), anyString(),eq(0),eq(perEnvSearchMaxResults))).thenReturn(SearchResponseEntity.ok(new ArrayList<>())); + mockMvc.perform(MockMvcRequestBuilders.get("/global-search/item-info/by-key-or-value") + .contentType(MediaType.APPLICATION_JSON) + .param("key", "query-key") + .param("value", "query-value")) + .andExpect(status().isOk()) + .andExpect(content().json("{\"body\":[],\"hasMoreData\":false,\"message\":\"OK\",\"code\":200}")); + verify(portalConfig,times(1)).getPerEnvSearchMaxResults(); + verify(globalSearchService,times(1)).getAllEnvItemInfoBySearch(anyString(), anyString(),eq(0),eq(perEnvSearchMaxResults)); + } + + @Test + public void testGet_ItemInfo_BySearch_WithKeyAndValueAndActiveEnvs_ReturnExpectedItemInfos_ButOverPerEnvLimit() throws Exception { + List allEnvMockItemInfos = new ArrayList<>(); + allEnvMockItemInfos.add(new ItemInfo("appid1","env1","cluster1","namespace1","query-key","query-value")); + allEnvMockItemInfos.add(new ItemInfo("appid2","env2","cluster2","namespace2","query-key","query-value")); + when(globalSearchService.getAllEnvItemInfoBySearch(eq("query-key"), eq("query-value"),eq(0),eq(perEnvSearchMaxResults))).thenReturn(SearchResponseEntity.okWithMessage(allEnvMockItemInfos,"In DEV , PRO , more than "+perEnvSearchMaxResults+" items found (Exceeded the maximum search quantity for a single environment). Please enter more precise criteria to narrow down the search scope.")); + mockMvc.perform(MockMvcRequestBuilders.get("/global-search/item-info/by-key-or-value") + .contentType(MediaType.APPLICATION_JSON) + .param("key", "query-key") + .param("value", "query-value")) + .andExpect(status().isOk()) + .andExpect(content().json("{\"body\":[" + + " { \"appId\": \"appid1\",\n" + + " \"envName\": \"env1\",\n" + + " \"clusterName\": \"cluster1\",\n" + + " \"namespaceName\": \"namespace1\",\n" + + " \"key\": \"query-key\",\n" + + " \"value\": \"query-value\"}," + + " { \"appId\": \"appid2\",\n" + + " \"envName\": \"env2\",\n" + + " \"clusterName\": \"cluster2\",\n" + + " \"namespaceName\": \"namespace2\",\n" + + " \"key\": \"query-key\",\n" + + " \"value\": \"query-value\"}],\"hasMoreData\":true,\"message\":\"In DEV , PRO , more than 200 items found (Exceeded the maximum search quantity for a single environment). Please enter more precise criteria to narrow down the search scope.\",\"code\":200}")); + verify(portalConfig,times(1)).getPerEnvSearchMaxResults(); + verify(globalSearchService, times(1)).getAllEnvItemInfoBySearch(eq("query-key"), eq("query-value"),eq(0),eq(perEnvSearchMaxResults)); + } + + @Test + public void testGet_ItemInfo_BySearch_WithKeyAndValueAndActiveEnvs_ReturnExpectedItemInfos() throws Exception { + List allEnvMockItemInfos = new ArrayList<>(); + allEnvMockItemInfos.add(new ItemInfo("appid1","env1","cluster1","namespace1","query-key","query-value")); + allEnvMockItemInfos.add(new ItemInfo("appid2","env2","cluster2","namespace2","query-key","query-value")); + when(globalSearchService.getAllEnvItemInfoBySearch(eq("query-key"), eq("query-value"),eq(0),eq(perEnvSearchMaxResults))).thenReturn(SearchResponseEntity.ok(allEnvMockItemInfos)); + + mockMvc.perform(MockMvcRequestBuilders.get("/global-search/item-info/by-key-or-value") + .contentType(MediaType.APPLICATION_JSON) + .param("key", "query-key") + .param("value", "query-value")) + .andExpect(status().isOk()) + .andExpect(content().json("{\"body\":[" + + " { \"appId\": \"appid1\",\n" + + " \"envName\": \"env1\",\n" + + " \"clusterName\": \"cluster1\",\n" + + " \"namespaceName\": \"namespace1\",\n" + + " \"key\": \"query-key\",\n" + + " \"value\": \"query-value\"}," + + " { \"appId\": \"appid2\",\n" + + " \"envName\": \"env2\",\n" + + " \"clusterName\": \"cluster2\",\n" + + " \"namespaceName\": \"namespace2\",\n" + + " \"key\": \"query-key\",\n" + + " \"value\": \"query-value\"}],\"hasMoreData\":false,\"message\":\"OK\",\"code\":200}")); + verify(globalSearchService, times(1)).getAllEnvItemInfoBySearch(eq("query-key"), eq("query-value"),eq(0),eq(perEnvSearchMaxResults)); + } + +} diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/service/GlobalSearchServiceTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/service/GlobalSearchServiceTest.java new file mode 100644 index 00000000000..661a692447d --- /dev/null +++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/service/GlobalSearchServiceTest.java @@ -0,0 +1,127 @@ +/* + * 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. + * + */ +package com.ctrip.framework.apollo.portal.service; + +/** + * @author hujiyuan 2024-08-10 + */ + +import com.ctrip.framework.apollo.common.dto.ItemInfoDTO; +import com.ctrip.framework.apollo.common.dto.PageDTO; +import com.ctrip.framework.apollo.common.http.SearchResponseEntity; +import com.ctrip.framework.apollo.portal.api.AdminServiceAPI; +import com.ctrip.framework.apollo.portal.component.PortalSettings; +import com.ctrip.framework.apollo.portal.entity.vo.ItemInfo; +import com.ctrip.framework.apollo.portal.environment.Env; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.data.domain.PageRequest; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class GlobalSearchServiceTest { + + @Mock + private AdminServiceAPI.ItemAPI itemAPI; + + @Mock + private PortalSettings portalSettings; + + @InjectMocks + private GlobalSearchService globalSearchService; + + private final List activeEnvs = new ArrayList<>(); + + @Before + public void setUp() { + when(portalSettings.getActiveEnvs()).thenReturn(activeEnvs); + } + + @Test + public void testGet_PerEnv_ItemInfo_BySearch_withKeyAndValue_ReturnExpectedItemInfos() { + activeEnvs.add(Env.DEV); + activeEnvs.add(Env.PRO); + + ItemInfoDTO itemInfoDTO = new ItemInfoDTO("TestApp","TestCluster","TestNamespace","TestKey","TestValue"); + List mockItemInfoDTOs = new ArrayList<>(); + mockItemInfoDTOs.add(itemInfoDTO); + Mockito.when(itemAPI.getPerEnvItemInfoBySearch(any(Env.class), eq("TestKey"), eq("TestValue"), eq(0), eq(1))).thenReturn(new PageDTO<>(mockItemInfoDTOs, PageRequest.of(0, 1), 1L)); + SearchResponseEntity> mockItemInfos = globalSearchService.getAllEnvItemInfoBySearch("TestKey", "TestValue", 0, 1); + assertEquals(2, mockItemInfos.getBody().size()); + + List devMockItemInfos = new ArrayList<>(); + List proMockItemInfos = new ArrayList<>(); + List allEnvMockItemInfos = new ArrayList<>(); + devMockItemInfos.add(new ItemInfo("TestApp", Env.DEV.getName(), "TestCluster", "TestNamespace", "TestKey", "TestValue")); + proMockItemInfos.add(new ItemInfo("TestApp", Env.PRO.getName(), "TestCluster", "TestNamespace", "TestKey", "TestValue")); + allEnvMockItemInfos.addAll(devMockItemInfos); + allEnvMockItemInfos.addAll(proMockItemInfos); + + verify(itemAPI,times(2)).getPerEnvItemInfoBySearch(any(Env.class), eq("TestKey"), eq("TestValue"), eq(0), eq(1)); + verify(portalSettings,times(1)).getActiveEnvs(); + assertEquals(allEnvMockItemInfos.toString(), mockItemInfos.getBody().toString()); + } + + @Test + public void testGet_PerEnv_ItemInfo_withKeyAndValue_BySearch_ReturnEmptyItemInfos() { + activeEnvs.add(Env.DEV); + activeEnvs.add(Env.PRO); + Mockito.when(itemAPI.getPerEnvItemInfoBySearch(any(Env.class), anyString(), anyString(), eq(0), eq(1))) + .thenReturn(new PageDTO<>(new ArrayList<>(), PageRequest.of(0, 1), 0L)); + SearchResponseEntity> result = globalSearchService.getAllEnvItemInfoBySearch("NonExistentKey", "NonExistentValue", 0, 1); + assertEquals(0, result.getBody().size()); + } + + @Test + public void testGet_PerEnv_ItemInfo_BySearch_withKeyAndValue_ReturnExpectedItemInfos_ButOverPerEnvLimit() { + activeEnvs.add(Env.DEV); + activeEnvs.add(Env.PRO); + + ItemInfoDTO itemInfoDTO = new ItemInfoDTO("TestApp","TestCluster","TestNamespace","TestKey","TestValue"); + List mockItemInfoDTOs = new ArrayList<>(); + mockItemInfoDTOs.add(itemInfoDTO); + Mockito.when(itemAPI.getPerEnvItemInfoBySearch(any(Env.class), eq("TestKey"), eq("TestValue"), eq(0), eq(1))).thenReturn(new PageDTO<>(mockItemInfoDTOs, PageRequest.of(0, 1), 2L)); + SearchResponseEntity> mockItemInfos = globalSearchService.getAllEnvItemInfoBySearch("TestKey", "TestValue", 0, 1); + assertEquals(2, mockItemInfos.getBody().size()); + + List devMockItemInfos = new ArrayList<>(); + List proMockItemInfos = new ArrayList<>(); + List allEnvMockItemInfos = new ArrayList<>(); + devMockItemInfos.add(new ItemInfo("TestApp", Env.DEV.getName(), "TestCluster", "TestNamespace", "TestKey", "TestValue")); + proMockItemInfos.add(new ItemInfo("TestApp", Env.PRO.getName(), "TestCluster", "TestNamespace", "TestKey", "TestValue")); + allEnvMockItemInfos.addAll(devMockItemInfos); + allEnvMockItemInfos.addAll(proMockItemInfos); + String message = "In DEV , PRO , more than 1 items found (Exceeded the maximum search quantity for a single environment). Please enter more precise criteria to narrow down the search scope."; + verify(itemAPI,times(2)).getPerEnvItemInfoBySearch(any(Env.class), eq("TestKey"), eq("TestValue"), eq(0), eq(1)); + verify(portalSettings,times(1)).getActiveEnvs(); + assertEquals(allEnvMockItemInfos.toString(), mockItemInfos.getBody().toString()); + assertEquals(message, mockItemInfos.getMessage()); + } + +} diff --git a/changes/changes-2.4.0.md b/changes/changes-2.4.0.md new file mode 100644 index 00000000000..615d7c1d7a4 --- /dev/null +++ b/changes/changes-2.4.0.md @@ -0,0 +1,13 @@ +Changes by Version +================== +Release Notes. + +Apollo 2.4.0 + +------------------ +* [Update the server config link in system info page](https://github.com/apolloconfig/apollo/pull/5204) +* [Feature support portal restTemplate Client connection pool config](https://github.com/apolloconfig/apollo/pull/5200) +* [Feature added the ability for administrators to globally search for Value](https://github.com/apolloconfig/apollo/pull/5182) + +------------------ +All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1) diff --git a/docs/en/README.md b/docs/en/README.md index 46fb251756f..d76c4540aa2 100644 --- a/docs/en/README.md +++ b/docs/en/README.md @@ -37,6 +37,10 @@ Demo Environment: * **Grayscale release** * Support grayscale configuration release, for example, after clicking release, it will only take effect for some application instances. After a period of observation, we could push the configurations to all application instances if there is no problem +- **Global Search Configuration Items** + - A fuzzy search of the key and value of a configuration item finds in which application, environment, cluster, namespace the configuration item with the corresponding value is used + - It is easy for administrators and SRE roles to quickly and easily find and change the configuration values of resources by highlighting, paging and jumping through configurations + * **Authorization management, release approval and operation audit** * Great authorization mechanism is designed for applications and configurations management, and the management of configurations is divided into two operations: editing and publishing, therefore greatly reducing human errors * All operations have audit logs for easy tracking of problems diff --git a/docs/en/deployment/distributed-deployment-guide.md b/docs/en/deployment/distributed-deployment-guide.md index 3251d4c5d5e..4dbe8dd7586 100644 --- a/docs/en/deployment/distributed-deployment-guide.md +++ b/docs/en/deployment/distributed-deployment-guide.md @@ -769,9 +769,9 @@ apollo.service.registry.cluster=same name with apollo Cluster ``` 2. (optional) If you want to customize Config Service and Admin Service's uri for Client, -for example when deploying on the intranet, -if you don't want to expose the intranet ip, -you can add a property in `config/application-github.properties` of the Config Service and Admin Service installation package + for example when deploying on the intranet, + if you don't want to expose the intranet ip, + you can add a property in `config/application-github.properties` of the Config Service and Admin Service installation package ```properties apollo.service.registry.uri=http://your-ip-or-domain:${server.port}/ ``` @@ -1447,6 +1447,14 @@ The default is true, which makes it easy to quickly search for configurations by If set to false, this feature is disabled +### 3.1.14 apollo.portal.search.perEnvMaxResults - set the Administrator Tool-Global Search for Value function's maximum number of search results for a single individual environment + +> For versions 2.4.0 and above + +Default is 200, which means that each environment will return up to 200 results in a single search operation. + +Modifying this parameter may affect the performance of the search function, so before modifying it, you should conduct sufficient testing and adjust the value of `apollo.portal.search.perEnvMaxResults` appropriately according to the actual business requirements and system resources to balance the performance and the number of search results. + ## 3.2 Adjusting ApolloConfigDB configuration Configuration items are uniformly stored in the ApolloConfigDB.ServerConfig table. It should be noted that each environment's ApolloConfigDB.ServerConfig needs to be configured separately, and the modification takes effect in real time for one minute afterwards. diff --git a/docs/en/design/apollo-design.md b/docs/en/design/apollo-design.md index f078c1881d2..0061f696f20 100644 --- a/docs/en/design/apollo-design.md +++ b/docs/en/design/apollo-design.md @@ -130,7 +130,7 @@ Why do we use Eureka as a service registry instead of the traditional zk and etc ### 1.3.2 Admin Service * Provide configuration management interface -* Provides interfaces for configuration modification, publishing, etc. +* Provides interfaces for configuration modification, publishing, retrieval, etc. * Interface service object is Portal ### 1.3.3 Meta Server diff --git a/docs/en/design/apollo-introduction.md b/docs/en/design/apollo-introduction.md index a001f5a3a81..f846e6be867 100644 --- a/docs/en/design/apollo-introduction.md +++ b/docs/en/design/apollo-introduction.md @@ -78,7 +78,13 @@ It is precisely based on the particularity of configuration that Apollo has been * **Client configuration information monitoring** * You can easily see which instances the configuration is being used on the interface +**Global Search Configuration Items** + +- A fuzzy search of the key and value of a configuration item finds in which application, environment, cluster, namespace the configuration item with the corresponding value is used +- It is easy for administrators and SRE roles to quickly and easily find and change the configuration values of resources by highlighting, paging and jumping through configurations + **Java and .Net native clients available** + * Provides native clients of Java and .Net for easy application integration * Support Spring Placeholder, Annotation and Spring Boot's ConfigurationProperties for easy application use (requires Spring 3.1.1+) * Also provides Http interface, non-Java and .Net applications can also be easily used diff --git a/docs/en/images/Configuration query-Non properties.png b/docs/en/images/Configuration query-Non properties.png new file mode 100644 index 0000000000000000000000000000000000000000..1a355074cbc6c388f74598aab746a66592d59737 GIT binary patch literal 35238 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%0DwvXUCoU)FC>beq-skNft&+I{;|S#(j3<6j)Y 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 literal 0 HcmV?d00001 diff --git a/docs/en/images/Configuration query-properties.png b/docs/en/images/Configuration query-properties.png new file mode 100644 index 0000000000000000000000000000000000000000..2f8fd7711093c9a116aaf0f71b1d2b41d6b92fa5 GIT binary patch literal 35102 zcmeFZcUV)|`Znx2b5I8qbfhSCM#iyFGKe&RWUyt%hKMvN5fNz#y$2FS0Tm&G3aC^? zMMM&6LP;5EK?4Lt2qBON5fDNMAtWKBy}@(N?>*;__qx76zw7(nbzOm-owe8AYprKJ z<$mt9?_M|$-mb2%zG>5@?SK4!;^L-FTS7N&`rC`Gn^k94Qxkkt|Na(t5qxYDdcbgA z_2nNSF6Ue}ZK_9U$O6?=-@lLk-6w9-Cf$7-pTGTa@#p1Do9;CJal++NasXGJ({4Bu zxPOgW{wtN2lDJ>0wbsSoa1)RvF9Q_;T9Gaa0{!|yy^vHyl9cGddiLR*P^^~h4jzkO?{6ubpLsHCG$Y& z-~M^%*?Ui~3FDWx&xybKaB8r=bBeb#u}cw7#YT_hmT#p^%HBs)-Z46l?+Y_pnIecKM(s*j7dL4lkIZd-psTuauNqgPN6ns0TH15qN>qz5s?$O|VI1ig zAJQ_fVVCVHlZ$Rj(Z2ED#H6Rp{E*z;7e}?6L zOrTU@wu?=amy_pbiaN|N_#Z5ZsaAf;GslwGTSGd}LB<$(-Z`XE*->%4gN^F>uI%fo ztTlAdsI%?jrc9u}+Z6UV#YH-{3zdyxCBrb$at=Xbs;ip!1((i)vox}avYF`5==Kg8 zNz}Y7i=)nfDNE9Yn;YZ&)p@q3T>%87s%GUz!A?^9jvLLLiAI|~S`BB2Mi!AA6Zo4o;)O%d(3++2qj~he4z`w??wrg&he;no_{g_$QSp`%+33#8{S&J-& zC<_3{l!}tt6(&eNECXtRQlGmGF13qq3>&Jh{!6|7f8W2QBK_dvxN<(={kk0vd5EOQ zV?^=HLxPy1LfjXzgMLyp7)_L) z9mCDbr{p|9V;CfHdrbM7HPva?QVTmT`u)D5+jfNcb1Bm#b^_lP@|`_7FOo`@iYjM< zM~N%WfCgu$3jy1QRNeV%z;YAH6)# z>V*_7)c|_?s9(O(yf2rVTJsndGV$ED&{%_lIcn_PV!r6;AK)EapDbTWA{khcWGl;9 z9Kiz5HsWoSih0ysO8KZtCP)@c@E?xl#@+|Wl8Ze{^XXF!i&o#gjKmXz(&wGQ1)1rl zqphidExm1fhLed_ORO{U6yvQ7N1i)X|KMSRnMHj#>}+?R#(45S7KOOQMU#`#YS-J7 z<;t_k3lqZ|53{Yo(ywI)Vzx?g4ro$!zEoW~e~x5;SPDV@G6DZ;HsidpN`oze!HW4? z?H%Y_Lg$I9PUm)XKe~PYL@d{0$~%QSooB&UoQnaX38L|6%B>ll#^uiz^f+@{6hEOa z6gV)kE74+I*1dwFTrG*Gh6s%ZUbpr_z+#ynoQsa8zH2Edrio{*ky=l2+g)vI17XzX z!+RP^1SwIjxMV73GQk!5{YCtDctm9o-Eoc^miT_jI`%00Y>=@w z=gVL4N5Rds;k4p0sp-uYd=b&8Pr2iYI`ZenmUWp9B_3}&m?M2fJXez9Vo;j0aJNp# z!--FiKPHXG`6quahEStW!SmUp7@kfOa#cz)S?Wc6?)$i3Pj}_ms+hUjKDCRk)#5#< z_yADGt|`PM2s3Ero5lIP|AiXN<3SIlimI)~b)HEW5elWb#oaj_4HqT~&WQ{EFk@Q1 zO>`-CNIZi*dy3f8V&10SB}(W%Y*0eAQfF4{_;5HYi$Rq}57V7A%U# zBbE*X8DQY~b5Xkwgi>Z+mISMDnFM%6NiHq7VO$N?cmSsNYs`Mx59;@CnubQaJkP3uzDPoUQ^2tC#0pk!|>Ww~Hg&4w5XH*cf(?jCfAqb_zdQ z;y}Pyq1zjs4Q@^Hd;>BHg0EwCn4w-<^d#3aw-I^Vm;h%lN1-q}Ul{mQ4K|1W)Hrrz z_T3#?>)!6!Xo_WMRs7~#{7-Y3-4u5D-n0xFw!`y9MK1UQg6m7}7_sxGfgXr@TyM9d zL>j#qBsYSPVLSUBQZXliZ0YY94xwhT)`So(kc6!~I)PxoQVvStQK{~0b|5}GkjJDJ zil3t6s6c~(>r?~RkO}Og3|mV|RXHMc7Q9=fg_p-|NAqO(*KdjfPVQj<+$k%y_)CD; zb{ih)X9@n%N!E+u-Q;_^P2KSeFQqXDgsCox~-=aQ_ zrm>#PMnvkir01aepL)6!>&-SA#tlwQw8l+g6bqQt8K^7?ANgp+BD6U8Bu!H^q!tZoeCE6>o++u@Y+Pr_Xca(d>opsF$ zDT8D2%?FH?3kx*{`?x{|cIM^m&=%vZ5Lt&_$1_aYJ`h>3<2>}TMtbvgZ^9^OBAg#sWMs_ea2CvtsK=~$lF z2aw6AnJ}6>fqg`qt=+`8QTmH09`dF>lCYSRZxV*T%qrDT+0F6h3DeM^)GB+lPI$Nc zgQ+Hk)AM03c)utI_tQ+MhdK82%({FftZObz`$p}>#+nM~S!*UzC423cjL07gqoRNm z+D?F-`q0HF$)lp8kS?c0CooNR#Qfs2=Q{i6L(Hmyl-|Rul5jPc6*C(T?(De?%o<1e zyx5(s$&w5trK-VX;;Wv`6}5TmoZp)Tc%MPbi<`C?z8f)5Sg2w_&)dCfgEWTTLHYD& zh?0=vq)s6XQalL3C4g@>y> z@@$NYC&i)o3Bp^A?xqDwgWFr&ht?h#J$`y-mh+m*a4Hz=?})ns9ih+yGiUy?H$gif zIGp1pC^VABv$kNLZImDqL&gQo`ls*%R?Okq#2TX0bUcFtg{Rq&1K{vBZx;shy6tG| zfN6EH;3ElXWx_z&&Q%dKQE9{;na40C>Ohn7&NBj(d~^jaJo~aDSB+gnT+d9fpK#4H4aU^h>_ivnF@j*ELuQMZ#w9DhQFNQ)(#T^8Z+FWts_IKZ`= zTDScjld_ZwmJ%L-wmexvOFlZ|9UYK351B2hsq!fo_n4MzW zMYX0<$Oijhew$;~K?)4QoJ8fQR(}Uc`Vz7Y#K$N-5R<$R%!~nR?G%F5tv(JuU$Qky z!UlaX(G#3eiiDEYX*t`Cq@1}`beo4%-jxwmR`A#Dl!KwteeNmo*HWsHzHX<48g-*H zm)#!cWRCW()JoQ5cX~wfnvczh^qK=^AFzwSW>fPVPIxA?y~1LyS@~4j9i$YVds9;Z z1GL8)8({31Rq~dTuRP3*G=fw16m|aEzyr``SDy+V93=lCkn7|9kmVvC zs~q&ojE^1pc%aNz%^U4pmR*=Qa@eGBe1)>D&|m9*a+NF}?;re&5q0Vy3sz-oF#R^0{i;lOGC=jZ6{)+ zuqi!=QbWR4AeB6&K|tcq8{wOK!)k`eyCuX+AiduH(J~bNGSsy8`JqC8ZbviP|0$MI z|C-Yl3w-k&T5-Q$C-^evc7!wSio?@`XS$xLvBp09o&WB73e9{^G3MD0Oay5ZeK&ZO5kjdyEfZz^D;n={{qd5I%{*PB&t>G*W=`gCBGeID)Hj5b*U zcY|!Mr5T+;EfA`e?GK9f_RrXQ-`NH!AP)yy*4n zsi@DpT%C3@6{EOB-GUu;HBT&Lo~^-$7FhMK*bE=~Z9VwR z%VZ3k(hzXMbH2d(R`>hg@;@P!ls=2vFi2fXGE&nH`)%gL3Z6J7!bIMYpS zXh2~?oa_h<6^f5V!`IVq4a4+9^wX<}yv%pv$ki3IT=PWs%AR`Wkq`aE_0(}c3}={A zmS`1_BYqLF?GrhvZU>`x};&3&!Y4+@*k5KXIsTUWoh z{@)y=IizKcsW>}UNl~_5K%H^}h&idyH|ta% z|GEXV=Cal(ChjLW8#}<4KTk?Xto1_BK+*A{HRmQi5Gl#mLYaqxlHa$<)?Jszh8$|w z#*&C~m}DmwE?%<0sPMNNGkYSy2=gb2le_Ya7RyJI<6CJSqlLkq;5}Xi(YMq>C+{47 zt4NCr7@VatigC&5K1&I@PnyZXPsYRUW}5}H5lEuxoHH}&v>Nv>!K0w?w;74~DR+1< z_`17AXw6Ky1!i{lonilLdkU%fAj|Te&ttd4d?zz(eG{h7OU5;2vzlw`!pbxE6J2%y zXh|QFCYdC|@|rZJ_uoH#sNbU7!?+7FXAWjy3FoheGj59#{|j&LIPOl2wk;NR?enNvtF`ib`GMP*0;?-qjdi>lTn!*Jlw! zElIbSJHN`aUP#Eb{MvJ&pg@wD_+bsdT7-es2VWxgz#C?q zmyJ8NZG^tHP7+Na!p)6)0+9F-{&)-KekLyLcc1+Cl*i+TE%Fj;^bXh3`~lz( z52{HeZ^UmPH@*y;wD>u{YC-b&9$_B3P7?g5+1gYnFJYS#GOcfc=z7MDP}coXNE9~H ziqW4ms$XNz+k6n%*NW0LVgeYPbMSGlblW{D!&y#yaMj4yNfJeJQOwn|5bO7qT+piVk)nev)?-{Js-a-S0f(b|TzA~0dKVIDZUo=`Yq zmvxLwdvxda+{zH;qBHONxw3Ry{PMI`haQi#e^DU|=@_N@vCKl0DwukiMG}Wfz79=m zJzmJ}3lS}Tk6fO$%VsJ1S=&fTI7v@2`>?zZ6`yY`~EC8g!9$42!d;q>@0eBMfWc;V{E5la07mT}SCpD8u*_xsaIK4A=Y>+ym8WNj*| zZ2#lZ5kP2Vc((k{`}L;IOM_8*f5c>`leDY3b=0KKWX^)xZz-&s({G5C$b_zR0)Nx% z}&{pOz%uzk2K!`SJ^K; zQj`)$2xpZQJwL8K{`!zl%@JDbJN_6;grzC30`Kv*VwSA!3&9{ry5i0JjGeYVqv~D# za9ZvBla>m(%8zv<#EPmA)B8K8VRCV!>2F4(3bnHS^e@z&7ut z2^>aj=yCO+?0sftnz1MZ^hmMXAGM^@&s zVV?@406ve3_=pOOa4)<$tFVaheqdVTaFNsdeN>EadTR$Wvu*(j|(ci9qi&8UU1 za0E%-=c{pzaI7*{j#4%PnYT|kxs}{V6UJer^yRj4$&+OkQ6c=dbm=z9kC~qd$@eU9 z7iQFZ-Y<11^?$Qu{lc7*(=c`NvRg~xj293@j~kh*@raxivph-~T9e98{qeZ^m3u&+ z&8t1-n8&k<|~iEyd&PP(Jl6QF#Qn_0Zz0x_a=lq;y~x4=oaC#+Q<-`Jy{HvAa#UG_$IHLr z*1jrapf^J#lL)57N&#;MN7xTvLfXgv>?EFuldNhoZrtJfT;_1}mH$Jk^7YloV=6GUYHz@YCaKz& zJA31Pq;qm8Zv-jpD$vv3(C?QV-QpOZQ6}q;0En<{b6J(0@)pLi@GtJixQT5}t~Rq- zeQ|uJN-|}|CMWH2^C$Jov+uqOgjfD{DDQUh=*~QDEaAXxkNw|w1vGv5ViM{hB zPd*Id>NM5=D1WDgxG)gNa(4yp2@p4e9_pd%cNmdJYiVCzer;z;24s&uc(@o;@rRYO z`vqORhTI~yFbc`>T{+ymBIhANGA1(BU4?@~oH|r-iV7Urh(TsD!_y}6JF^9clT^WK zH?LeSp~<0&mShqwWccoI&TuYDQUFoBTpkNT&4@Sr$T06X#}%*oirTK}xOwo?0aUYM z{S|DKy?tD}>Ff){Bfd6;eoVU6b;kUDp~CH1dQJL2VC%nFhAL;Yl+hS-IiSezfnJkV znt(I)#x?zUV+CQRjM16tW%;FQu~x74;m^P%Cw-g33SJ7WJyngn1+;wg)E}269XG$5 za%BnkPpzOL^;6ZqV6%-=58#ymg%fDprm)<@MOs5z;dS`3R>&z#(J1aV2+o6duIJt#sf?(5-7WkzQ#L;r50fJ-KsS;&-Cw|wi8G_1`T{)`V z^sDK}P`fqgnBG(ErcHn5sXRYO@+2oUb6!;)*O4nf${ez@EzciemOYQ7D z{1InSuU&`8H!*7i|NUpO8zIVCmPm=azhx8$a3muI3Mm1=QPLTRtb5>?&H4Sml&y>V zNn-dE8Sw?YGQ5{eCh|eDY<$Iimat9muzVYGZPre#KJFOVUR%Y>JcMX~&+!`V#9 zOS{qB{Vl5-;{xYBJ3a-Ge?^HtjFzw8E#I1ioIfMkS>fsow@u@;qi(bJlXQ%cEAatX zVCu@XwMjc-eUj30x5z( zx{(tE$8Soh8p%GG$(~OoDVi~v5NQDRfWMP5(78JaW?&? zU+6J-Y6I2o0LU3g#(^u~8L&bE(t-o!^8@Y%k(Lukqgu0#!bzGAdnAJVjNdX_wtWl82=)p?xS>4ss`gmxQ#NP&pcN zEO*DVPPzQKNTP$R=#1sO`L&?wolPcdIi#&fQiv3oQq9hI!OzT4|J~p zyZ^~__!qI@v`V8rU;T#p9-0Ov3yM28K;cfRtQfhAx$H~QQE;-hOT;*!$^y+0z&T(V zQ26EH6cK~4@oP_rSOciisbbY4nR*~5f&AixBU6K3#2<)CIZs6>z+VJzbn!8^#TsNB zqm#5q=2>d*%$J;=K#;jDu_~&?;pTr+M{W##6CzdhO=c%tROyM0Qg`hC`ANl)0D#w4 zue+4*1PNxF8^-v26Q+G@3T{UW5OQ)t6F`~*9GGfo@VNopX8*8>)j<;+W)NJSMS^Fn3cEW za;^D!MZp(AUn8hj+0mWf`0eMvE_Ox!l(?-v;_JeXCp=BnFaH6IsoX==+h8iFgy~X! z(1pR%r`-lmuec3v2na{E z;$64<)N=a%#@eubz9s+@e)fS4$?kiU;dwD9!}9@k!Ndg)G;E7xFaKR6dsfF~Z|Qh$ zLort$->;cfTOU7B2Yf3X%)0sa6!?OsO5>>0ak*mpEqc}-N< z3~zd1f{+kBVNtl4PIWG~xTQr?*neJC)Xntdfqr`(PgE!(oKsvm>(pR&P1@JLV5T3N zCt^oZ?ahrijB2O^y7qa!?ioknBl?mvoo0^+;ZJCIMQIkJ`x*ontaw^nO<&&AiV1GP zWL3va4mOxweffv`?XW$rF}fN3((?sHjj-O4_ReLuG)qJ2#OKG9U1W}Rvygb!#+aHe zAMFuV#&T%c1XfPz_kpg7D5ABrf%_5nL8Cwzzfta$f6@}(q2QCpt*qBHeLo$?-RDw^3e+C}zDdVSyu$FgMG7Bqc_Ei)OP z!f5Rboa@OAoH7du2j3bo+OA=G@6~nN+rs7y?9oGso$D+7`rdFrRkx*9{aY2Fe2M|| zMUtdyFw9{4FU72gH`758W6ATQD=b`A5Xw+9oAhwfp_pc`6_RIc5fGQ+;3XA5NiPl; zTn2;|cSkjzfH7@fS6(s>99QGEJjD=Z`SL(PkMqAHY-mR(A|1;gTrU8jluhCpS_vc?B`^l$(u05Zg-5eB`%qXvvyJS^Z+G}dX|2D zvoB*-XXw$(3&KEocCjEo^F-EEhx{|2f^x(4UlXmeyXs4tzkns}UdF3Sh z6(pm~agXL?(>|zdu&pj~vaL?>5jJ&2{`y(ZfU0aqh6?GJj%RHC+p#GE4P>ZMK3&Q_oKH>P{NL%zThHXC;f~1}No0h4)5k47-cLyEy z?Ty`hh-J*3PYg!IMeIAKdf?LO|UQuv;8pfbbmua zSnm)~&)9hf*1y%l&_4BmxnF*6{b*bK)Q~)oG9r(~VmtS5SWqEm^RzCayh}^qK ziVX5LQT~2K-ctu--MuP_FS~AM>~c9*`f%#=UMkCS{3xY+^dMeJ*Vmi{BMYhSx}O>7 zG4De37AXhfN~$nQ3?Eal8Fyc%SQXfd7QhSlKqvaJ^D!VizZv#45{FOtj>1ZY@tbCq*A4XXJ)5G zYsPb^>WjJaf$TSFsPj$@AgEb+l)NW1`s@1@EG1(2!^Z<^8+LdL5#{J>Yj=%u)Uql~ zT34&gKH|mFKko^6j)l#e-{3IH`XoIvn1E&K9G7K(V2|_Lm}+28=RM+$hd^PAizvv9s#n z7L}Aew)(eIa7lgK`xFeyUE`Uqbl2ec#&}RC&NgSb4jA##xClZo2rjb z1mUtQ98m=?!PTP20xhGhQdHUZdb(20{zLrU;T*k=>Pm7)bwK_jLg7!{opnv9QI1Yi z%u~9|P92>%H3H1Qt}{Q{L&V(gebE!Tq8{2MKZA}|?_*J#jyb=W$C)qgN%#khHXq+8 zMjXdfaP`t)go{<{#NPFtjeJdy>Whl@L)K5hA){v=UcY(xZ3Rqwf^G(q_CBi%ArC}< z_U(2=E={cgz54y*o)##Y*hwKhG`HNRi^70*n?>YTX#H70$#ycR-Nv_!FM{3=U|rT_ z(G7G{U&6?7%{|bNoujeWS%m|_w;(FGQ?XW7K*6;UTm24ovPh@n9JGcGg%(%+xD)~X zPDOG+u?1}w{if_KhFa~QOXomvBvnNF+JpB58-{GA%)Rjg>L!YMVt?_Cx|d;hh$*d= z=4Cf_7aI+L5`R%1&lW&fdu#*f%Aow^kM!u(o^yeM(wRpM!KA>>S9M&os(bVP_In}; zSC1>eRfy`U95%tBdPi_VJHggeseeI?> z6cT&=n0=fWs~GB~N9=B|=ntTdCZD<)t5^Y*y&s)ph7T7`k_7Na5$vyhL^&PqmrISE z47=a>H1@4l^BA{vcHIH`*0%5@77^t~GvRcSli-skVr}Y%a%z0`Z{>7W1y;SLL^vwg zVNgtHnP{zpP42u|oRpPWW2XcDsn++DOR*dt7qlg5=2DB0O;77W_DFHAUY7yC9+z3Z z8{dg@lRKQ+t#tIu#uobL5ZnBW6|VYOy+6lqTYh&z~*^Cq{Bhr`NtX^M4;ozQ)7Wr3{Zk_h^F!Jzc?dDPG*({>8E_ zU_%#pIBT{p1;RDBWP&#On_p|PJv{IEZcvI~v<{baA|T~UG;fLf;Fn>2q>6G#PK?~X zrN-1NqGyAn8H~M;Mx$s8kA~#&MlO!g+CieDSvpO^tI44^3(9{;_Z^e8RyK>T-^otQ z7SgfTrhXR^OSG~yxv^aGeT{u3PyU+em=+e3^igM zd`?uleGA$Gcaq)!VF6=>Q6hpl^=^y0H5TBee4HaRIv4sp;u?-XNM0#_VsEUD&BP?PSX+y*1xl=d_} zi3KOMuinw8K77CQuspo=P+`0AaNvWC#I+=dXVvrmPqVx){=OYOS|PZoF@4QP;OE;z zNoD8tIwT;AL zai7c+&S!mR!rF~ZTe@U>^uym@9y)i`?IOI4$zG{^n3R2vACI*&Qm?wKc7bqYK6XkH z*FBU&kI1WfI*DihrYL2$PB?fx+H;l8}w_2IJCxH$3}<)Tq$is(1**QW_k zA1>5z-w*p`((CaJfBg&jd{@$^}NCvPCGUxErr_VUE>Wm z^QCq+W-^rRzw^r^zAiF(0etQNHGhxgZP(>CVfr%nMNz}?qRlAKq;?~oKoM; zQAgL{;rE3RDM!Mh&NiL5n?&@>>vt(bE1!~}^wIN0X|n?`$%;X|x$ox5s*^wo9o8Ms zP6l4|qq##)amDFhTEYu^3)W+eBxTpx(SaceYUCnJcI)w~0d4idVBq`^5(yrih^th{`;RIiDzg-q-UPCq=6DlDuU zG=LvRTz{0e^g5je4cV+Qoc6StqiYrlm@qjr+o;kmouAtvNJEtb{dL;?U+}~q)hZ_U zV_2Ia+P>VRzxgHd5^G)_7>7t7o-#P4-Bi3;sbwkKZ-wuv0g`HTpoY(izff13&AKzi{b2VV;Ise4B0(zE8K&4z}GF%_~o} zNt(I_q_&$ZyctvH6LP?5Jo))J`V5BD*i@rh|B2fBSfBIn-P+gxo102ta*W3(9aW6s z^RsJX3m&Uv4-u2qv>)33NlvXmyOgZEk>zFW@(+AbE+ISvS>L z+(@3#*F_u1({lF$;bky8lcjUY#a$ttnNWvI;2;qX-XNMJ-NLj2`yZB1>PMzK6%WI4 zGnu}@YjDEvD$V|7H1%N(pz@|T{Zq>V!nMaTw9&!YFMSS5l&uS)=2>==bs)pkub1q_ zXJxmtp!d&T91NhDr-R>=!=@x_X>`u(yi<`*bBek-YkKPVQMOC{wg>?|kiXIxH%hm_`aR5 z3^eDIhMrH4sE?a!1fJnsD*w)LiQT|S=6+Yj>hCr!V9&p>yS=?Aa;pJgQri|n*avOB z>OPh%nXQ{y=nr%^Pqc%5&p=N+c2-=AdBOcv z*thHzd+FEe=+|O>%EcFWi*QSvXZ{bA($r_WoQWVPaeB08=tXx}G%Y)q)-`MprQzE^ zBc`~wn!lN;4-6VhjAG;Ko$LQ%z*#rzgq_8m%<#7-wCqrQi?Isgq64aIkNrg}q(!s) zkvWxq@P@$CO>?k0^wiky!st5DYE>j9fac%r=mrQ)g4EYaoP0dzwvKy05-Hc7+oAL< zAF(6Xyx*)+K0yurONeS9Xh}W=_p#F$AIB1&H5=uC1Zdt`<_3M-mer#;WJ~B=;^7Ghcc2+O#U^L;Su<0-MDux{Z$c`Lh$XZ zuY`Auu1(OLE@7IL`Z?3npJ0rY%H5T-hr{#?o@6IwZ}*zeKYtxC-BG!vW0Zo+x+NTb zaVo~a-$^fzmGGk>fHJ|110tOa*FU_EUOJWt=L$_Ghul_69d#2S#A;*{w_6n;zv>U< z9S_L~PJXug7H*UjVO`kkbsZB}5zR@qu&BI;#s3*qe%KbCJ2#F2NR=|jP~SIS%MgbEfxU%i0RUvg}FpbQv@)m?xiw(ifd#@sJ9{el*#jbGA4yGmZ{F?0qGn2$et( z?`5DD`;|Zb1V3vdZ>g=$rKYNQvv!kfo%w+SChCk)*g`JJUe1=R^p@?1nM+!JfJMRlx(V;6M4gf>AX~!IxO)wZY0V z%AA!iK`uI8ZZmYD(n`yf$3a-Tu+VWs-KC?(3n2=l*|nw2=+}s@ zg+DtEAZYU)A>yo3yYkS&tE&CJ0{*?rl8=B3S7Ty+G;G0wvUDv%t@*1&gHenY2UODU zYl|JXbVArK;`hSy+SkEb#YGVgd6tvrRBzGTPV_U#e0iXFhnR@ ze51$oxLLpSI)@z+2{Y_ABo(donUXuE0N2>7apVTEG|j%v8X;boMYu@26m`ujRt1<-OPBKIzJ?XHw;v>~XS(_;5IM ze?u@q(AmuvA#>Bp><3wnqv*miV^<89mbuIox=qHkS*Vqr>$&deRu}KxoS^ zZ~*Be-Z|1Ml_ASzbSSbT)M6ev(?-{b+wb2bu7T}GVtvD180M!VD6iaM?JIrHpp&d! zA`^$515PZ~kF%`6^r=hO03F7v4CT_0@RmRb14c=;b{y*Gddt5?{wdG#WVqgK7LtKuY(TO z%q;baRQHWM`pI$St-GiVn~qhyd79~lXqrIx2S)hx_3TaI6gYN10}tqejXlF(j?@>6 zR#9l_`9$Tq_-fvPP=FKikfE}ptS*?HRhL38tJ~o^oaRsX(Md`4bkiGdl zDQ)VkJwC#HY?=Sv)LC0Cjav6}u8WH|>k^WruDV$q9#!|=2_!H=N>)4ZTFPF zG0hwARhHtK)zMIM|9T)a6yx69wGyw|^W>>S4}NIHKs5~PVCu!Tu zjvgyt-3?7N95Ju;nz|3(QcjgkBYXiy?5*Pq@oaHCte!peQQ9~U4J(^=0zD_Dj0;rS zi61%}u~{Q&W8w>5{7dgtRv7)H`=a?UAPMR^mYURTCWG21PxjQ!f0%~JcSQ+jH4B*`1rGR+*k6cXmiJ$ zc461F$lK|M&5L(EHXpk;5@n(yxPUZs0-BymVWGY`T;r z?5Ql}@hhNoX4Ae!v`!yiO9Oi?2-^A55o|p31P^s=fnP>Ke^t{n9xpA|OkMsuFPK^$ zh}}kpU2;;FEGZ6>!Qb*x?J@3H=Q}Jbf;Ci{+*$jW!h1QvW57?P8NYMWd^lD{!fsl`&CgS1^>9tKT2kyB8ZpNP8qHny+*ce7xN zZX>qpeFPc+!s^AAWd8GBzkPWOdj#7)k1c8LIchE_3#;wqzaI96_B;^WRP7j!N@E*z zYRLz+GLbTrlJEEPKt##hKcD&s$S&0l>aZ&5|F#0&c4ycc*RS?jQ*V0(r0m15FVxI5 z{|cIJ$z|K+vF*zs5pO&D+Be$2Gn~8{W^S*{R`Sc^3o8VF{+}B}C|NlG;_IlLQ{|6(tzh(f#IApG3)nloS+EQ zztX9w4Bd|}D>5avwHda$C071wU!7h&RsJ6;y{fQyUa>*UY(Dp|6#{nAn7=sKk>)wPj{$T1w|0mTNIAj zUkCk?frq<@*+LX_4IV3_#@_TJJt<+vVF|7n@)aPjCI(5 ztEIlT70zuJ9v$b{X3DE_YCP}7W;b8;nHf+4JnnNx?IZtJd*1=obf?aOSfrKH%fDl4t z1_*)gf!134f6x2<-|u|q8|Q7#iEv_gp69;q>%P|gyMFiNu^b8hD$hg}X_uPT?B+V- zM!&i_mZR@?^oo_fj_R^h{VW~fug*PGztAW5Prn$w(pokZm1h`Yu1B7CvX(Ba<7NA3 zjt%j8c23B#UeiM-DB^C7$)F^YoXO?BCT`Wiq8JP_Z?IP~>3UPGuUbGSN9+3WIu?tjQ61=6Ym$*PZq1V}{ncOq@Kpf-t=)RVs1rEiNoDxraF z@NdfM_rfB1m#dB@-k*ehzGa)!JZ%sa#z=fPCySOe0$ex=OOlpQh-5SPHR ziH)!QYst4OtuEO)p7cOckk`(P_` z&+fWk$XoJKnk60SR7FhhbuIU?D-BhF!k9tfNx%@&PXR^BGbOW6>~rmS&U^0wJwRe` z#SlPXGMMdLbz3(zsjfD@5dksHbQ#c8hO_eBB3XNLN^C!9R^_6UxEVZ|oXN>L3=4!r;XFR;52zz(Tn}}javQ8#iwGmT*YJf$>+8o^Y6zh0cw|RJ*4exZf z5Vi*;ZYQdur)~CI6>wgWtqL64^ejUSPpp7Fk+G$#Ghhky#W`}lu+uYG4-n=h@3$Bt zXPFUfY0pColc!!JJq39@460V&JKZVhGJk`4kl~f~n7r3CKpf-$q*|jh9VUL6jqWc%8VJ@f0hmYpKgwrF_{kAl_=?moR%ya;I z<{9!0?o0gvd_l|GrVQ8psJvX<089)0Ew)zy1+Wk})OS(f7{ag+P^5d;y@1^_lc>i^ zeJo|0;Bh6OeZXdA?=6X%zqP$7Tjn|JXkN<+|r>JwE<=Oyg%F>_e+Za3TB{TZJ7IuvZ
    3~Uud@r0JtwO~G;^{rx0*sZ2SeJC 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 literal 0 HcmV?d00001 diff --git a/docs/en/images/System-parameterization-of-global-search-configuration-items.png b/docs/en/images/System-parameterization-of-global-search-configuration-items.png new file mode 100644 index 0000000000000000000000000000000000000000..4f47b58cd7b79417467c0f70b24406427cd3825b GIT binary patch literal 27530 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 适用于2.4.0及以上版本 + +默认为200,意味着每个环境在单次搜索操作中最多返回200条结果 + +修改该参数可能会影响搜索功能的性能,因此在修改之前应该进行充分的测试,根据实际业务需求和系统资源情况,适当调整`apollo.portal.search.perEnvMaxResults`的值,以平衡性能和搜索结果的数量 + ## 3.2 调整ApolloConfigDB配置 配置项统一存储在ApolloConfigDB.ServerConfig表中,需要注意每个环境的ApolloConfigDB.ServerConfig都需要单独配置,修改完一分钟实时生效。 diff --git a/docs/zh/design/apollo-design.md b/docs/zh/design/apollo-design.md index e2724800c5f..7cd1b49b2c0 100644 --- a/docs/zh/design/apollo-design.md +++ b/docs/zh/design/apollo-design.md @@ -136,7 +136,7 @@ sequenceDiagram ### 1.3.2 Admin Service * 提供配置管理接口 -* 提供配置修改、发布等接口 +* 提供配置修改、发布、检索等接口 * 接口服务对象为Portal ### 1.3.3 Meta Server diff --git a/docs/zh/design/apollo-introduction.md b/docs/zh/design/apollo-introduction.md index e05da35ec54..166af68c3a4 100644 --- a/docs/zh/design/apollo-introduction.md +++ b/docs/zh/design/apollo-introduction.md @@ -69,26 +69,30 @@ Apollo支持4个维度管理Key-Value格式的配置: * **灰度发布** * 支持配置的灰度发布,比如点了发布后,只对部分应用实例生效,等观察一段时间没问题后再推给所有应用实例 +* **配置项的全局视角搜索** + * 通过对配置项的key与value进行的模糊检索,找到拥有对应值的配置项在哪个应用、环境、集群、命名空间中被使用 + * 通过高亮显示、分页与跳转配置等操作,便于让管理员以及SRE角色快速、便捷地找到与更改资源的配置值 + * **权限管理、发布审核、操作审计** - * 应用和配置的管理都有完善的权限管理机制,对配置的管理还分为了编辑和发布两个环节,从而减少人为的错误。 - * 所有的操作都有审计日志,可以方便地追踪问题 + * 应用和配置的管理都有完善的权限管理机制,对配置的管理还分为了编辑和发布两个环节,从而减少人为的错误。 + * 所有的操作都有审计日志,可以方便地追踪问题 * **客户端配置信息监控** - * 可以在界面上方便地看到配置在被哪些实例使用 + * 可以在界面上方便地看到配置在被哪些实例使用 * **提供Java和.Net原生客户端** - * 提供了Java和.Net的原生客户端,方便应用集成 - * 支持Spring Placeholder, Annotation和Spring Boot的ConfigurationProperties,方便应用使用(需要Spring 3.1.1+) - * 同时提供了Http接口,非Java和.Net应用也可以方便地使用 + * 提供了Java和.Net的原生客户端,方便应用集成 + * 支持Spring Placeholder, Annotation和Spring Boot的ConfigurationProperties,方便应用使用(需要Spring 3.1.1+) + * 同时提供了Http接口,非Java和.Net应用也可以方便地使用 * **提供开放平台API** - * Apollo自身提供了比较完善的统一配置管理界面,支持多环境、多数据中心配置管理、权限、流程治理等特性。不过Apollo出于通用性考虑,不会对配置的修改做过多限制,只要符合基本的格式就能保存,不会针对不同的配置值进行针对性的校验,如数据库用户名、密码,Redis服务地址等 - * 对于这类应用配置,Apollo支持应用方通过开放平台API在Apollo进行配置的修改和发布,并且具备完善的授权和权限控制 + * Apollo自身提供了比较完善的统一配置管理界面,支持多环境、多数据中心配置管理、权限、流程治理等特性。不过Apollo出于通用性考虑,不会对配置的修改做过多限制,只要符合基本的格式就能保存,不会针对不同的配置值进行针对性的校验,如数据库用户名、密码,Redis服务地址等 + * 对于这类应用配置,Apollo支持应用方通过开放平台API在Apollo进行配置的修改和发布,并且具备完善的授权和权限控制 * **部署简单** - * 配置中心作为基础服务,可用性要求非常高,这就要求Apollo对外部依赖尽可能地少 - * 目前唯一的外部依赖是MySQL,所以部署非常简单,只要安装好Java和MySQL就可以让Apollo跑起来 - * Apollo还提供了打包脚本,一键就可以生成所有需要的安装包,并且支持自定义运行时参数 + * 配置中心作为基础服务,可用性要求非常高,这就要求Apollo对外部依赖尽可能地少 + * 目前唯一的外部依赖是MySQL,所以部署非常简单,只要安装好Java和MySQL就可以让Apollo跑起来 + * Apollo还提供了打包脚本,一键就可以生成所有需要的安装包,并且支持自定义运行时参数 # 3、Apollo at a glance diff --git a/docs/zh/images/Configuration query-Non properties.png b/docs/zh/images/Configuration query-Non properties.png new file mode 100644 index 0000000000000000000000000000000000000000..1a355074cbc6c388f74598aab746a66592d59737 GIT binary patch literal 35238 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%0DwvXUCoU)FC>beq-skNft&+I{;|S#(j3<6j)Y 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 literal 0 HcmV?d00001 diff --git a/docs/zh/images/Configuration query-properties.png b/docs/zh/images/Configuration query-properties.png new file mode 100644 index 0000000000000000000000000000000000000000..2f8fd7711093c9a116aaf0f71b1d2b41d6b92fa5 GIT binary patch literal 35102 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
    3~Uud@r0JtwO~G;^{rx0*sZ2SeJC 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 literal 0 HcmV?d00001 diff --git a/docs/zh/images/System-parameterization-of-global-search-configuration-items.png b/docs/zh/images/System-parameterization-of-global-search-configuration-items.png new file mode 100644 index 0000000000000000000000000000000000000000..4f47b58cd7b79417467c0f70b24406427cd3825b GIT binary patch literal 27530 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 Date: Mon, 30 Sep 2024 08:38:52 +0800 Subject: [PATCH 4/8] =?UTF-8?q?fix=EF=BC=9AFixed=20link=20namespace=20publ?= =?UTF-8?q?ished=20items=20show=20missing=20some=20items=20(#5240)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix:Fixed link namespace published items show missing some items * fix:add CHANGES.md --- CHANGES.md | 1 + .../views/component/namespace-panel-master-tab.html | 10 ++++++---- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ed756c3f80e..c1fc1402fec 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ Apollo 2.4.0 * [Update the server config link in system info page](https://github.com/apolloconfig/apollo/pull/5204) * [Feature support portal restTemplate Client connection pool config](https://github.com/apolloconfig/apollo/pull/5200) * [Feature added the ability for administrators to globally search for Value](https://github.com/apolloconfig/apollo/pull/5182) +* [Fix link namespace published items show missing some items](https://github.com/apolloconfig/apollo/pull/5240) ------------------ All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1) \ No newline at end of file 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 6b8fa17f47d..c93bd8f90a4 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 @@ -624,7 +624,7 @@
    {{'Component.Namespace.Master.Items.Body.Link.NoCoverLinkItem' | translate } + ng-if="config.item.key && ((!config.isModified && !config.isDeleted) || config.oldValue)"> @@ -643,9 +643,11 @@
    {{'Component.Namespace.Master.Items.Body.Link.NoCoverLinkItem' | translate } - - + ng-click="(config.isModified || config.isDeleted) ? showText(config.oldValue) : showText(config.item.value)"> + + + + From a1c7c9612b025b5a343b95a66eeb8beb53e848c2 Mon Sep 17 00:00:00 2001 From: yangzl Date: Tue, 1 Oct 2024 12:11:18 +0800 Subject: [PATCH 5/8] solve the problem of duplicate comments and blank lines (#5232) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 1. solve the problem of duplicate comments and blank lines 2. reduce the data growth of the item table 3. in the same case, the size of ItemChangeSets can be reduced 4. when revoke configuration, if there is only one comment or blank line, it will not be deleted. * fix: 1. add CHANGES.md 2. PropertyResolver code style format 3. Revoke configurations cause the line num to be disordered * fix:code optimization * feat: 1. solve the problem of duplicate comments and blank lines 2. reduce the data growth of the item table 3. in the same case, the size of ItemChangeSets can be reduced 4. when revoke configuration, if there is only one comment or blank line, it will not be deleted. * fix: 1. add CHANGES.md 2. PropertyResolver code style format 3. Revoke configurations cause the line num to be disordered * fix:code optimization --- CHANGES.md | 3 +- .../txtresolver/PropertyResolver.java | 78 +++++++++++-------- .../apollo/portal/service/ItemService.java | 21 ++--- .../portal/service/NamespaceService.java | 9 ++- .../txtresolver/PropertyResolverTest.java | 18 ++--- 5 files changed, 72 insertions(+), 57 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index c1fc1402fec..2d9cd3d8735 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,7 +8,8 @@ Apollo 2.4.0 * [Update the server config link in system info page](https://github.com/apolloconfig/apollo/pull/5204) * [Feature support portal restTemplate Client connection pool config](https://github.com/apolloconfig/apollo/pull/5200) * [Feature added the ability for administrators to globally search for Value](https://github.com/apolloconfig/apollo/pull/5182) +* [Fix: Resolve issues with duplicate comments and blank lines in configuration management](https://github.com/apolloconfig/apollo/pull/5232) * [Fix link namespace published items show missing some items](https://github.com/apolloconfig/apollo/pull/5240) ------------------ -All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1) \ No newline at end of file +All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1) diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/txtresolver/PropertyResolver.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/txtresolver/PropertyResolver.java index 44080d7520d..9c3751f7bea 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/txtresolver/PropertyResolver.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/component/txtresolver/PropertyResolver.java @@ -21,15 +21,20 @@ import com.ctrip.framework.apollo.common.exception.BadRequestException; import com.ctrip.framework.apollo.common.utils.BeanUtils; +import com.ctrip.framework.apollo.core.utils.StringUtils; import com.google.common.base.Strings; import org.springframework.stereotype.Component; +import org.springframework.util.CollectionUtils; import javax.validation.constraints.NotNull; +import java.util.Comparator; import java.util.HashMap; import java.util.HashSet; +import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; /** * normal property file resolver. @@ -45,12 +50,21 @@ public class PropertyResolver implements ConfigTextResolver { @Override public ItemChangeSets resolve(long namespaceId, String configText, List baseItems) { - Map oldLineNumMapItem = BeanUtils.mapByKey("lineNum", baseItems); Map oldKeyMapItem = BeanUtils.mapByKey("key", baseItems); - //remove comment and blank item map. oldKeyMapItem.remove(""); + // comment items + List baseCommentItems = new LinkedList<>(); + // blank items + List baseBlankItems = new LinkedList<>(); + if (!CollectionUtils.isEmpty(baseItems)) { + + baseCommentItems = baseItems.stream().filter(itemDTO -> isCommentItem(itemDTO)).sorted(Comparator.comparing(ItemDTO::getLineNum)).collect(Collectors.toCollection(LinkedList::new)); + + baseBlankItems = baseItems.stream().filter(itemDTO -> isBlankItem(itemDTO)).sorted(Comparator.comparing(ItemDTO::getLineNum)).collect(Collectors.toCollection(LinkedList::new)); + } + String[] newItems = configText.split(ITEM_SEPARATOR); Set repeatKeys = new HashSet<>(); if (isHasRepeatKey(newItems, repeatKeys)) { @@ -63,17 +77,25 @@ public ItemChangeSets resolve(long namespaceId, String configText, List for (String newItem : newItems) { newItem = newItem.trim(); newLineNumMapItem.put(lineCounter, newItem); - ItemDTO oldItemByLine = oldLineNumMapItem.get(lineCounter); //comment item if (isCommentItem(newItem)) { + ItemDTO oldItemDTO = null; + if (!CollectionUtils.isEmpty(baseCommentItems)) { + oldItemDTO = baseCommentItems.remove(0); + } - handleCommentLine(namespaceId, oldItemByLine, newItem, lineCounter, changeSets); + handleCommentLine(namespaceId, oldItemDTO, newItem, lineCounter, changeSets); //blank item } else if (isBlankItem(newItem)) { - handleBlankLine(namespaceId, oldItemByLine, lineCounter, changeSets); + ItemDTO oldItemDTO = null; + if (!CollectionUtils.isEmpty(baseBlankItems)) { + oldItemDTO = baseBlankItems.remove(0); + } + + handleBlankLine(namespaceId, oldItemDTO, lineCounter, changeSets); //normal item } else { @@ -83,7 +105,7 @@ public ItemChangeSets resolve(long namespaceId, String configText, List lineCounter++; } - deleteCommentAndBlankItem(oldLineNumMapItem, newLineNumMapItem, changeSets); + deleteCommentAndBlankItem(baseCommentItems, baseBlankItems, changeSets); deleteNormalKVItem(oldKeyMapItem, changeSets); return changeSets; @@ -97,7 +119,7 @@ private boolean isHasRepeatKey(String[] newItems, @NotNull Set repeatKey String[] kv = parseKeyValueFromItem(item); if (kv != null) { String key = kv[0].toLowerCase(); - if(!keys.add(key)){ + if (!keys.add(key)) { repeatKeys.add(key); } } else { @@ -122,16 +144,18 @@ private String[] parseKeyValueFromItem(String item) { } private void handleCommentLine(Long namespaceId, ItemDTO oldItemByLine, String newItem, int lineCounter, ItemChangeSets changeSets) { - String oldComment = oldItemByLine == null ? "" : oldItemByLine.getComment(); - //create comment. implement update comment by delete old comment and create new comment - if (!(isCommentItem(oldItemByLine) && newItem.equals(oldComment))) { + if (null == oldItemByLine) { changeSets.addCreateItem(buildCommentItem(0L, namespaceId, newItem, lineCounter)); + } else if (!StringUtils.equals(oldItemByLine.getComment(), newItem) || lineCounter != oldItemByLine.getLineNum()) { + changeSets.addUpdateItem(buildCommentItem(oldItemByLine.getId(), namespaceId, newItem, lineCounter)); } } private void handleBlankLine(Long namespaceId, ItemDTO oldItem, int lineCounter, ItemChangeSets changeSets) { - if (!isBlankItem(oldItem)) { + if (null == oldItem) { changeSets.addCreateItem(buildBlankItem(0L, namespaceId, lineCounter)); + } else if (lineCounter != oldItem.getLineNum()) { + changeSets.addUpdateItem(buildBlankItem(oldItem.getId(), namespaceId, lineCounter)); } } @@ -149,12 +173,12 @@ private void handleNormalLine(Long namespaceId, Map keyMapOldIt ItemDTO oldItem = keyMapOldItem.get(newKey); - if (oldItem == null) {//new item + //new item + if (oldItem == null) { changeSets.addCreateItem(buildNormalItem(0L, namespaceId, newKey, newValue, "", lineCounter)); - } else if (!newValue.equals(oldItem.getValue()) || lineCounter != oldItem.getLineNum()) {//update item - changeSets.addUpdateItem( - buildNormalItem(oldItem.getId(), namespaceId, newKey, newValue, oldItem.getComment(), - lineCounter)); + //update item + } else if (!StringUtils.equals(newValue, oldItem.getValue()) || lineCounter != oldItem.getLineNum()) { + changeSets.addUpdateItem(buildNormalItem(oldItem.getId(), namespaceId, newKey, newValue, oldItem.getComment(), lineCounter)); } keyMapOldItem.remove(newKey); } @@ -173,7 +197,7 @@ private boolean isBlankItem(ItemDTO item) { } private boolean isBlankItem(String line) { - return Strings.nullToEmpty(line).trim().isEmpty(); + return Strings.nullToEmpty(line).trim().isEmpty(); } private void deleteNormalKVItem(Map baseKeyMapItem, ItemChangeSets changeSets) { @@ -183,23 +207,11 @@ private void deleteNormalKVItem(Map baseKeyMapItem, ItemChangeS } } - private void deleteCommentAndBlankItem(Map oldLineNumMapItem, - Map newLineNumMapItem, + private void deleteCommentAndBlankItem(List baseCommentItems, + List baseBlankItems, ItemChangeSets changeSets) { - - for (Map.Entry entry : oldLineNumMapItem.entrySet()) { - int lineNum = entry.getKey(); - ItemDTO oldItem = entry.getValue(); - String newItem = newLineNumMapItem.get(lineNum); - - //1. old is blank by now is not - //2.old is comment by now is not exist or modified - //3.old is blank by now is not exist or modified - if ((isBlankItem(oldItem) && !isBlankItem(newItem)) - || (isCommentItem(oldItem) || isBlankItem(oldItem)) && (newItem == null || !newItem.equals(oldItem.getComment()))) { - changeSets.addDeleteItem(oldItem); - } - } + baseCommentItems.forEach(oldItemDTO -> changeSets.addDeleteItem(oldItemDTO)); + baseBlankItems.forEach(oldItemDTO -> changeSets.addDeleteItem(oldItemDTO)); } private ItemDTO buildCommentItem(Long id, Long namespaceId, String comment, int lineNum) { diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/ItemService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/ItemService.java index b1677e18858..589052a6f82 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/ItemService.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/ItemService.java @@ -50,6 +50,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import java.util.stream.Collectors; @Service public class ItemService { @@ -216,12 +217,13 @@ public void revokeItem(String appId, Env env, String clusterName, String namespa } List baseItems = itemAPI.findItems(appId, env, clusterName, namespaceName); Map oldKeyMapItem = BeanUtils.mapByKey("key", baseItems); - Map deletedItemDTOs = new HashMap<>(); + //remove comment and blank item map. + oldKeyMapItem.remove(""); //deleted items for comment - findDeletedItems(appId, env, clusterName, namespaceName).forEach(item -> { - deletedItemDTOs.put(item.getKey(),item); - }); + Map deletedItemDTOs = findDeletedItems(appId, env, clusterName, namespaceName).stream() + .filter(itemDTO -> !StringUtils.isEmpty(itemDTO.getKey())) + .collect(Collectors.toMap(itemDTO -> itemDTO.getKey(), v -> v, (v1, v2) -> v2)); ItemChangeSets changeSets = new ItemChangeSets(); AtomicInteger lineNum = new AtomicInteger(1); @@ -229,16 +231,15 @@ public void revokeItem(String appId, Env env, String clusterName, String namespa ItemDTO oldItem = oldKeyMapItem.get(key); if (oldItem == null) { ItemDTO deletedItemDto = deletedItemDTOs.computeIfAbsent(key, k -> new ItemDTO()); - changeSets.addCreateItem(buildNormalItem(0L, namespaceId,key,value,deletedItemDto.getComment(),lineNum.get())); - } else if (!oldItem.getValue().equals(value) || lineNum.get() != oldItem - .getLineNum()) { - changeSets.addUpdateItem(buildNormalItem(oldItem.getId(), namespaceId, key, - value, oldItem.getComment(), lineNum.get())); + int newLineNum = 0 == deletedItemDto.getLineNum() ? lineNum.get() : deletedItemDto.getLineNum(); + changeSets.addCreateItem(buildNormalItem(0L, namespaceId, key, value, deletedItemDto.getComment(), newLineNum)); + } else if (!StringUtils.equals(oldItem.getValue(), value) || lineNum.get() != oldItem.getLineNum()) { + changeSets.addUpdateItem(buildNormalItem(oldItem.getId(), namespaceId, key, value, oldItem.getComment(), oldItem.getLineNum())); } oldKeyMapItem.remove(key); lineNum.set(lineNum.get() + 1); }); - oldKeyMapItem.forEach((key, value) -> changeSets.addDeleteItem(oldKeyMapItem.get(key))); + oldKeyMapItem.forEach((key, value) -> changeSets.addDeleteItem(value)); changeSets.setDataChangeLastModifiedBy(userInfoHolder.getUser().getUserId()); updateItems(appId, env, clusterName, namespaceName, changeSets); diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/NamespaceService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/NamespaceService.java index 43f05e4de94..183a58b891e 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/NamespaceService.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/NamespaceService.java @@ -309,7 +309,6 @@ private NamespaceBO transformNamespace2BO(Env env, NamespaceDTO namespace, boole //latest Release ReleaseDTO latestRelease; Map releaseItems = new HashMap<>(); - Map deletedItemDTOs = new HashMap<>(); latestRelease = releaseService.loadLatestRelease(appId, env, clusterName, namespaceName); if (latestRelease != null) { releaseItems = GSON.fromJson(latestRelease.getConfigurations(), GsonType.CONFIG); @@ -333,9 +332,9 @@ private NamespaceBO transformNamespace2BO(Env env, NamespaceDTO namespace, boole if (includeDeletedItems) { //deleted items - itemService.findDeletedItems(appId, env, clusterName, namespaceName).forEach(item -> { - deletedItemDTOs.put(item.getKey(), item); - }); + Map deletedItemDTOs = itemService.findDeletedItems(appId, env, clusterName, namespaceName).stream() + .filter(itemDTO -> !StringUtils.isEmpty(itemDTO.getKey())) + .collect(Collectors.toMap(itemDTO -> itemDTO.getKey(), v -> v, (v1, v2) -> v2)); List deletedItems = parseDeletedItems(items, releaseItems, deletedItemDTOs); itemBOs.addAll(deletedItems); @@ -385,6 +384,8 @@ private void fillAppNamespaceProperties(NamespaceBO namespace) { private List parseDeletedItems(List newItems, Map releaseItems, Map deletedItemDTOs) { Map newItemMap = BeanUtils.mapByKey("key", newItems); + //remove comment and blank item map. + newItemMap.remove(""); List deletedItems = new LinkedList<>(); for (Map.Entry entry : releaseItems.entrySet()) { diff --git a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/component/txtresolver/PropertyResolverTest.java b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/component/txtresolver/PropertyResolverTest.java index 4c8664f7b47..ed18a70c448 100644 --- a/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/component/txtresolver/PropertyResolverTest.java +++ b/apollo-portal/src/test/java/com/ctrip/framework/apollo/portal/component/txtresolver/PropertyResolverTest.java @@ -91,9 +91,9 @@ public void testDeleteItem() { @Test public void testDeleteCommentItem() { ItemChangeSets changeSets = resolver.resolve(1, "a=b\n\nb=c", mockBaseItemWith2Key1Comment1Blank()); - Assert.assertEquals(2, changeSets.getDeleteItems().size()); - Assert.assertEquals(2, changeSets.getUpdateItems().size()); - Assert.assertEquals(1, changeSets.getCreateItems().size()); + Assert.assertEquals(1, changeSets.getDeleteItems().size()); + Assert.assertEquals(3, changeSets.getUpdateItems().size()); + Assert.assertEquals(0, changeSets.getCreateItems().size()); } @Test @@ -120,17 +120,17 @@ public void testUpdateCommentItem() { + "a=b\n" +"\n" + "b=c", mockBaseItemWith2Key1Comment1Blank()); - Assert.assertEquals(1, changeSets.getDeleteItems().size()); - Assert.assertEquals(0, changeSets.getUpdateItems().size()); - Assert.assertEquals(1, changeSets.getCreateItems().size()); + Assert.assertEquals(0, changeSets.getDeleteItems().size()); + Assert.assertEquals(1, changeSets.getUpdateItems().size()); + Assert.assertEquals(0, changeSets.getCreateItems().size()); } @Test public void testAllSituation(){ ItemChangeSets changeSets = resolver.resolve(1, "#ww\nd=e\nb=c\na=b\n\nq=w\n#eee", mockBaseItemWith2Key1Comment1Blank()); - Assert.assertEquals(2, changeSets.getDeleteItems().size()); - Assert.assertEquals(2, changeSets.getUpdateItems().size()); - Assert.assertEquals(5, changeSets.getCreateItems().size()); + Assert.assertEquals(0, changeSets.getDeleteItems().size()); + Assert.assertEquals(4, changeSets.getUpdateItems().size()); + Assert.assertEquals(3, changeSets.getCreateItems().size()); } /** From 94c28af31bb1d9ce7025a0c1e3034c2e0646c468 Mon Sep 17 00:00:00 2001 From: yangzl Date: Sun, 6 Oct 2024 10:12:26 +0800 Subject: [PATCH 6/8] feat: add determine appid+cluster namespace num limit logic (#5228) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add determine appid+cluster namespace num limit logic * fix: 1. Added switch control function on and off 2. Add unit tests and usage documentation 3. Update the CHANGES.md 4. Function and code style optimization * fix:Optimize CHANGES.md、unit tests、usage documentation * fix:Optimize unit tests * Update CHANGES.md --------- Co-authored-by: Jason Song --- CHANGES.md | 1 + .../apollo/biz/config/BizConfig.java | 18 ++++ .../biz/repository/NamespaceRepository.java | 2 + .../apollo/biz/service/NamespaceService.java | 14 ++- .../NamespaceServiceIntegrationTest.java | 95 ++++++++++++++++++ doc/images/namespace-num-limit-enabled.png | Bin 0 -> 32715 bytes doc/images/namespace-num-limit-white.png | Bin 0 -> 35780 bytes doc/images/namespace-num-limit.png | Bin 0 -> 32053 bytes docs/en/portal/apollo-user-guide.md | 23 +++++ docs/zh/portal/apollo-user-guide.md | 22 +++- 10 files changed, 173 insertions(+), 2 deletions(-) create mode 100644 doc/images/namespace-num-limit-enabled.png create mode 100644 doc/images/namespace-num-limit-white.png create mode 100644 doc/images/namespace-num-limit.png diff --git a/CHANGES.md b/CHANGES.md index 2d9cd3d8735..db77fc9598a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -10,6 +10,7 @@ Apollo 2.4.0 * [Feature added the ability for administrators to globally search for Value](https://github.com/apolloconfig/apollo/pull/5182) * [Fix: Resolve issues with duplicate comments and blank lines in configuration management](https://github.com/apolloconfig/apollo/pull/5232) * [Fix link namespace published items show missing some items](https://github.com/apolloconfig/apollo/pull/5240) +* [Feature: Add limit and whitelist for namespace count per appid+cluster](https://github.com/apolloconfig/apollo/pull/5228) ------------------ All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1) diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java index 2652995f582..fe26e81bc45 100644 --- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java +++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/config/BizConfig.java @@ -21,12 +21,14 @@ import com.ctrip.framework.apollo.common.config.RefreshablePropertySource; import com.google.common.base.Strings; import com.google.common.collect.Maps; +import com.google.common.collect.Sets; import com.google.gson.Gson; import com.google.gson.reflect.TypeToken; import java.lang.reflect.Type; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import org.springframework.stereotype.Component; @@ -36,6 +38,9 @@ public class BizConfig extends RefreshableConfig { private static final int DEFAULT_ITEM_KEY_LENGTH = 128; private static final int DEFAULT_ITEM_VALUE_LENGTH = 20000; + + private static final int DEFAULT_MAX_NAMESPACE_NUM = 200; + private static final int DEFAULT_APPNAMESPACE_CACHE_REBUILD_INTERVAL = 60; //60s private static final int DEFAULT_GRAY_RELEASE_RULE_SCAN_INTERVAL = 60; //60s private static final int DEFAULT_APPNAMESPACE_CACHE_SCAN_INTERVAL = 1; //1s @@ -99,6 +104,19 @@ public int itemValueLengthLimit() { return checkInt(limit, 5, Integer.MAX_VALUE, DEFAULT_ITEM_VALUE_LENGTH); } + public boolean isNamespaceNumLimitEnabled() { + return getBooleanProperty("namespace.num.limit.enabled", false); + } + + public int namespaceNumLimit() { + int limit = getIntProperty("namespace.num.limit", DEFAULT_MAX_NAMESPACE_NUM); + return checkInt(limit, 0, Integer.MAX_VALUE, DEFAULT_MAX_NAMESPACE_NUM); + } + + public Set namespaceNumLimitWhite() { + return Sets.newHashSet(getArrayProperty("namespace.num.limit.white", new String[0])); + } + public Map namespaceValueLengthLimitOverride() { String namespaceValueLengthOverrideString = getValue("namespace.value.length.limit.override"); Map namespaceValueLengthOverride = Maps.newHashMap(); diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/NamespaceRepository.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/NamespaceRepository.java index fc36a10524b..1c134275c8a 100644 --- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/NamespaceRepository.java +++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/repository/NamespaceRepository.java @@ -44,4 +44,6 @@ public interface NamespaceRepository extends PagingAndSortingRepository= bizConfig.namespaceNumLimit()) { + throw new ServiceException("namespace[appId = " + entity.getAppId() + ", cluster= " + entity.getClusterName() + "] nowCount= " + nowCount + ", maxCount =" + bizConfig.namespaceNumLimit()); + } + } + entity.setId(0);//protection Namespace namespace = namespaceRepository.save(entity); diff --git a/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/service/NamespaceServiceIntegrationTest.java b/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/service/NamespaceServiceIntegrationTest.java index 3bc57305138..0f2307e371b 100644 --- a/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/service/NamespaceServiceIntegrationTest.java +++ b/apollo-biz/src/test/java/com/ctrip/framework/apollo/biz/service/NamespaceServiceIntegrationTest.java @@ -17,6 +17,7 @@ package com.ctrip.framework.apollo.biz.service; import com.ctrip.framework.apollo.biz.AbstractIntegrationTest; +import com.ctrip.framework.apollo.biz.config.BizConfig; import com.ctrip.framework.apollo.biz.entity.Cluster; import com.ctrip.framework.apollo.biz.entity.Commit; import com.ctrip.framework.apollo.biz.entity.InstanceConfig; @@ -25,23 +26,31 @@ import com.ctrip.framework.apollo.biz.entity.Release; import com.ctrip.framework.apollo.biz.entity.ReleaseHistory; import com.ctrip.framework.apollo.biz.repository.InstanceConfigRepository; +import com.ctrip.framework.apollo.biz.repository.NamespaceRepository; import com.ctrip.framework.apollo.common.entity.AppNamespace; +import com.ctrip.framework.apollo.common.exception.ServiceException; import java.text.ParseException; import java.text.SimpleDateFormat; +import java.util.Arrays; import java.util.Date; +import java.util.HashSet; +import org.junit.Assert; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.test.context.jdbc.Sql; import java.util.List; +import org.springframework.test.util.ReflectionTestUtils; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.when; public class NamespaceServiceIntegrationTest extends AbstractIntegrationTest { @@ -62,6 +71,11 @@ public class NamespaceServiceIntegrationTest extends AbstractIntegrationTest { private ReleaseHistoryService releaseHistoryService; @Autowired private InstanceConfigRepository instanceConfigRepository; + @Autowired + private NamespaceRepository namespaceRepository; + + @MockBean + private BizConfig bizConfig; private String testApp = "testApp"; private String testCluster = "default"; @@ -134,4 +148,85 @@ public void testGetCommitsByModifiedTime() throws ParseException { } + @Test + @Sql(scripts = "/sql/namespace-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) + public void testNamespaceNumLimit() { + + when(bizConfig.isNamespaceNumLimitEnabled()).thenReturn(true); + when(bizConfig.namespaceNumLimit()).thenReturn(2); + + Namespace namespace = new Namespace(); + namespace.setAppId(testApp); + namespace.setClusterName(testCluster); + namespace.setNamespaceName("demo-namespace"); + namespaceService.save(namespace); + + try { + Namespace namespace2 = new Namespace(); + namespace2.setAppId(testApp); + namespace2.setClusterName(testCluster); + namespace2.setNamespaceName("demo-namespace2"); + namespaceService.save(namespace2); + + Assert.fail(); + } catch (Exception e) { + Assert.assertTrue(e instanceof ServiceException); + } + + int nowCount = namespaceRepository.countByAppIdAndClusterName(testApp, testCluster); + Assert.assertEquals(2, nowCount); + + } + + @Test + @Sql(scripts = "/sql/namespace-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) + public void testNamespaceNumLimitFalse() { + + when(bizConfig.namespaceNumLimit()).thenReturn(2); + + Namespace namespace = new Namespace(); + namespace.setAppId(testApp); + namespace.setClusterName(testCluster); + namespace.setNamespaceName("demo-namespace"); + namespaceService.save(namespace); + + Namespace namespace2 = new Namespace(); + namespace2.setAppId(testApp); + namespace2.setClusterName(testCluster); + namespace2.setNamespaceName("demo-namespace2"); + namespaceService.save(namespace2); + + int nowCount = namespaceRepository.countByAppIdAndClusterName(testApp, testCluster); + Assert.assertEquals(3, nowCount); + + } + + @Test + @Sql(scripts = "/sql/namespace-test.sql", executionPhase = Sql.ExecutionPhase.BEFORE_TEST_METHOD) + @Sql(scripts = "/sql/clean.sql", executionPhase = Sql.ExecutionPhase.AFTER_TEST_METHOD) + public void testNamespaceNumLimitWhite() { + + when(bizConfig.isNamespaceNumLimitEnabled()).thenReturn(true); + when(bizConfig.namespaceNumLimit()).thenReturn(2); + when(bizConfig.namespaceNumLimitWhite()).thenReturn(new HashSet<>(Arrays.asList(testApp))); + + Namespace namespace = new Namespace(); + namespace.setAppId(testApp); + namespace.setClusterName(testCluster); + namespace.setNamespaceName("demo-namespace"); + namespaceService.save(namespace); + + Namespace namespace2 = new Namespace(); + namespace2.setAppId(testApp); + namespace2.setClusterName(testCluster); + namespace2.setNamespaceName("demo-namespace2"); + namespaceService.save(namespace2); + + int nowCount = namespaceRepository.countByAppIdAndClusterName(testApp, testCluster); + Assert.assertEquals(3, nowCount); + + } + } diff --git a/doc/images/namespace-num-limit-enabled.png b/doc/images/namespace-num-limit-enabled.png new file mode 100644 index 0000000000000000000000000000000000000000..a415132aa12aa9c39fe8a807b13aebf9626039eb GIT binary patch literal 32715 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%0AH9a>#7)Q~KjO<@y)OWDOS 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 literal 0 HcmV?d00001 diff --git a/doc/images/namespace-num-limit.png b/doc/images/namespace-num-limit.png new file mode 100644 index 0000000000000000000000000000000000000000..45f22e79956cc81af522e643308ee8e07780d382 GIT binary patch literal 32053 zcmeFZc~p~0_by5|pV+MkJvh;g;Di_mpdcVJX)7wT1{uPvkRUQCNMabZX{8%cWJ(BA z92jKQgh2=pZCfA-Au2-%A)rD)2mv8NgplM`K)>$qcg|hwo^|hC>zuWI`G+LDskh#$ zUAy*vo@dt|r<_h~SohO91qFo-wkMCl6cm)`3JPC;^NkYtW^dKYqY4Uf%eKdkoVnsV z#a+zY9o=FTBP$h;NQ_#~{_E1We}0I3zHjv5y|?K}-&|AQJb(4-ldB!K*X`W?HLYZC z#aZQj%Yp*g&q2QUeexFpXFmjHpqLAkd2gAB=r;_VeZ7)#U{(Up$ z@~=~G3JxiRTCF_B?_1BT*}4VX;Y)}2KU}v@Us&stIED|(wO)R!%5Zh}>0Mr8QNlH1 z-rpNbQnh+d*l?V8ZLP-n>L>3~kgRGSN#@qFmROOA>2O?B)Y3$01NAL5U~pyVb`h~h zdfolv>o6alt;OlZj@&ZMEigydC*7-8P>9pK+;DO(n1 zj`L%O#lB)snRr+>q>(sYfX)>S#n!|wl}Q;VK@rfqyE5yKkC zazj)+v6p*^F?x(c%MBDsm1QFBMI||WPJKS&L0(oychM7ilp*87TJy6H1~KCW3~-I0 zj2cX;M37t6h#Sm=okrS;Ixnm{==aBfSS;0q4@qOoLMEYE>U3zL2<3)IQ0h9wuUrt#Ko{e^aprG-`*DDP zTh2%OF^8AOOgR<{{Q-j5>iilf@idykDa1YplkF>(1(z0>n6>E6SQDpyU9W#|FI-B~ zt$~`${A5e4u_+R>aCcg9IEic_D$~90-Gw9cCJ-%3(~gAsl^{Aq8ydJMFLB|+#80Q@ zT8%{UBzk9aS9sGzNs@{t4cPurxJY&WEpgY8)kR#f?F>V~St&`s<>Tvx4-pB%y!y)> zrSrmeG>w?0tTsiRTT-k?*>^i`+AQJW;dMW$)lzv8Pu|{;+Vb75yEf!c+Or#moNJMi zkL&ZZBgKLSXZ^hB0w0SO@iaMXu6fiV+G3GAlfY7WIp4A{5F*enh>~tZs$Hq>32Tuq z@g+B_Y}d69CO2=6{8??85r%{-y&|ozx3hv5&Q<7&b|{A9uk~Bz&l!;3MjmA4wQpBUB8tPcA2u*yay#dRT@#xuO^2Zp2{Cq ztFv^R$P9BYz+RKUd6&EL1hXWj6-VV$>2Doxl`VquE-@`dwaG4UE*cC*K5K$P%X&ev5WO)W_sgPjCk&#CT28U zdQK?=CbX80SgYvRHBrOWf~Yg?%NBu3aNl9b1DHo3s~D0j^mI16!?N?GEG~9T)nutP zPA|7IqDMt%7z35}$F6VjJzw)c7Xc5C$Zf7)LckLe=5}P+Auo?DMR{>~5fsS+kmZxx83f5x zkS_L&7OsXJ4B?x+PS>aV#?8K5JPxz;LI^ql_!(=?kz0of z1KWzB8-md*I?O=8 zGJ5&bTPUAi6kQak9xmR__jT6@=(AUfkCaZq_9~_|cEQOPr*McyYtx}C0kN8M-p|sp z=R_y0mtGa^rN4zEBH#j4ywPZD^xBk)xEX0DLdmY6kWd3pSRT=IM&8|RmOpIdiba$h zQ*t1)3WNv+E2C^-RGtu7Fu(Ru<_;eEd?9n=&blxixHcTyZ5S0aQ2o-R9GYhm5_VpA zg`lEU^DH8s?Eip|`m9XVrct!_ zP!z9@8}Iy5ui?T1Qt-7gB4pj~Qho~(%Ns8xKxc4>cuYiwaNDG%2LnonQWJ>Y@cKrT z2B>{{E{lsaA0$GF5~f4Pz1596P%o*R%iUKpdUE0p{3~Z`$z?Ar@8*>7+p%ysme2G@ z*!^CFJc9VnsGX#H?~oo{Ot71-U>{fODDydtLip>Df9w>L?4?Q=VOgC|mV^Y^AoiC= znJp^b71)~6uZHL0q(`u)p@DGhR~1=4YUk z=qn>3lyHnG#I@tPO)ua~8l_VSMxuL4zeXKo`ngk}6+W>jm$YJv#e`LZga~CuBauCC z0;>4ltV*9NC_1szf*QUnq%UyTPdbNNs4Ebsk3&elJJTTida{1haHPgTb&SLfUY~o7 zRTs)K^;Gg8E55)Zuyf1SGNH9L`D6LDx!p!>CAK^6T>JP_DD!ZH%J_H%jTmYg<+BJk zh;DHu7U$3xEUXD;g@y*49;hZPC3`R}6i2*8-HwVsxigvMLk(u@MRak{55X#G%DRQe z;J)6iNKfCX!#7;xjUE3OT2p?Nfzsvhy%Uu>h0DC8Eo?zpeI6vBiHxMr4NQ-<2F3cZ z@-m3#E@o-EHZUjpc61>#pQ4|qYBE~8JI#6qviXQ_CU<6>FZE4{>M|?!DV)!Mzw1U3 z;960A&Zcne{42jcy-qR$PV24Y#6`fnaCok9H%WIUY)-5{vtfxU&d;{mw=o1lk0gj2 z(tDU6w^lYl=?j%s$L`4ZijIw;g-nYyNXDFJ2Qz??%3wzIoy{(43>`|w!4Ygb96c%* z!BsWo1zA~-*ZusmxuINi+ALq}N7iJ_l!YeRp%?{%xanBm3U+JHT6H(D{FgeTd546g z?!@5&f#0<3<=^p^4xPv+Y+M7`WT=3iD8@Od1hIZM^R_vn^yl1(guw5NlYYgsv-llX zPfI^_9ljvF$mFTGVtLJVvCq818xfQG%NcQo)`{m?mG?KEK1~{k+@|)D_SE%uY5yMy zaBO=X=b?{suL!|&8R4g2`_8R1(Pn|~0+9XZ5+jUrhc4>5R|71c9T!1Btw*^5I zjjryu?@L6Fxm!waWz6SaORJu%bbe?b!`9Yf{pL~AlwAZ>9t;)B@YL&m^?MJkESrQ<-qn^ZSMo~-x8t#Q| z!!UZMdsPs6ocUXBq_a(CBdJDvlEnBI1CqjG$q*b- z$}l?VW!UeT)Xipu2$uB8$j+-FqlXMfkSE*Kqa0#~Giu@mAp(?TQipAVvs6rx#m0Kp zkC;Ua+acifaCp0#GIJ&=)Sje5il@(vd5RznWnq;V!aaGwR4-dVkdR2?-G_i#74 znekRR0oQr1OJ8vp1#NVtfUd=!Mm{nxcR2YN9~LOBxigK$A z4l!bw`iIHoAF4iDEBpv1s*xVjn({83*xYKbGtP{?zI2lo)3V~VN*_=4MEs#Akm1~`tH^h#?Osp<3SdjUkA1Y)fXH8T(1%0%VL`Z zn%^10Krsf{9I9B_Oktw5tPL;`8sJwO(T%r5{UY-)2DlM!m@ngYeqHI+T{f!`O^Di= zDp*O%P37rcQAi+0g*7djOq)`ESai4Js$aIMtG||-0^_Mi7vb8qY!oGnPeWXIdNA?L z_!W^0Il9G1?MmhAqE=41`IU6*PZgK>ScKYv_kM{f6;OWSpV3uw9#NKXQ^@HH$YXZR z%6pC}?T;#yKK1I05RplS-weva|4{b4jDFsq{nhK-iR_O-v0=X{HwWH5L{4ycjJt-< zJ)6Vnyz9HJJ-?7^7v0-b_G~sz$12Id%$qk>`>xCn%bV}lqfd%&M=l=CMnHsfiTb8t z`}QeXtG%40g}G1Ud~JpZ@8Lfb+lKTzgs`h4YNegT-JH@;^AOhW+-@Y;KuivD%6|Io z)-trOem4iVtd2B)r56z?J!Gj^1M%5Op-k4pum~LbsNGlC;hT`ZV5vM&&+%*oyR~9G^*Bj%VRt!{5tSWw zg;8Ardq|crLZ3)ODqxy4wO=%X(CC|LA7>`Jp!BA~g?o3G3z^WC<^0j+MM=Lic3c#n zpfUG0*Lx4CkYrJFy}70g{|oo)n%CJQTyZ*q6H{|~IH^9PE(-y%kEDpHQLVYeP+x+m zwK+j7s%iC#)>?$y&Sr`z7!_gjD3R^!2Jho)o%3BkA0Guz=5m9^4^5J1&VildHE+)9 zp*S98rf1`mQqz1K_SYEN0XcKrvM+-m%WA_I+nB>OYg0)|S%CO znNG~L?2+_bP+CU}Z9%{_j{2a(t=D)2TAukn%X+kwp+e^w?!&@CLz zk1JWvS@e$7Tt)}y1j5nZ&0j1&NDD(eZCck>M0=Qxnmq#=3ZT^{M0$$E@pcp_AIt+{ z&X(7UMZnRJO~S3j1ai%_+*w?A`BQttJDJbK9sZ}Bn0HUB1aVa)6G8R2d%=EdFEgiS zWc>>tj}MEN)xw8CbU|f0+F_SlZ7;AQ*(HG=Of+-eGn*L`>`gui2BQ}@R1aP5cFZI& zLped&mrvNCs&Z6+=p5J(fUOELO@&9)aY)J;K|X;TUj)3NnK?6_uN~8dVWZJi zIrE<6U+BVgViii_!Ms%8fVVwEvbA%fOX#S^E-&)gN_H~dw%fSsAbz1B{Dfy=!%`Q~ zVqsa@6+bgwc&T^>ste*e%Y{rH1i#coK08HQ6c7LC8R?mgm^boz!8(pZnECY`wT1(0 zf{x8&X3EE_^P~&Gxs}hG6FWkz5T^w(`)UX}^rjij3&&kI(YUJ4Huc-)&z1!v;9-hL zAN^vq(@d)~&BREgr{yVFci@lZVM1dLpSEDa^I~Y|amCgZPEIpU)ZL z;zOflbIXYB5Ay~rgSbx95lU-Hxqki73gC}QmTni>r4Sz;>9l)K(P(7&{TX@B?#_B$1G9So+}0s z8Kp8$+3tkZqm20;J$jQ-ijZ6l($e3#a4MuNQksQWd~{L;9puG|`T^rKH&Ag+qGLyF zVZ5C_jOM0U-0sYvyP8B{D#~PHn!m;#dbaK=8APXBFQ#_52&S9NX5oPksER)@H zx1AZA#iTW3NmUcmKaesIW?{uMNepI8-_a_1?*3lTW0ILLiV)2r*tpF_xy_s|%|<*1 zE{TNWRE8bPW-mVVENf?3d(Sn|V`p-0`nX4bnLg?p(I^93Tyydi^B7}zD_8XnFUE~r z(KoG3x|Q(74}lb5z5t<>_*dz^hclOHqIE@gClWPJtV@M5Wp(tn-HQKO46sZ*#hO!o z?cg%3nElngTvzO9cMse*@h`OfeHeK%1sFAImn&`pi-_iOpOwXxo70;R@J-Pn=`;2f z?7;NrYRdFC=llEwO*Km9_I=J=3Ne?v4`rR5&zYQlU8o0K3IYM3vC+)7GR?>kR;35S-L6cQovC+IHVr8smNSnR8 zQg{>Do3>Q*&ahZb9U|Y+Zgd_lXFJ#?V2%3oj?7LUj;wFdrJ^Y2zY_#!MOW%GCHOUs zL!p`%knjxEQ=_s0EF#ZE({8Js)$a7221yjiSH#=kf2(#BIn-L2-1&_?^`&Ed7$(m3 z7uVC-?UY<%W$Q4M!&IiR&~3%go65+b?dF^hxjQmOV~iC1C^B*>&#GrO^hcrCKyld2 zH;K!3D%sZ&RmkrW;=U41&s0}e5^iaH^i6ACOC7O=%s^g4R96gh(%0wi|J@WPwW#o0zQQ1MY2tJ`^Q@nePwJ^R%MdK?OqxmY2<6Z3OU2+ zcP~)Dcs(qBNY!kGda_u+$9*jsfZ7h%ey zbp_odue&hvY-dx>g3(ub@&KQaL7+_Qzi*;meSZ)6LpzAYQH!{+W9U=X)|rjcd~FU< zc>sCWcsRbBtI?HP5VISRcc=Z1;pCH%+aawViwzu=qM{DcBR#u}!o=zA{eCsJ8ea9b zuGN8(9{)HEVI{GWTv6Nkp`2~Yu182*zr|jg8+dW9+S~{mWxb1>TFG&E|JH*LrhUB$HM-`{BJ>R-J_LM$B}-^uxFd2n?6koA^D zHF^{-%tId+7UScL%t7Z;YaA+YVzBhr_d-!!t2E}Mndgfc4h<9|Ahv@KBay2^Dtw)Dl z>8Z@KlRlbBh%rn2ZoGSD@qvZnKDF&tzvw7xFD98M8vqOwzs4!sjkGRL=O8SOCo`XE zjxtPwPE23eLsqok?`D*hq5O4^zhgtk&A1<)AZ@fD!I6F-^H#bXRX8|(A(~~A=x$%< z1Tw5E8&U(|GIJ~76KQQ02Jo6XKB?6k-4~>kDK2a8j|OJQ%8bqzds2xp)su}~AzEo= z=088C5tW-4SoSbcOUQu!ZJ2lO_Dg+!ebId`b~HF?6EYi-el0iO&F`{bd*;Rz3Ea@k z*@=%!d&KS|`V5Dv9dr)PfN61xl{>1DxRBEbH1N)lut?AN?{-E?r>9$YDn3$s$Ur?d zziY0VR0;VJb~jL*(>{-9#+5kA9bTSWR+XE+b%>X#ed3MKJ};$W^03QhQVLs%pnfQh6LbFX7}ULJ@l;M0S< zQ58)Y-tP6i5;gpy57=#THNO4qcLaAYthFdJG^aN;ND%y!d| zKChxvw52lV^qSp?pT-l$F0q#0m#)h>RQOfxVDjMc#JgrmuZ;Qn0t!`vfj%5Qp}oZIdtXJ;bcm9N+rXxg<7M#QFBVmy$SU1| z+Yf1?I>NUaC7}i!9zRbPgd;`8SMA{@6&t_C1|AQRiBFF^BI$@dGPmMYfco)<@-h2K z_n)rPMX`Z<)I&?+bgW-YRkGtB;oIr~Es0qLvj1Pxl8u0c$cq;*%x_VAbd_ky1dL|C z_p05kkg!38m=em?t820Zn0mg#(=pfzJKE7zV9tSmqvmh?T-kp=LR=uYQa{eFR#eEd zUx6VE)jSvuzbO;P$l=G11wPIYpzB9c3>6L<{@>BQathcBE1xRxx~jL*3V8PvyD}W$ zoj-l$&fwiYS!ApEA+e_M&VRnV_R|c%|Hq3>JdOS1_rG5q*jq%0WB+*7hh`!E*#Hso z0Q%!U8c5gs8G--%_rL!h%HB)=$M25?(vQRU{Idlwlm8RDpi%*i6&Hwd0qs|eSzrg{ zfvG?KkLL_@v~9mry`$hLCHXtE40sfLp^r0DZ}3J=&n}K;I_ExroG6pZX=?_sjy&`b z=0qTTuBn5+t0uFWOGY=8f(^<(#SoUuu!TlhrUhUm2RAkm}wQAHGk7wc>mnYP>aN7pX)kO=RRHhw%Wq`*s8FJEz|aL z6IW2kF`7_+ZCRA`Ui8Dx6FWon=|nHmyod9vw!RFO9rEk!`OS5qVe{+Vk)O^-qp3o@Qha$qQce12hhwN51;|n5PJW~&#gPt4Al&2hGV(J)LUOJ zEBfE+GNReXwx1&oFs)_FM=Sh*S2V6Z1KHvFTPN>VK^a{6fxXN<*g_nhGJ*JG!j0&Eo%hK^nmbk7@*=8 zU!^6~hO(s+ao&$k2k&73!;aGYMwq3=$XVV*qv!y=^W1WE;7>#IK2Hl?Wo7GkEZ|TBSj7Ng~ibIH(Zf7ubUNv5O7L zjZ2di+70PKLd;~DV1z%kU`knJn2=F|3;LAhIqF=Nrs?3tqp=6U_OEU=(<~g288nV& zPzCDMjJ|dM2Is>2bRTM=r?-RvnMN++{Xin@pODB8a_XpTq%S20eTi&T-D8iJoS) z^nNVFPwW|s54dfHBwBzF)>b7^!uKOUYo2pi;6xfdz-8YE3@mJ{SYOsD13yPJaf@kaQqjVBrnh9%Qz4$C&% z2IFL}XU?xA4aD(u{eshiZoILZiyLchtln7KT~s-VL&y~;99EdhcytD#LA{HZkA1FH zGrRFD{#z^$AB|->SBq2=KOK!~--ZU{yFy>cq@ZCq+yEDiC7K~3?VfamJk(&>Y8QO= z9O)}V{De4F+wJ644_$L&U2f;VA$7n$EU1@1(UA}YQWM5n{uAN6QBpUwVdywq9}?B)NLd@-OJlGG}hZ{ky` zyPd7$VaHt?Gk@JfdT~LN~wqQH8O)}-LvEEoL#EKXPT zLgbLTltq!&O~rAEQ?E{FhDDU-#du42Y{zqfxC}E({$1F0cQo9`8SjQgxL>WyYo9u_ z4@Vb1^8Fc(?F}vMEaF$}+z%@X+*X`rhsk1jZ!%|d-{L6CyoJYOA2%MsY%4lI7iJ+E zZ+Yv#@b*k+=aAoFu)!-Mx5Dt5=k&^H(IpoAGqg|x$Po792rQQ_Gr1Qux90YhR7iSr=fxTgziwd>_@t;1IC>PKT$X2 z;@LAlX4Rvw<}&KKh;1jR9q;aAc!mLv9v=lsy08UL3PdMD{_?%@_WHtp-ovl-=5D~#CL?;OChRvg&3ykjYA;!g^_*fu<0p#MzsEwLlH?pzjDbZR*B;R^GVPI8~Vbypb#A$5*Y!zWWF(LtYKFSId02 z$M$WOniGUzDAXk-M#i8FJYXdV_%u4c)V%>ZF>_--0-i#ca?>YYahT0~t^MLKz=0~n zfFz(16JS=OVp54M9Q*ZdqcEa5B_dJG5g>>bvhn>|7>7Fh7a7qR^XVC-1aAP)+kTZ! z)FrV%LY5hBD6FE@cw4~?d(Z_uF@rcHPOCzeA|#cp;{brNhTL?^XwP~*mVQD550%>^ zKHUuvCtH5SY(TOy)4AKnK6*QY)O@k)QFvK4{!Rs0<8h<456_hmvQCjwo@OD~*=JLF zvfLozNIWNk(xNvox8yvxmCQL53 zAB2ZeEZXNKL6Ge(JI7+8w$1lXUTjqiv;2>-BSPu^am8Y;OJjsti%fC~Vr1A+2tR1n zQsIZSNx7Hhgg5IQ-thJUjo8^kTSN;6jH69cA)|(*5=!$K2$p>UqC4Fp8d|u5MWYiB zI(FfNMRz8&OM9mk$Up%$Tw7WIc6K@}t;IZQ?-Kbe6k~P4J?3Y`E;NE4(lJ>vEK04A=xq=Yvil*!RHwl`8NEfVg{T`L(VO&CLRU z-x^oI?>4w%t3Kufd_D4z(Tk?CKdZz>9_6EUX8n$OGyLfrk9DoJ@h|F6|Ho5TL-{i| z9#@#(tn7i-fY6D|-QXAB0JBwl3BV!L=#^GbHb=xQVZhTI*^ ziEgHZtF!~F;HsUA$4}sX-B*(&#gzX};{$;ft)|)=Rfk}kw$9$7qT6h^rP(xM-1ra( zEzB34>f{a->BhLorwcPUjc1SS(DUSx-a6l>IMjGRgXL|M5y6mx`Ky)3XL~gCZz13}MnY zER%{%AaTUz+h}w$=7|XrfIKF!lZXZXl)F=r!avs=gs#Ugbv0!jhSxy4<_;YMN{QT& z+ve3R%Q19fmW`REk;L#hlzbh?xFENQeEJr@xW{_BQ5?vh#LDU#yVtu#V)<@Zqiwyb zd(>=s(w#CeeQ19p59?I|@UX)!ZP`GEV&4VAn;ni1P-2o=v)^XRLK@_o{%oCqBPew~VrbA9D9yaD^w5?!BMiT;_8{W?tft1W8 zuFF4(&lp)8dX(|CE@^;Ry+p+TvIoFY@WTc`GJC|f%!M?%lf1JTs3JK#AunJWM`^i5 z8X?=9OEy759=`&{M9{5iChGZ@-=$Wox$MDO)MuGQ#~`D5=Jn_55OAa+Hs9K4-frEa zTuAmj{s&#enah+?U0ZmTVGY@Ii*$^<;sKve#p`>i!04q~bRtU=Z(WD}y`%(I-YNhnh8ocS8BZOmZ)Jvj~o z(z=zv;uM1I8CdVm_{&TkqI%<}Q#C}WmYY_)%jUovUJX|&fG|)WHr+d?K3y8?+3;IA z@xx#g({eA|N({~&A8}Bdm_8QY`bC|Gx@Z5 zM<4^eVkj_w?e-Zn5!%A{97Wb5*gNS{MY)!4h7b0v@wF?AOLm2kuJ!2}#Vj?r!?q%B z^q(@Rfc|WUYRQ??<{?u^Q`7s6T1~u=J5V>DHQzS0*EckRmTGk)|JZiBb9bh5sG{P{Q-SW(UWEMAKY`yzoMf4yamwyt4{f zc3>T)CSAiFi(YE}g>qbEFa7FiYMyL)$A>w(fhm9yLWkl-KC5_Vx?bLLSUx3P*g*VK zIVWfpz4AXZ7u>k~{i6_8wLC9o04(S;*hxfWQf>)NBc*O#!<`C3JK%VZ!{&86}HvMY&@Vk+{^M8368#Z?*GZddJWAW+E`%`G}zWOKpjMmlqJ@03_po z`dDhs4dv8VSuWSr#8<6^o96A4767agL`wh$mk%Wi>Nx{E@E8GLm-_N#ZDZ}@CJgu1 zmj?(-J+EafNrYNouK`^n{^P4nH;IaCRci5|s-fzE7nU0@;xa%H%>ZaN5H*nP?#pfl z2v#Nnq|KM*aKz=VH2`Pt&q6$HUL9{BT#x6$PP;WO-jJGka*0r~Z{__dkl}ftTBnAl zoA}isP8PF&0kJVCY|4|nK1;!kpTp0$3JYcq$icVRWdP}3NE-$vGTe%T_f<-vv7nA% zB`zv}e|%w(@Yrnf;e%XZmw5ynGY4vtOiKv~F<$rM-mdpMF9*?VF*(nHF5V#5h-pEx zwB*k&juRqQAi#Ew*wDO?nidpWLJKB{2ge6Ehd_N&p8WgBfdv7F6J5~VI+tB@0Kl1& z(c1L}&5z{KRPK8HCwB{Mv-w?NXx_)BIHb4at!knNDyHvrwQ1@2<3RKcEv5ieqx2O5 zVYy&xm_mt~kj=}hffm`!guZ+rXBLcfHIXF?9PLsWzobYfA4wxA^oX ziSeVe#jChQx?gyWd0GYPZBFDtx?jQ~ul_*STW@s6^F=tQQS-P=GxQ%?UDN)dPM;>{ zh*QfX@%VXHI11pK^)l^lWLEKa^xz;uJj(0Od|<+13sdaw&6~{huAh)6_JY}sDo4%o ziBs$#LP6FpNGEZ9>qZ^Oe>$Zw3khP=OR+;b@vWdXtf5_w5|242F1dL5^RA@i zif1Skz3j+lq)u@&ZFZvtJQ=bx%3_UItG#}*u-O940mIRh+oQo09JgyxXNHY6xqQd_ zBu7^lKCacc;?IsQeL|mI3UAtY+)N6dzcs#cc&V>pU<8r^lNN8cKaxcsYl^feW*vYG zMzUwXWD6FBiMPj1c8VUatIRkzMWpM8sE#+=Y6SS2M_ceMd3^va1Go>~%+X1nL`bt- zoPt$Z(Y$)%?YZ1aepiTb18$t8!2)SqcOjN_WZmnH31)@S1@(rW`VLT9E|u*i3&u*=vrJ(3K)RD@SXtv_pDvH@9BCv_PgTw)qa(aN2JV5SV6!%v zS%vI*o&Km0Fv>2rWf_RZ>MUzWd=rqy+h-f$cVLBm4y26-z%5;2hY?C{MWLNjW!0Uu zCIEvBjBDXqK*i3U(&FZdnQps3aNWaTol++tv#lAi@>MAaOTp!PAl-K+`PdPLeB;oZ?ed{WXank~AIc3bIZCttVCau{sk< z%D4};aa~uOk2Tz51JgpdJEd*B1fX6MgehJ!^Llc~EwCX8?nZv;FyTr*QHe*mdiDxX zi1_07_Hd8!szCI0kSy2MAYVo!aLH`fKmdf;H+q7S02c0-3W{!fhOqh!P*zsxPMdM6T2}$ zi$4ph)bcxr$|EB^;cg;h=GpAS5GpKz7is3Z#x=abe59%(<*`&^cfs_w52O^Pf#St> z%y9$KiIpliX}m4f6XwL5jV^w3D4z(m@pVsOcqX4-6Ch_au2voXd$S5X4Xki|Zk9eA z`??ZoRD769tf+1(FNnteu&1>2Bg9x&4`Cxnxwtp)G?phGApZPfIzV1S1N&hf{XEA< zZ%q6+60ckR4^s$FxWyd|2e*u6wY7dnbufXT=8BSI-vNwGJF7g&D#fQR!vie z^3uN80fy;tbLirCCJ#Vy4?vint6GP$+U2!Yg>J(?*7p&oY`@i z^6U!N<81#2{y9X;=s{JhpKd8YLs@eNqr4zDi2Sw=N$2zy7YE*gOKtS}@>~q|-QG1G zDG${_U1hBJQ`Euh=c`Rg99Y6O(3Sw=US1nH zTFZdaBgt^pZo6N=xZ42a{mbs^kqx+z^K%lPcswQ>EbqaWeJ1^7>j!OLCt>BV5kGTttdyEs9$Y%F$+}Qs3BrXRC7DW(@d&oXd#t<~{E_G126gqcS}YtpV@{qZ$y>S!9>Cw` zzx-3q$R;2Cdtv-fqB#FED*)QXegEhHh|Itb-}wKtwH0%I`t0v;=c@-q5Jj(4mcNj9 zte6+UTV^Qlt6AL~o<5xFfBXO=sAJ`Vul1C4T za>_XrvdB63KV15Ll#ytqhqL|{(HiwR6tBu%379wiI|c)T0A5LQ7HFlz|0jNUK#CPj zBinCpW7LOna+5V;PJT{{1MRf}cB=hM-TEzb-Iu5w^J9+bZw8d*NO_z+#zhZr`mD-e z!>BnY+m0I^zrQcw^xiKyO=-y-@}gXQFw_#8=WMn3CE@;e)3$AP{@?%ma_y3#M41E$ zs@)kY`T}|Oo$A|m3}6uZGg7mk?mYE5)5wrS&ag=*PRk80G+5rp|7_^zr&HnJUkd&e z^sRpoQ*G}3M>_K87`$!g>3=3I3Lf{N0qI}*u{xE|)7Zx+zZlmli;dk&_t^av^)ETaH3)cb28eLQ+QuL@i;ErjIimTM^~Cm-^8x^6x3I+^bv_FL+~_5t z{2%~8?pUvyto&g-_R^UwQh&IIG0{CSuQF-$kVpiI56p9-+wHai3@_=mjE=JkWT?w5n#v#ZySbUDlDZy z2~gi2(LCF>@JX&^Ss~dAK}`O*5r`R7b9+%m+nnI_;CzI!7hC+iQH>xchOK}(&;tmy zo5xnOPoX}7Et4eb8PKOC0W$9&fu08GZs(D_ild_0c*)1an(9ECJ{N%7jUA9Tsbnt`f>^IU#JM1J9~(CZ#pG_Xkf;UtqP zfGBSp0mMAu#V`+gDYxyy1NQPEbVCv}Z9o5AC8_IzJt<}iG_P>;_`lcEvu_Fj04_lP z-dp)NcOn`HQpQq^y1=kW7sok1#vm5tk*Sdb@`EiFuC%s;ve-lUFny!03=qc>!j9n7 z!j8g0pqCwul1nAz2V}^0gFF0(G5t0T!Wr9dv4$0aV`ox`yAdoOG`H>?d;tu1v8CQ{ z5|&@777SSSJv?z&qz|y1kXRqHIH1FbI0=t_0&-kBg2;Ih=Le@Y9U99C7??r**HJot z9yqMbAE3n^EJam|msoEzJyNIqlnKN*D7_Oe2twp16HuciVBz|@J>|vHB8LuH8nJ!B z8lZ-4czY2?YQtW$#b*qn$9TRLC4l{ zJ_yVv4K)4&hV&Td|HeoJNvPKyE%urbIg$83EMdjtyo+>V2fC>Rq`QJCqWX#l3H*D) zw`K!RBfoFJ{|D~v^Jack>mTi}0yj^8_t5^-T&^^|H>Zq z-=p>4qowe_!%DmxEQkNVrU(KaxgPU#UhvcncOUw9NcGuV{dFt;XWv##Zbsbucc5|g z^lIGZb(*qQSb%{3^d)8m8(ato$XI@ICG$Sd?i6Cje};?f#{nmVfZsJ@!_)T&hus0Y)OJxlzJ-4M`Id)3nC$Gx+i-S) zuJ;9(&(7GNo_cF9?MnbDcgqU?r&gc-|KSJ!S{3#0z69L4*KAhht%}DV$VwhLVsCyH zM28luA3XR4G<*7I9{e`LQ~z@}4BOP-2ew828|**p7_a!rI@#LnQI2py{zBOC-8~$ryG|+{W~5 zJ=u1wDPT9r&L~Iw^W?sT>e5tnsJRQb&bFK#6b>&QY!?|whLOYCUo_ey9kNt(PydOw zU-sAxNQ>WvUI;GP7tr-NB)@K0v9|&qh}rdta=riB*z*>9cEQx*@YD~V2Lt}$a{D2} zh0`P3)qX77S%oC@h8kU0`@()evl31($QsZhXKe3$!Bg3N$&R|Rs)1#q__?%+^1=@0 zme2tXOnX6uJ^2H^`=?tww%Tkw8uzu2&0pV~{@1a?r_QnuLcFJn6RWZpJjGgWVO4;!6_+g2qcA{DxakqJQOPzP>HnW5h z^X8nR5e3sx{`=SBqTb|uo~@22Mrz|88sd|Nn^!C(Ftx~_OQ{&%1}j-_l(Uv*7%Znl ze$qu|N82ZEFg9DueuryqaMFwaK2G@QHSW3TX3BwS>!z0iug=02MSD z%Z!&Ow;M0f=_49yic}9UH7m(f>jCq+1E{$LD2b?*UZtc)+JE&e88* z=+Gj6zowqxU$)z8Ir52js=(@UIj1hiU8QgmrS5^peKC{cxf7Rer^W{=?+)0Z_9M0b zWZOwYw~YOnsRd`wnwaf=z(#a_kHFW;d86wSa76T{7wX5iCvJ1KflFg_{JufAG=8+X zB>uUyX^W^}s?e{iujM8?>V9na?66hi-%kKd3kgi!6|l{CI(`40)a!v?R-El7q}oxc z2QB6Kew8+#yINoFG>^>YJ>i(;A8!4~JEd4-knrswQCKxiN>hJ76gyrxoj5$(^kw)< z?Eal!26pZa(3Urt0=m8HOJlAi+GcD}c7gYy6L1yXI~42yQFujm{ew1h+vLKOb^kVt zyi(TufGtBZ>$B5Ce-Sm6IIMcl!_l`y}h?p%tj;~ z*8S4njrOiCKMUUOTJ6nIQ=|Cl5AU`#=RZV=?zu?54UO`i{!!a$g(1A^sJH6m|I6y);afuJ>0l zmHWO&Y((lm2Q6cN_Mr$oF%<(6cPw!0Q4w?3t72o3Z0ZYxs1rCk(elLRCb=;dy04sL z=m3N-a-$1W)(U{AxlsFQfk=2M^nvB-B<93E2U4O}U0WKL=Utpm=Ka_`c5}!Hq4vnO zneQ``>kC}|qmr|5?BmSTdx21`03Asuu{#Lc1@D8<&pWTKjIp81aO{sS-QXzy+4V#G zh{8$dGvw$RqH_Nyd5ryZ5un)td(_OSTkYClpmuia&3|^EL7eJ&y@i~BWWT*$Oblhd zwhaNy>2W}UDkyZZilCgR`R-1wij$ zfdz&r9$>Zhg>jprFV4&ZrG5Yxu#SgXa_GCQ6PXr5hs4EX>*ceFi|=1O;DTLKn=WV0 zR5c#I4r}1{#gCg0Pl&G9MM~ybXpjgOCiJfZ+U%Mp0D7QfX4k=)auaS()11kqFIq;v ze|e8e90l!9ieA(W0_KD(KR@yuJV6r(wkckZ0pY8lkY58%xj@1Yz~dQ~fIMYrFTDtn zg}A6zaPZQ7%^t~+sFkISOD^v0_i?a|HW}BUM}Bh+=YsY56^{U@6<_+!Kzd(}N$#O1 zERYD%iOcPYgQ_tEd%JAqKy9_raIGYnT9%))(x^PobT(++9?`mp6oE2>PHt;lud92s zoS@vp(v17?Y}#4&(OFEFeM*z{#{&sDe$Wl}NU}(fVPtGqtGV>FkA!i`GM!TCAq5Zx zC9!iGdhnGsGH1#F)A@1E&hE!9=Ym$4&fAlu9Rrj|6(r*Zr4$I71+w8&RHAdj zTxKSp2|%CRG_Zy}=$|cSLiPDWXn+%Bb;R&R`5lL60QB9sQ?|^MHSbXBB3ya*N5bKy z&^7;8dtV-wWZM7ROz+q`W!gL0VwSd9VW_E-xuG-JGA?DIXyihsV5O#MqNr$1Z!%+x zsR*KmHKmq&kQlBtjH!UoOr??wSt2Qlr2@Gi=YG_hnREX7opW91ulY;i;<=yux!3Rg z{VY%0rvzch8K4svBwO*`ZMJrHG>tKRIyfgNI$wu9v1O=7y0yYh~M5VTvpp>&QE zt}2TlmWXF*#!znMJm4yfCbu(h0x>5qbMQ?m_dY~c4&7GmpUNmzvS+42+GT<|p70>a z+5}3qHHIC>AqGM$zY177?OK-XFXX?hGSmfAdo@-c9Dt#P+ zO$37**quG)IoSG$9=3m%Tu-&2V)ZWwF2re-Wv;thj-}my*uF7F>#^ru?5%`2n@8H@ z5H#x$uGIf9T4({tf9pL@`ImYmzFA3xV$3ypu!lqAGhLu*S5Y%(91d_9syZoxDEjcs zSy!QI?OPX(fIK9pMkqX(sNEC$^_o#|{vVwks+c`9Ml)v50u@A~Mmq|@oV9!or$YIz zRcS&-BjDD{Lx|;B-E_4)3 z;P#W0WT3|x$!FrM4m%lwXcNJ;d~W;!cM0cSaL! zYO6~dFWrfWt0fsFJxac={XXd<$4IVgOm)U%z4vM1GC?}AM6;RP+~1&|*!Eo%-F$JC z?SFl^m;8jAh63UT3tB^^?6JJ6a@(!`{p&qwUr?popr`~~`wPN(N1z1pz;UtlU_j@) z)5npI@_?<>(VM(`S_85vDv+wLy7U6JoarDrtRutAtY6I7|oYrA#;5pElw-}AvZ8g5~Olj+I*{da< z`(9dm=x6+BIYh(}a9^SVSBPe5AF?=O5_SJMRMgAia{hs0k&Whqk>+x7 zO!QcG0S)_M> zP*$f|Pe1=IOqP?M1?7m;Nk=LWaD(W7|2iXY4HxN%4yKS4?t!$9ws2ATBw~E1q(Zs> ze7Q8%iyraFL;CC%X6DTrq5b5fV7#i-~= z@Xdb8D=9iFd>p+rqlP{m{CGEYJYIjItg8WR9EX=Ow=Vu2c$yHdwcH|#> z8L}D7JPHh-ZeRkW^*Z!5q6t_E#Ya3xE-Lu)9pj9<)#8xmh;{2dwVyIP6MacaLiBr@vc=4v8fEd3fgy?*-=d|MmW_^#P%@4 z;(9Cfd}7$`wY|w^(sRa6u_DzSZklJ&VMTu6Do2Iw%|lt(Ni?rflw9m?I!V0Ig?uUr zVc~2GDzBKJ#&T$<$(HaAPsanUoqoaw%8$E8Z9AA1cU!o^p-5lU7k)yY>A0n=!~k}l zc!@|*)!6Rd`YHhC$;7;@@w%N<9@A#@ul{&0i#!Os$fQrQTk(g!rVwq?+M-bx*Ne=D zi2a1|S~*B`zZ1vO@3e2hVp1QE0m~`X@BOGN_AlM4GbLX}or`?oY zGr8(|U=CB^N|YPRsH$9H1;AKKCkcXh6hr*-Z3?ts*%vnh~3>um1qcqs+!rba@Qz10k!A_*aNlqMk}BZ^W!^((~`Cju>4M z9*wxv%3+kc-nJ|42Q;I0jCcnrkl#1uin|u$`B@P;WQHHfm@xSk`1yGzVukvFr;~706CEBXNcl zJ15_z7tz3p$SpOa24R)TFfH!Sb<_@0D)csToRbSHc@3*U~U|M3V9l#OgG^T9k~tD-dQDlIuQb}%}T?+^xF~A9v(ma zG;AI6Oz@FMOYf0=3Um@a0;Q@u5T+GdSnh;%FdZN+6GnLJLnVCY^A4*Ya%Hv{K_unX zE*LQduud){mWK>@gpnC=wCqL#M_4Ys5ywhkv-xY?XAIvQu3##3@-QdZcdJ{CVzqP!QK zRPeBMm_@EN<`q@@D09ha*!q~Z@&2e)-y6%mg_B4!l+_rcqUab;iuwwvOn2C(89)@B z;!bn5n-b;Ds7PsSuyKdgod@Mbo!kV+TrkRp?P1m9bt23d?V$2yjwv<2;CRO?4GC}t z+7cURD4*70?o?fo$^b08iXFVQE9cKQW|#E*!Dhb5&SCpu966YPWA1CqzVA%kZwM?B zOTl5}^|`z7R>is#WgYqyEQ-Dlck5-~nk|bTvps-4$ZrEt?J%srYYP8e%XjCqupBA3 z^yh29$!5hx{l@}0#cmX(RU?bX30|;U1Ot9xk!-fQ5x_h-#%|}|2}E0~v*B*10ULj! zloh5)yq?|#JjW8&(vwY6q)x81+`TX$w%r_qkRUhU&()9E0XBz?(9bdPrYjr`Lb>_){dxt<$~d$JT-45$cH7Bvs@|81XxNE?Zbg-$RvElG4Moi5s1E z#^hIJNNsZm8Kv6Gnt{4WH)SlT_(HMhzc4z(qkjdRO?SR|fScQwaf*HxUG#o=M z`ETk}Dd{T=So74y88Xqf7{v5U0T^W6f|`!_0jV{WxnWFSDh>OnxfF=@mL#?_k)uvS z7|=n{f7ute>HkakU7}W|)eXP>FDqxbeN9n_#R^!+8E+8;6wWGRfq<&7Bg&T96*YTT z`-C(;^#(HwyW7U!sx9h=>cKB$R}_b-5c=dBV|d4IkGEyLLu*ibG*H}L_Myif%j0@Hb~I$)xcdrx$f_L6BxBQm!AjY!Gh9lTx^w_B*mkGUg5j~>z-T9t|xneg?m!}Tapkwm)6tJET*Ctx; zUS{4Xi^@;bs&dldRZlsPWJahLSbLwL29E4`KIb8rGc#vfY$u~^r~jyYQPLs6myA6E zF3mOju%!laHVSr#|eo4fMDM1^9#VLR1;%<;k&#^a?x$JeF^Z9hlrVV;f%U=`mp4a7~em9Z9Z-We?) zWfggeZScGI^#$6+8Kxk2Q7^a#_Ki^st7pjWfq0w~b{kqb*%xTsVZw7oB)9MGdhbUH zEM?qZn)Ac$EXEe6YIZVx%c#cBY(JWE(ma2E??G;9qBTLVjD}tf>lqI~9>FHOfR~so zj`n3mn}uvU?kLXr-X}kB>2oq>vOY_OilT@5NBh!ulCHOfHZd9-Dj_+QST&RWZs6 zb#GG{#?lmSv~-q`*mj;98nQaL3}dz(7Ksc%eX(}Z_6LGbmrX0)J@EX{i~CP@mg5_s zC6pbARzh|GjJ9z{hIwIS>uvj>8h8l+<(t<}E~%q+IuJ}2LwDdDOjw*<_N6%^ zVgR9&Iej-VCm?{dk1s2HVNfXeu&2H*LtVm3PKxcArR4*#9N!W@AYR{V&5L@(yFJ_t zBSr7%F2y9xMzfJ57GE;6Y|)}fM>6&*psdU%`oO-C#V4<$cL6QhYM70sjU%xBT-u@i zM~JoH{2-g6{W|13&0fjwv5)H!Wt85Uxvba zMC4b=GrviS&o^O5a9Mqu_HG$9mUsmx=bDW{%RHlgY)01hhU;iWjQf?pnJ+7vaI?tj z@;obvzj>)VfQj377V!LQ0U3Qtc*c&6pasx_)nnbXR%>?zE}oE~zL&&`%I}-{(tGh8kl-CYuh zW>EssPbYa->CCnq_wUKh8HGRCbr3x#mWLcT#0$xcz8J125*Nd3yU*ZW9*^4bd@u^k z+Pe?AA$D$N2U17K{+!Lb3f@diWeuvxBlh)S;B*gIbcwLfVL^5R&?I|D1>C6;77Oz2DZlnlOZ?dqy=*Q6L0VI@0mf6(z2@*5crF>6;Gu-j zHV`BO3NI9xosgILP&W9_oQ!>6R?TC8L>fe|oriV+L_o=~0Cb~q;Ohkel>@ywSkbSv z*(*I4t0_91PAYc(=M&I-n!wc(8de>+wgm2L11VzXA%(}?;BEVT1(^R^(qd1tI)(v-7n&H-`e|TOzl! za>o@20Ll0(_;eXuuuJ)6*{6E@q9~>VzYP{JhTng%>X~gakFQB=6Uj|mCp>FVhZgUAHN#C7F1LVI%jI-%LbJI z(eq&CvgaO9vsVU|p~(9`qq(1fHP@%drlKj!|EyYWVjxd)_B39EM6=HCeU+Du7lXF# z$StZ7I!4_}c-OS$ib^%21B&eY*bW-adXCEb8EOS>MggY#(w$a$d)*em$EBb}lRtP# zO%r4Qd2D-IRJ-T8=|u9TDy;Owta+hfFaDwC4FqLYiatZZtpHEGoCLvtF%66)Z?p=J z)-s*PMIP_aPP=)tF@Bw4szvgDvNE3!Y^|zLT^9MOF^43}0Tfx_1nC@6cLXxeo$_yv`S0``)@S+cFkkv3-=cxcL(g{OQeBo9403gL6es7>im99%Njlc^$R3 zoq6*b^LY-L?8H$`HNK=^KXlGbd878gip8zJOTH}2;OH*2?oU^3IS&94;f2i)m>bxz z;HEH!#MxZusTbcQh`un3-j(`fOU&;~{Sh{KuQ$h)7c2*WPG)8;uS zXZ+@#61~pqDA_{1FoVe6dbpT+0}3ueXw6&0nH5W3tU0jI`soisYzB@7ZQOljc;dLR z=!D-_0}CPX)%Lr)a%UMGrs^gy;-*q_(kI4?f$j6R8$+~$j5B$>iv&RBnd_ix+-W>Z z^7%IwAzJ7VTmH`ZJWuB?@5D`hvMaYgjVW9U?d3&C)Cyf|&^|%k`>8%{->d}|i4gGu z%#%fnP5~h>v~13O$eE`i%>93%aQ^A$KHP#W+$WMH?-tjqQjB6Xlc^DS%^~# zewQ!aqdAiyOd5?q*qSLB99SzopNG) zwy(-*L`bC2utv2b)g@=0#C}``oaEtN3{L>6hMLfFcA=B_7^MeNn?t;MA;@>GmYth- z1?onFwnPV3omnSkhwpw!JoXHkFZwPu|4WS*~e<1fqRr9 zu_B;U5EB9SV}y#B9%g2uL(qBlRZnkwqqmud7Y{$gknVzNGB%<-^o<`aAdoM*Y%)QSh$b~-{;w3^4I6n1{tmZ3l*(lj1q-Q*lZnSDH%;d|(d@sUo7$J69=c2Z{Wk5O-XqoB=+5IFV-WQonYHQn6+^4viG25yXYEz=9O#}B`Q%n?v3z)uRT20o;- zQCvp16DiEW7kO{qn;e_DV?a1$VJq&8ZyjcKR|CW{G{h|{@_9ug#RVsQ!IKJ=s!7^_d+AZ5%o~`=JXY=f(C(v&h)4EnT%vQxnc5a;H$R z)Kf$!6O_Z~2k?$qdABIP8dyh!6Il`BUdRzdO$3weWj%2HETKo(0rA}kRuQe~*V9RbH#hgq cK|;+EN~8g618dR-dO(-`hxe85J@MoJ0+7)Fy8r+H literal 0 HcmV?d00001 diff --git a/docs/en/portal/apollo-user-guide.md b/docs/en/portal/apollo-user-guide.md index 56674a741fa..d084ab5a0aa 100644 --- a/docs/en/portal/apollo-user-guide.md +++ b/docs/en/portal/apollo-user-guide.md @@ -509,6 +509,29 @@ Please note that modifications to system parameters may affect the performance o ![System-parameterization-of-global-search-configuration-items](../images/System-parameterization-of-global-search-configuration-items.png) + + +## 6.4 Parameter settings for limiting the number of namespaces in the appld+cluster dimension + +Starting from version 2.4.0, apollo-portal provides the function of checking the upper limit of the number of namespaces that can be created under the appld+cluster dimension. This function is disabled by default and needs to be enabled by configuring the system `namespace.num.limit.enabled`. At the same time, the system parameter `namespace.num.limit` is provided to dynamically configure the upper limit of the number of Namespaces under the appld+cluster dimension. The default value is 200. Considering that some basic components such as gateways, message queues, Redis, and databases require special processing, a new system parameter `namespace.num.limit.white` is added to configure the verification whitelist, which is not affected by the upper limit of the number of Namespaces. + +**Setting method:** + +1. Log in to the Apollo Configuration Center interface with a super administrator account. +2. Go to the `Administrator Tools - System Parameters - ConfigDB Configuration Management` page and add or modify the `namespace.num.limit.enabled` configuration item to true/false to enable/disable this function. It is disabled by default. + + ![item-num-limit-enabled](https://cdn.jsdelivr.net/gh/apolloconfig/apollo@master/doc/images/namespace-num-limit-enabled.png) + +3. Go to the `Administrator Tools - System Parameters - ConfigDB Configuration Management` page to add or modify the `namespace.num.limit` configuration item to configure the upper limit of the number of namespaces under a single appld+cluster. The default value is 200 + + ![item-num-limit](https://cdn.jsdelivr.net/gh/apolloconfig/apollo@master/doc/images/namespace-num-limit.png) + +4. Go to `Administrator Tools - System Parameters - ConfigDB Configuration Management` page to add or modify the `namespace.num.limit.white` configuration item to configure the whitelist for namespace quantity limit verification. Multiple AppIds are separated by English commas. + + ![item-num-limit](https://cdn.jsdelivr.net/gh/apolloconfig/apollo@master/doc/images/namespace-num-limit-white.png) + + + # VII. Best practices ## 7.1 Security Related diff --git a/docs/zh/portal/apollo-user-guide.md b/docs/zh/portal/apollo-user-guide.md index ef42e470222..cb38986bb2b 100644 --- a/docs/zh/portal/apollo-user-guide.md +++ b/docs/zh/portal/apollo-user-guide.md @@ -482,6 +482,26 @@ Apollo从1.6.0版本开始增加访问密钥机制,从而只有经过身份验 ![System-parameterization-of-global-search-configuration-items](../images/System-parameterization-of-global-search-configuration-items.png) + +## 6.4 appld+cluster维度下命名空间数量限制功能参数设置 +从2.4.0版本开始,apollo-portal提供了appld+cluster维度下可以创建的命名空间数量上限校验的功能,此功能默认关闭,需要配置系统 `namespace.num.limit.enabled` 开启,同时提供了系统参数`namespace.num.limit`来动态配置appld+cluster维度下的Namespace数量上限值,默认为200个,考虑到一些基础组件如网关、消息队列、Redis、数据库等需要特殊处理,新增了系统参数`namespace.num.limit.white` 来配置校验白名单,不受Namespace数量上限的影响 + +**设置方法:** +1. 用超级管理员账号登录到Apollo配置中心的界面 +2. 进入 `管理员工具 - 系统参数 - ConfigDB 配置管理` 页面新增或修改 `namespace.num.limit.enabled` 配置项为true/false 即可开启/关闭此功能,默认关闭 + + ![item-num-limit-enabled](https://cdn.jsdelivr.net/gh/apolloconfig/apollo@master/doc/images/namespace-num-limit-enabled.png) + +3. 进入 `管理员工具 - 系统参数 - ConfigDB 配置管理` 页面新增或修改 `namespace.num.limit` 配置项来配置单个appld+cluster下的namespace数量上限值,默认为200 + + ![item-num-limit](https://cdn.jsdelivr.net/gh/apolloconfig/apollo@master/doc/images/namespace-num-limit.png) + +4. 进入 `管理员工具 - 系统参数 - ConfigDB 配置管理` 页面新增或修改 `namespace.num.limit.white` 配置项来配置namespace数量上限校验的白名单,多个AppId使用英文逗号分隔 + + ![item-num-limit](https://cdn.jsdelivr.net/gh/apolloconfig/apollo@master/doc/images/namespace-num-limit-white.png) + + + # 七、最佳实践 ## 7.1 安全相关 @@ -512,4 +532,4 @@ Apollo 支持细粒度的权限控制,请务必根据实际情况做好权限 1. `apollo-configservice`和`apollo-adminservice`是基于内网可信网络设计的,所以出于安全考虑,禁止`apollo-configservice`和`apollo-adminservice`直接暴露在公网 2. 对敏感配置可以考虑开启[访问秘钥](#_62-%e9%85%8d%e7%bd%ae%e8%ae%bf%e9%97%ae%e5%af%86%e9%92%a5),从而只有经过身份验证的客户端才能访问敏感配置 3. 1.7.1及以上版本可以考虑为`apollo-adminservice`开启[访问控制](zh/deployment/distributed-deployment-guide?id=_326-admin-serviceaccesscontrolenabled-配置apollo-adminservice是否开启访问控制),从而只有[受控的](zh/deployment/distributed-deployment-guide?id=_3112-admin-serviceaccesstokens-设置apollo-portal访问各环境apollo-adminservice所需的access-token)`apollo-portal`才能访问对应接口,增强安全性 -4. 2.1.0及以上版本可以考虑为`eureka`开启[访问控制](zh/deployment/distributed-deployment-guide?id=_329-apolloeurekaserversecurityenabled-配置是否开启eureka-server的登录认证),从而只有受控的`apollo-configservice`和`apollo-adminservice`可以注册到`eureka`,增强安全性 \ No newline at end of file +4. 2.1.0及以上版本可以考虑为`eureka`开启[访问控制](zh/deployment/distributed-deployment-guide?id=_329-apolloeurekaserversecurityenabled-配置是否开启eureka-server的登录认证),从而只有受控的`apollo-configservice`和`apollo-adminservice`可以注册到`eureka`,增强安全性 From b32fcc60db9a05557e0e9e74935acb55a55619f4 Mon Sep 17 00:00:00 2001 From: "l.xie" Date: Tue, 8 Oct 2024 20:24:59 +0800 Subject: [PATCH 7/8] feat: add observe status access-key for pre-check and logging only (#5216) (#5236) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add observe status access-key for pre-check and logging only (#5216) - ALTER TABLE `AccessKey` ADD COLUMN `Mode`, 0: filter,1: observer - portal: CRUD for observe status access-key - configservice: pre-check and logging via ClientAuthenticationFilter * changelog: add observe status access-key for pre-check and logging only (#5216) * refactor: #5236 * refactor: #5236 test code * refactor: #5236 Update apolloconfigdb.sql Co-authored-by: Jason Song * changelog & refactor: #5236 --------- Co-authored-by: Jason Song --- CHANGES.md | 1 + .../controller/AccessKeyController.java | 8 ++- .../apollo/biz/entity/AccessKey.java | 13 +++- .../apollo/biz/service/AccessKeyService.java | 1 + .../src/test/resources/sql/accesskey-test.sql | 10 +-- .../common/constants/AccessKeyMode.java | 25 +++++++ .../apollo/common/dto/AccessKeyDTO.java | 10 +++ .../filter/ClientAuthenticationFilter.java | 68 +++++++++++++++---- .../service/AccessKeyServiceWithCache.java | 13 +++- .../configservice/util/AccessKeyUtil.java | 4 ++ .../ClientAuthenticationFilterTest.java | 53 ++++++++++++++- .../apollo/portal/api/AdminServiceAPI.java | 6 +- .../controller/AccessKeyController.java | 7 +- .../portal/service/AccessKeyService.java | 4 +- .../main/resources/static/app/access_key.html | 25 ++++--- .../src/main/resources/static/i18n/en.json | 14 ++-- .../src/main/resources/static/i18n/zh-CN.json | 16 +++-- .../scripts/controller/AccessKeyController.js | 12 ++-- .../scripts/services/AccessKeyService.js | 7 +- changes/changes-2.4.0.md | 1 + .../profiles/h2-default/apolloconfigdb.sql | 1 + .../v230-v240/apolloconfigdb-v230-v240.sql | 43 ++++++++++++ .../apolloconfigdb.sql | 1 + .../v230-v240/apolloconfigdb-v230-v240.sql | 39 +++++++++++ .../profiles/mysql-default/apolloconfigdb.sql | 1 + .../v230-v240/apolloconfigdb-v230-v240.sql | 41 +++++++++++ scripts/sql/src/apolloconfigdb.sql | 1 + .../v230-v240/apolloconfigdb-v230-v240.sql | 25 +++++++ 28 files changed, 391 insertions(+), 59 deletions(-) create mode 100644 apollo-common/src/main/java/com/ctrip/framework/apollo/common/constants/AccessKeyMode.java create mode 100644 scripts/sql/profiles/h2-default/delta/v230-v240/apolloconfigdb-v230-v240.sql create mode 100644 scripts/sql/profiles/mysql-database-not-specified/delta/v230-v240/apolloconfigdb-v230-v240.sql create mode 100644 scripts/sql/profiles/mysql-default/delta/v230-v240/apolloconfigdb-v230-v240.sql create mode 100644 scripts/sql/src/delta/v230-v240/apolloconfigdb-v230-v240.sql diff --git a/CHANGES.md b/CHANGES.md index db77fc9598a..f151343137f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ Apollo 2.4.0 * [Fix: Resolve issues with duplicate comments and blank lines in configuration management](https://github.com/apolloconfig/apollo/pull/5232) * [Fix link namespace published items show missing some items](https://github.com/apolloconfig/apollo/pull/5240) * [Feature: Add limit and whitelist for namespace count per appid+cluster](https://github.com/apolloconfig/apollo/pull/5228) +* [Feature support the observe status access-key for pre-check and logging only](https://github.com/apolloconfig/apollo/pull/5236) ------------------ 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/controller/AccessKeyController.java b/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/AccessKeyController.java index 1e95a2ee8fd..db8aee73e95 100644 --- a/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/AccessKeyController.java +++ b/apollo-adminservice/src/main/java/com/ctrip/framework/apollo/adminservice/controller/AccessKeyController.java @@ -16,6 +16,8 @@ */ package com.ctrip.framework.apollo.adminservice.controller; +import static com.ctrip.framework.apollo.common.constants.AccessKeyMode.FILTER; + import com.ctrip.framework.apollo.biz.entity.AccessKey; import com.ctrip.framework.apollo.biz.service.AccessKeyService; import com.ctrip.framework.apollo.common.dto.AccessKeyDTO; @@ -27,6 +29,7 @@ import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; /** @@ -61,9 +64,11 @@ public void delete(@PathVariable String appId, @PathVariable long id, String ope } @PutMapping(value = "/apps/{appId}/accesskeys/{id}/enable") - public void enable(@PathVariable String appId, @PathVariable long id, String operator) { + public void enable(@PathVariable String appId, @PathVariable long id, + @RequestParam(required = false, defaultValue = "" + FILTER) int mode, String operator) { AccessKey entity = new AccessKey(); entity.setId(id); + entity.setMode(mode); entity.setEnabled(true); entity.setDataChangeLastModifiedBy(operator); @@ -74,6 +79,7 @@ public void enable(@PathVariable String appId, @PathVariable long id, String ope public void disable(@PathVariable String appId, @PathVariable long id, String operator) { AccessKey entity = new AccessKey(); entity.setId(id); + entity.setMode(FILTER); entity.setEnabled(false); entity.setDataChangeLastModifiedBy(operator); diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/AccessKey.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/AccessKey.java index 3e927bbf914..a66ade65c15 100644 --- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/AccessKey.java +++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/entity/AccessKey.java @@ -36,6 +36,9 @@ public class AccessKey extends BaseEntity { @Column(name = "`Secret`", nullable = false) private String secret; + @Column(name = "`Mode`") + private int mode; + @Column(name = "`IsEnabled`", columnDefinition = "Bit default '0'") private boolean enabled; @@ -55,6 +58,14 @@ public void setSecret(String secret) { this.secret = secret; } + public int getMode() { + return mode; + } + + public void setMode(int mode) { + this.mode = mode; + } + public boolean isEnabled() { return enabled; } @@ -66,6 +77,6 @@ public void setEnabled(boolean enabled) { @Override public String toString() { return toStringHelper().add("appId", appId).add("secret", secret) - .add("enabled", enabled).toString(); + .add("mode", mode).add("enabled", enabled).toString(); } } diff --git a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/AccessKeyService.java b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/AccessKeyService.java index f70f8be6822..641d801d2e0 100644 --- a/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/AccessKeyService.java +++ b/apollo-biz/src/main/java/com/ctrip/framework/apollo/biz/service/AccessKeyService.java @@ -74,6 +74,7 @@ public AccessKey update(String appId, AccessKey entity) { throw BadRequestException.accessKeyNotExists(); } + accessKey.setMode(entity.getMode()); accessKey.setEnabled(entity.isEnabled()); accessKey.setDataChangeLastModifiedBy(operator); accessKeyRepository.save(accessKey); diff --git a/apollo-biz/src/test/resources/sql/accesskey-test.sql b/apollo-biz/src/test/resources/sql/accesskey-test.sql index 2ea1c96e9f2..36ffb2a1b6b 100644 --- a/apollo-biz/src/test/resources/sql/accesskey-test.sql +++ b/apollo-biz/src/test/resources/sql/accesskey-test.sql @@ -13,9 +13,9 @@ -- See the License for the specific language governing permissions and -- limitations under the License. -- -INSERT INTO "AccessKey" (`Id`, `AppId`, `Secret`, `IsEnabled`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_CreatedTime`, `DataChange_LastModifiedBy`, `DataChange_LastTime`) +INSERT INTO "AccessKey" (`Id`, `AppId`, `Secret`, `Mode`, `IsEnabled`, `IsDeleted`, `DataChange_CreatedBy`, `DataChange_CreatedTime`, `DataChange_LastModifiedBy`, `DataChange_LastTime`) VALUES - (1, 'someAppId', 'someSecret', 0, 0, 'apollo', '2019-12-19 10:28:40', 'apollo', '2019-12-19 10:28:40'), - (2, '100004458', 'c715cbc80fc44171b43732c3119c9456', 0, 0, 'apollo', '2019-12-19 10:39:54', 'apollo', '2019-12-19 14:46:35'), - (3, '100004458', '25a0e68d2a3941edb1ed3ab6dd0646cd', 0, 1, 'apollo', '2019-12-19 13:44:13', 'apollo', '2019-12-19 13:44:19'), - (4, '100004458', '4003c4d7783443dc9870932bebf3b7fe', 0, 0, 'apollo', '2019-12-19 13:43:52', 'apollo', '2019-12-19 13:44:21'); + (1, 'someAppId', 'someSecret', 0, 0, 0, 'apollo', '2019-12-19 10:28:40', 'apollo', '2019-12-19 10:28:40'), + (2, '100004458', 'c715cbc80fc44171b43732c3119c9456', 0, 0, 0, 'apollo', '2019-12-19 10:39:54', 'apollo', '2019-12-19 14:46:35'), + (3, '100004458', '25a0e68d2a3941edb1ed3ab6dd0646cd', 0, 0, 1, 'apollo', '2019-12-19 13:44:13', 'apollo', '2019-12-19 13:44:19'), + (4, '100004458', '4003c4d7783443dc9870932bebf3b7fe', 0, 0, 0, 'apollo', '2019-12-19 13:43:52', 'apollo', '2019-12-19 13:44:21'); diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/constants/AccessKeyMode.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/constants/AccessKeyMode.java new file mode 100644 index 00000000000..24844c407fa --- /dev/null +++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/constants/AccessKeyMode.java @@ -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. + * + */ +package com.ctrip.framework.apollo.common.constants; + +public interface AccessKeyMode { + + int FILTER = 0; + + int OBSERVER = 1; + +} diff --git a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/dto/AccessKeyDTO.java b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/dto/AccessKeyDTO.java index 4ba5a32d5ab..ecd7eb37511 100644 --- a/apollo-common/src/main/java/com/ctrip/framework/apollo/common/dto/AccessKeyDTO.java +++ b/apollo-common/src/main/java/com/ctrip/framework/apollo/common/dto/AccessKeyDTO.java @@ -24,6 +24,8 @@ public class AccessKeyDTO extends BaseDTO { private String appId; + private Integer mode; + private Boolean enabled; public Long getId() { @@ -50,6 +52,14 @@ public void setAppId(String appId) { this.appId = appId; } + public Integer getMode() { + return mode; + } + + public void setMode(Integer mode) { + this.mode = mode; + } + public Boolean getEnabled() { return enabled; } diff --git a/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/filter/ClientAuthenticationFilter.java b/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/filter/ClientAuthenticationFilter.java index 25677b5626a..43329e135c4 100644 --- a/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/filter/ClientAuthenticationFilter.java +++ b/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/filter/ClientAuthenticationFilter.java @@ -17,9 +17,11 @@ package com.ctrip.framework.apollo.configservice.filter; import com.ctrip.framework.apollo.biz.config.BizConfig; +import com.ctrip.framework.apollo.common.utils.WebUtils; import com.ctrip.framework.apollo.configservice.util.AccessKeyUtil; import com.ctrip.framework.apollo.core.signature.Signature; import com.ctrip.framework.apollo.core.utils.StringUtils; +import com.ctrip.framework.apollo.tracer.Tracer; import com.google.common.net.HttpHeaders; import java.io.IOException; import java.util.List; @@ -70,29 +72,60 @@ public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain List availableSecrets = accessKeyUtil.findAvailableSecret(appId); if (!CollectionUtils.isEmpty(availableSecrets)) { - String timestamp = request.getHeader(Signature.HTTP_HEADER_TIMESTAMP); - String authorization = request.getHeader(HttpHeaders.AUTHORIZATION); - - // check timestamp, valid within 1 minute - if (!checkTimestamp(timestamp)) { - logger.warn("Invalid timestamp. appId={},timestamp={}", appId, timestamp); - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "RequestTimeTooSkewed"); + if (!doCheck(request, response, appId, availableSecrets, false)) { return; } - - // check signature - String uri = request.getRequestURI(); - String query = request.getQueryString(); - if (!checkAuthorization(authorization, availableSecrets, timestamp, uri, query)) { - logger.warn("Invalid authorization. appId={},authorization={}", appId, authorization); - response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); - return; + } else { + // pre-check for observable secrets + List observableSecrets = accessKeyUtil.findObservableSecrets(appId); + if (!CollectionUtils.isEmpty(observableSecrets)) { + doCheck(request, response, appId, observableSecrets, true); } } chain.doFilter(request, response); } + /** + * Performs authentication checks(timestamp and signature) for the request. + * + * @param preCheck Boolean flag indicating whether this is a pre-check + * @return true if authentication checks is successful, false otherwise + */ + private boolean doCheck(HttpServletRequest req, HttpServletResponse resp, + String appId, List secrets, boolean preCheck) throws IOException { + + String timestamp = req.getHeader(Signature.HTTP_HEADER_TIMESTAMP); + String authorization = req.getHeader(HttpHeaders.AUTHORIZATION); + String ip = WebUtils.tryToGetClientIp(req); + + // check timestamp, valid within 1 minute + if (!checkTimestamp(timestamp)) { + if (preCheck) { + preCheckInvalidLogging(String.format("Invalid timestamp in pre-check. " + + "appId=%s,clientIp=%s,timestamp=%s", appId, ip, timestamp)); + } else { + logger.warn("Invalid timestamp. appId={},clientIp={},timestamp={}", appId, ip, timestamp); + resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "RequestTimeTooSkewed"); + return false; + } + } + + // check signature + if (!checkAuthorization(authorization, secrets, timestamp, req.getRequestURI(), req.getQueryString())) { + if (preCheck) { + preCheckInvalidLogging(String.format("Invalid authorization in pre-check. " + + "appId=%s,clientIp=%s,authorization=%s", appId, ip, authorization)); + } else { + logger.warn("Invalid authorization. appId={},clientIp={},authorization={}", appId, ip, authorization); + resp.sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); + return false; + } + } + + return true; + } + @Override public void destroy() { //nothing @@ -130,4 +163,9 @@ private boolean checkAuthorization(String authorization, List availableS } return false; } + + protected void preCheckInvalidLogging(String message) { + logger.warn(message); + Tracer.logEvent("Apollo.AccessKey.PreCheck", message); + } } diff --git a/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/AccessKeyServiceWithCache.java b/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/AccessKeyServiceWithCache.java index 4dc3519f11d..46c449f445f 100644 --- a/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/AccessKeyServiceWithCache.java +++ b/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/service/AccessKeyServiceWithCache.java @@ -19,6 +19,7 @@ import com.ctrip.framework.apollo.biz.config.BizConfig; import com.ctrip.framework.apollo.biz.entity.AccessKey; import com.ctrip.framework.apollo.biz.repository.AccessKeyRepository; +import com.ctrip.framework.apollo.common.constants.AccessKeyMode; import com.ctrip.framework.apollo.core.utils.ApolloThreadFactory; import com.ctrip.framework.apollo.tracer.Tracer; import com.ctrip.framework.apollo.tracer.spi.Transaction; @@ -37,11 +38,11 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -86,13 +87,21 @@ private void initialize() { } public List getAvailableSecrets(String appId) { + return getSecrets(appId, key -> key.isEnabled() && key.getMode() == AccessKeyMode.FILTER); + } + + public List getObservableSecrets(String appId) { + return getSecrets(appId, key -> key.isEnabled() && key.getMode() == AccessKeyMode.OBSERVER); + } + + public List getSecrets(String appId, Predicate filter) { List accessKeys = accessKeyCache.get(appId); if (CollectionUtils.isEmpty(accessKeys)) { return Collections.emptyList(); } return accessKeys.stream() - .filter(AccessKey::isEnabled) + .filter(filter) .map(AccessKey::getSecret) .collect(Collectors.toList()); } diff --git a/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/util/AccessKeyUtil.java b/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/util/AccessKeyUtil.java index 910d203d964..472e1c37c43 100644 --- a/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/util/AccessKeyUtil.java +++ b/apollo-configservice/src/main/java/com/ctrip/framework/apollo/configservice/util/AccessKeyUtil.java @@ -46,6 +46,10 @@ public List findAvailableSecret(String appId) { return accessKeyServiceWithCache.getAvailableSecrets(appId); } + public List findObservableSecrets(String appId) { + return accessKeyServiceWithCache.getObservableSecrets(appId); + } + public String extractAppIdFromRequest(HttpServletRequest request) { String appId = null; String servletPath = request.getServletPath(); diff --git a/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/filter/ClientAuthenticationFilterTest.java b/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/filter/ClientAuthenticationFilterTest.java index b5676f9f149..5d060aa4263 100644 --- a/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/filter/ClientAuthenticationFilterTest.java +++ b/apollo-configservice/src/test/java/com/ctrip/framework/apollo/configservice/filter/ClientAuthenticationFilterTest.java @@ -17,7 +17,9 @@ package com.ctrip.framework.apollo.configservice.filter; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -26,6 +28,7 @@ import com.ctrip.framework.apollo.configservice.util.AccessKeyUtil; import com.ctrip.framework.apollo.core.signature.Signature; import com.google.common.collect.Lists; +import java.util.Collections; import java.util.List; import javax.servlet.FilterChain; import javax.servlet.http.HttpServletRequest; @@ -58,7 +61,7 @@ public class ClientAuthenticationFilterTest { @Before public void setUp() { - clientAuthenticationFilter = new ClientAuthenticationFilter(bizConfig, accessKeyUtil); + clientAuthenticationFilter = spy(new ClientAuthenticationFilter(bizConfig, accessKeyUtil)); } @Test @@ -141,6 +144,54 @@ public void testAuthorizedSuccessfully() throws Exception { clientAuthenticationFilter.doFilter(request, response, filterChain); + verifySuccessAndDoFilter(); + } + + @Test + public void testPreCheckInvalid() throws Exception { + String appId = "someAppId"; + String availableSignature = "someSignature"; + List secrets = Lists.newArrayList("someSecret"); + String oneMinAgoTimestamp = Long.toString(System.currentTimeMillis() - 61 * 1000); + String errorAuthorization = "Apollo someAppId:wrongSignature"; + + when(accessKeyUtil.extractAppIdFromRequest(any())).thenReturn(appId); + when(accessKeyUtil.findAvailableSecret(appId)).thenReturn(Collections.emptyList()); + when(accessKeyUtil.findObservableSecrets(appId)).thenReturn(secrets); + when(accessKeyUtil.buildSignature(any(), any(), any(), any())).thenReturn(availableSignature); + when(request.getHeader(Signature.HTTP_HEADER_TIMESTAMP)).thenReturn(oneMinAgoTimestamp); + when(request.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(errorAuthorization); + when(bizConfig.accessKeyAuthTimeDiffTolerance()).thenReturn(60); + + clientAuthenticationFilter.doFilter(request, response, filterChain); + + verifySuccessAndDoFilter(); + verify(clientAuthenticationFilter, times(2)).preCheckInvalidLogging(anyString()); + } + + @Test + public void testPreCheckSuccessfully() throws Exception { + String appId = "someAppId"; + String availableSignature = "someSignature"; + List secrets = Lists.newArrayList("someSecret"); + String oneMinAgoTimestamp = Long.toString(System.currentTimeMillis()); + String correctAuthorization = "Apollo someAppId:someSignature"; + + when(accessKeyUtil.extractAppIdFromRequest(any())).thenReturn(appId); + when(accessKeyUtil.findAvailableSecret(appId)).thenReturn(Collections.emptyList()); + when(accessKeyUtil.findObservableSecrets(appId)).thenReturn(secrets); + when(accessKeyUtil.buildSignature(any(), any(), any(), any())).thenReturn(availableSignature); + when(request.getHeader(Signature.HTTP_HEADER_TIMESTAMP)).thenReturn(oneMinAgoTimestamp); + when(request.getHeader(HttpHeaders.AUTHORIZATION)).thenReturn(correctAuthorization); + when(bizConfig.accessKeyAuthTimeDiffTolerance()).thenReturn(60); + + clientAuthenticationFilter.doFilter(request, response, filterChain); + + verifySuccessAndDoFilter(); + verify(clientAuthenticationFilter, never()).preCheckInvalidLogging(anyString()); + } + + private void verifySuccessAndDoFilter() throws Exception { verify(response, never()).sendError(HttpServletResponse.SC_BAD_REQUEST, "InvalidAppId"); verify(response, never()).sendError(HttpServletResponse.SC_UNAUTHORIZED, "RequestTimeTooSkewed"); verify(response, never()).sendError(HttpServletResponse.SC_UNAUTHORIZED, "Unauthorized"); diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java index b8035aae7a2..b05c2cc77df 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/api/AdminServiceAPI.java @@ -317,9 +317,9 @@ public void delete(Env env, String appId, long id, String operator) { } @ApolloAuditLog(type = OpType.RPC, name = "AccessKey.enableInRemote") - public void enable(Env env, String appId, long id, String operator) { - restTemplate.put(env, "apps/{appId}/accesskeys/{id}/enable?operator={operator}", - null, appId, id, operator); + public void enable(Env env, String appId, long id, int mode, String operator) { + restTemplate.put(env, "apps/{appId}/accesskeys/{id}/enable?mode={mode}&operator={operator}", + null, appId, id, mode, operator); } @ApolloAuditLog(type = OpType.RPC, name = "AccessKey.disableInRemote") diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/AccessKeyController.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/AccessKeyController.java index fb8518d7dec..b5ef6f123f5 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/AccessKeyController.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/controller/AccessKeyController.java @@ -16,6 +16,8 @@ */ package com.ctrip.framework.apollo.portal.controller; +import static com.ctrip.framework.apollo.common.constants.AccessKeyMode.FILTER; + import com.ctrip.framework.apollo.audit.annotation.ApolloAuditLog; import com.ctrip.framework.apollo.audit.annotation.OpType; import com.ctrip.framework.apollo.common.dto.AccessKeyDTO; @@ -77,9 +79,10 @@ public void delete(@PathVariable String appId, @ApolloAuditLog(type = OpType.UPDATE, name = "AccessKey.enable") public void enable(@PathVariable String appId, @PathVariable String env, - @PathVariable long id) { + @PathVariable long id, + @RequestParam(required = false, defaultValue = "" + FILTER) int mode) { String operator = userInfoHolder.getUser().getUserId(); - accessKeyService.enable(Env.valueOf(env), appId, id, operator); + accessKeyService.enable(Env.valueOf(env), appId, id, mode, operator); } @PreAuthorize(value = "@permissionValidator.isAppAdmin(#appId)") diff --git a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AccessKeyService.java b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AccessKeyService.java index 26ba0ff92c0..9f7890ccf8e 100644 --- a/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AccessKeyService.java +++ b/apollo-portal/src/main/java/com/ctrip/framework/apollo/portal/service/AccessKeyService.java @@ -49,8 +49,8 @@ public void deleteAccessKey(Env env, String appId, long id, String operator) { accessKeyAPI.delete(env, appId, id, operator); } - public void enable(Env env, String appId, long id, String operator) { - accessKeyAPI.enable(env, appId, id, operator); + public void enable(Env env, String appId, long id, int mode, String operator) { + accessKeyAPI.enable(env, appId, id, mode, operator); } public void disable(Env env, String appId, long id, String operator) { diff --git a/apollo-portal/src/main/resources/static/app/access_key.html b/apollo-portal/src/main/resources/static/app/access_key.html index debca68fd0f..4cb1c9c4993 100644 --- a/apollo-portal/src/main/resources/static/app/access_key.html +++ b/apollo-portal/src/main/resources/static/app/access_key.html @@ -60,10 +60,11 @@

    {{'Common.Environment' | translate }}: {{env}} {{accessKey.secret}} - {{accessKey.enabled ? ('AccessKey.Operator.Enabled' | translate) : ('AccessKey.Operator.Disabled' | translate) }} + {{'AccessKey.Operator.Enabled' | translate}} + {{'AccessKey.Operator.Observed' | translate}} + {{'AccessKey.Operator.Disabled' | translate}} {{accessKey.dataChangeLastModifiedBy}} {{accessKey.dataChangeLastModifiedTime | date: 'yyyy-MM-dd HH:mm:ss'}} - {{'AccessKey.Operator.Enable' | translate}} - {{'AccessKey.Operator.Disable' | translate}} - {{'AccessKey.Operator.Remove' | translate }} + {{'AccessKey.Operator.Enable' | translate}} + {{'AccessKey.Operator.Observe' | translate}} + {{'AccessKey.Operator.Disable' | translate}} + {{'AccessKey.Operator.Remove' | translate }} diff --git a/apollo-portal/src/main/resources/static/i18n/en.json b/apollo-portal/src/main/resources/static/i18n/en.json index 81c3bded0a6..69ae8e5e4cf 100644 --- a/apollo-portal/src/main/resources/static/i18n/en.json +++ b/apollo-portal/src/main/resources/static/i18n/en.json @@ -517,10 +517,11 @@ "ServiceConfig.KeyExistsAndSaveTip": "Key: '{{key}}' already exists. Click Save will override the configuration item.", "AccessKey.Tips.1": "Add up to 5 access keys per environment.", "AccessKey.Tips.2": "Once the environment has any enabled access key, the client will be required to configure access key, or the configurations cannot be obtained.", - "AccessKey.Tips.3": "Configure the access key to prevent unauthorized clients from obtaining the application configuration. The configuration method is as follows(only apollo-client version 1.6.0+):", - "AccessKey.Tips.3.1": "Via jvm parameter: apollo-client version >=1.9.0 is recommended to use -Dapollo.access-key.secret; other versions use -Dapollo.accesskey.secret", - "AccessKey.Tips.3.2": "Through the os environment variable: apollo-client version >=1.9.0 is recommended to use APOLLO_ACCESS_KEY_SECRET; other versions use APOLLO_ACCESSKEY_SECRET", - "AccessKey.Tips.3.3": "Via META-INF/app.properties or application.properties: apollo-client version >=1.9.0 is recommended to use apollo.access-key.secret; other versions use apollo.accesskey.secret(note that the multi-environment secret is different)", + "AccessKey.Tips.3": "Observed access keys are used for pre-check and logging only. Note: Once the environment has any enabled access key, the observed status will no longer take effect.", + "AccessKey.Tips.4": "Configure the access key to prevent unauthorized clients from obtaining the application configuration. The configuration method is as follows(only apollo-client version 1.6.0+):", + "AccessKey.Tips.4.1": "Via jvm parameter: apollo-client version >=1.9.0 is recommended to use -Dapollo.access-key.secret; other versions use -Dapollo.accesskey.secret", + "AccessKey.Tips.4.2": "Through the os environment variable: apollo-client version >=1.9.0 is recommended to use APOLLO_ACCESS_KEY_SECRET; other versions use APOLLO_ACCESSKEY_SECRET", + "AccessKey.Tips.4.3": "Via META-INF/app.properties or application.properties: apollo-client version >=1.9.0 is recommended to use apollo.access-key.secret; other versions use apollo.accesskey.secret(note that the multi-environment secret is different)", "AccessKey.NoAccessKeyServiceTips": "There are no access keys in this environment.", "AccessKey.ConfigAccessKeys.Secret": "Access Key Secret", "AccessKey.ConfigAccessKeys.Status": "Status", @@ -529,19 +530,24 @@ "AccessKey.ConfigAccessKeys.Operator": "Operation", "AccessKey.Operator.Disable": "Disable", "AccessKey.Operator.Enable": "Enable", + "AccessKey.Operator.Observe": "Observe", "AccessKey.Operator.Disabled": "Disabled", "AccessKey.Operator.Enabled": "Enabled", + "AccessKey.Operator.Observed": "Observed", "AccessKey.Operator.Remove": "Remove", "AccessKey.Operator.CreateSuccess": "Access key created successfully", "AccessKey.Operator.DisabledSuccess": "Access key disabled successfully", "AccessKey.Operator.EnabledSuccess": "Access key enabled successfully", + "AccessKey.Operator.ObservedSuccess": "Access key observed successfully", "AccessKey.Operator.RemoveSuccess": "Access key removed successfully", "AccessKey.Operator.CreateError": "Access key created failed", "AccessKey.Operator.DisabledError": "Access key disabled failed", "AccessKey.Operator.EnabledError": "Access key enabled failed", + "AccessKey.Operator.ObservedError": "Access key observed failed", "AccessKey.Operator.RemoveError": "Access key removed failed", "AccessKey.Operator.DisabledTips": "Are you sure you want to disable the access key?", "AccessKey.Operator.EnabledTips": "Are you sure you want to enable the access key?", + "AccessKey.Operator.ObservedTips": "Are you sure you want to observe the access key?", "AccessKey.Operator.RemoveTips": "Are you sure you want to remove the access key?", "AccessKey.LoadError": "Error Loading access keys", "SystemInfo.Title": "System Information", 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 2ef309b46dc..8ae676eda59 100644 --- a/apollo-portal/src/main/resources/static/i18n/zh-CN.json +++ b/apollo-portal/src/main/resources/static/i18n/zh-CN.json @@ -516,11 +516,12 @@ "ServiceConfig.KeyNotExistsAndCreateTip": "Key: '{{key}}' 不存在,点击保存后会创建该配置项", "ServiceConfig.KeyExistsAndSaveTip": "Key: '{{key}}' 已存在,点击保存后会覆盖该配置项", "AccessKey.Tips.1": "每个环境最多可添加 5 个访问密钥", - "AccessKey.Tips.2": "一旦该环境有启用的访问密钥,客户端将被要求配置密钥,否则无法获取配置", - "AccessKey.Tips.3": "配置访问密钥防止非法客户端获取该应用配置,配置方式如下:(仅支持apollo-client 1.6.0+)", - "AccessKey.Tips.3.1": "通过JVM参数配置: apollo-client >=1.9.0 推荐使用 -Dapollo.access-key.secret; 其它版本使用 -Dapollo.accesskey.secret", - "AccessKey.Tips.3.2": "通过操作系统环境变量配置: apollo-client >=1.9.0 推荐使用 APOLLO_ACCESS_KEY_SECRET; 其它版本使用 APOLLO_ACCESSKEY_SECRET", - "AccessKey.Tips.3.3": "通过 META-INF/app.properties 或 application.properties配置: apollo-client >=1.9.0 推荐使用 apollo.access-key.secret; 其它版本使用 apollo.accesskey.secret(注意多环境 secret 不一样)", + "AccessKey.Tips.2": "一旦该环境有启用状态的访问密钥,客户端将被要求配置密钥,否则无法获取配置", + "AccessKey.Tips.3": "观察状态密钥用于预校验,只做日志记录不拦截配置获取,注意:一旦该环境有启用状态的访问密钥,观察状态将不再生效", + "AccessKey.Tips.4": "配置访问密钥防止非法客户端获取该应用配置,配置方式如下:(仅支持apollo-client 1.6.0+)", + "AccessKey.Tips.4.1": "通过JVM参数配置: apollo-client >=1.9.0 推荐使用 -Dapollo.access-key.secret; 其它版本使用 -Dapollo.accesskey.secret", + "AccessKey.Tips.4.2": "通过操作系统环境变量配置: apollo-client >=1.9.0 推荐使用 APOLLO_ACCESS_KEY_SECRET; 其它版本使用 APOLLO_ACCESSKEY_SECRET", + "AccessKey.Tips.4.3": "通过 META-INF/app.properties 或 application.properties配置: apollo-client >=1.9.0 推荐使用 apollo.access-key.secret; 其它版本使用 apollo.accesskey.secret(注意多环境 secret 不一样)", "AccessKey.NoAccessKeyServiceTips": "该环境没有配置访问密钥", "AccessKey.ConfigAccessKeys.Secret": "访问密钥", "AccessKey.ConfigAccessKeys.Status": "状态", @@ -529,19 +530,24 @@ "AccessKey.ConfigAccessKeys.Operator": "操作", "AccessKey.Operator.Disable": "禁用", "AccessKey.Operator.Enable": "启用", + "AccessKey.Operator.Observe": "观察", "AccessKey.Operator.Disabled": "已禁用", "AccessKey.Operator.Enabled": "已启用", + "AccessKey.Operator.Observed": "已观察", "AccessKey.Operator.Remove": "删除", "AccessKey.Operator.CreateSuccess": "访问密钥创建成功", "AccessKey.Operator.DisabledSuccess": "访问密钥禁用成功", "AccessKey.Operator.EnabledSuccess": "访问密钥启用成功", + "AccessKey.Operator.ObservedSuccess": "访问密钥观察成功", "AccessKey.Operator.RemoveSuccess": "访问密钥删除成功", "AccessKey.Operator.CreateError": "访问密钥创建失败", "AccessKey.Operator.DisabledError": "访问密钥禁用失败", "AccessKey.Operator.EnabledError": "访问密钥启用失败", + "AccessKey.Operator.ObservedError": "访问密钥观察失败", "AccessKey.Operator.RemoveError": "访问密钥删除失败", "AccessKey.Operator.DisabledTips": "是否确定禁用该访问密钥?", "AccessKey.Operator.EnabledTips": " 是否确定启用该访问密钥?", + "AccessKey.Operator.ObservedTips": " 是否确定观察该访问密钥?", "AccessKey.Operator.RemoveTips": " 是否确定删除该访问密钥?", "AccessKey.LoadError": "加载访问密钥出错", "SystemInfo.Title": "系统信息", diff --git a/apollo-portal/src/main/resources/static/scripts/controller/AccessKeyController.js b/apollo-portal/src/main/resources/static/scripts/controller/AccessKeyController.js index 7044ba54286..b34f939486f 100644 --- a/apollo-portal/src/main/resources/static/scripts/controller/AccessKeyController.js +++ b/apollo-portal/src/main/resources/static/scripts/controller/AccessKeyController.js @@ -136,17 +136,19 @@ function AccessKeyController($scope, $location, $translate, toastr, } } - function enable(id, env) { - var confirmTips = $translate.instant('AccessKey.Operator.EnabledTips', { + function enable(id, env, mode) { + mode = (mode === 1) ? 1 : 0; + var tipsPrefix = mode === 1 ? 'AccessKey.Operator.Observed' : 'AccessKey.Operator.Enabled'; + var confirmTips = $translate.instant(tipsPrefix + 'Tips', { appId: $scope.pageContext.appId }); if (confirm(confirmTips)) { - AccessKeyService.enable_access_key($scope.pageContext.appId, env, id) + AccessKeyService.enable_access_key($scope.pageContext.appId, env, id, mode) .then(function () { - toastr.success($translate.instant('AccessKey.Operator.EnabledSuccess', {env})); + toastr.success($translate.instant(tipsPrefix + 'Success', {env})); loadAccessKeys(env); }, function (result) { - toastr.error(AppUtil.errorMsg(result), $translate.instant('AccessKey.Operator.EnabledError', {env})); + toastr.error(AppUtil.errorMsg(result), $translate.instant(tipsPrefix + 'Error', {env})); }); } } diff --git a/apollo-portal/src/main/resources/static/scripts/services/AccessKeyService.js b/apollo-portal/src/main/resources/static/scripts/services/AccessKeyService.js index 35a42c0fe0a..aca9c393735 100644 --- a/apollo-portal/src/main/resources/static/scripts/services/AccessKeyService.js +++ b/apollo-portal/src/main/resources/static/scripts/services/AccessKeyService.js @@ -31,7 +31,7 @@ appService.service('AccessKeyService', ['$resource', '$q', 'AppUtil', function ( }, enable_access_key: { method: 'PUT', - url: AppUtil.prefixPath() + '/apps/:appId/envs/:env/accesskeys/:id/enable' + url: AppUtil.prefixPath() + '/apps/:appId/envs/:env/accesskeys/:id/enable?mode=:mode' }, disable_access_key: { method: 'PUT', @@ -79,12 +79,13 @@ appService.service('AccessKeyService', ['$resource', '$q', 'AppUtil', function ( }); return d.promise; }, - enable_access_key: function (appId, env, id) { + enable_access_key: function (appId, env, id, mode) { var d = $q.defer(); access_key_resource.enable_access_key({ appId: appId, env: env, - id: id + id: id, + mode: mode }, {}, function (result) { d.resolve(result); diff --git a/changes/changes-2.4.0.md b/changes/changes-2.4.0.md index 615d7c1d7a4..3ebe2e98381 100644 --- a/changes/changes-2.4.0.md +++ b/changes/changes-2.4.0.md @@ -8,6 +8,7 @@ Apollo 2.4.0 * [Update the server config link in system info page](https://github.com/apolloconfig/apollo/pull/5204) * [Feature support portal restTemplate Client connection pool config](https://github.com/apolloconfig/apollo/pull/5200) * [Feature added the ability for administrators to globally search for Value](https://github.com/apolloconfig/apollo/pull/5182) +* [Feature support the observe status access-key for pre-check and logging only](https://github.com/apolloconfig/apollo/pull/5236) ------------------ All issues and pull requests are [here](https://github.com/apolloconfig/apollo/milestone/15?closed=1) diff --git a/scripts/sql/profiles/h2-default/apolloconfigdb.sql b/scripts/sql/profiles/h2-default/apolloconfigdb.sql index 8a24f1c82c9..9fb40f0c21f 100644 --- a/scripts/sql/profiles/h2-default/apolloconfigdb.sql +++ b/scripts/sql/profiles/h2-default/apolloconfigdb.sql @@ -393,6 +393,7 @@ CREATE TABLE `AccessKey` ( `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键', `AppId` varchar(64) NOT NULL DEFAULT 'default' COMMENT 'AppID', `Secret` varchar(128) NOT NULL DEFAULT '' COMMENT 'Secret', + `Mode` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '密钥模式,0: filter,1: observer', `IsEnabled` boolean NOT NULL DEFAULT FALSE COMMENT '1: enabled, 0: disabled', `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/apolloconfigdb-v230-v240.sql b/scripts/sql/profiles/h2-default/delta/v230-v240/apolloconfigdb-v230-v240.sql new file mode 100644 index 00000000000..459c12ff9b4 --- /dev/null +++ b/scripts/sql/profiles/h2-default/delta/v230-v240/apolloconfigdb-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 config 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 `AccessKey` ADD COLUMN `Mode` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '密钥模式,0: filter,1: observer' AFTER `Secret`; + +-- +-- =============================================================================== +-- == == +-- == 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/apolloconfigdb.sql b/scripts/sql/profiles/mysql-database-not-specified/apolloconfigdb.sql index 4d2e0c19118..dd9b4dcae8e 100644 --- a/scripts/sql/profiles/mysql-database-not-specified/apolloconfigdb.sql +++ b/scripts/sql/profiles/mysql-database-not-specified/apolloconfigdb.sql @@ -404,6 +404,7 @@ CREATE TABLE `AccessKey` ( `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键', `AppId` varchar(64) NOT NULL DEFAULT 'default' COMMENT 'AppID', `Secret` varchar(128) NOT NULL DEFAULT '' COMMENT 'Secret', + `Mode` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '密钥模式,0: filter,1: observer', `IsEnabled` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: enabled, 0: disabled', `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/apolloconfigdb-v230-v240.sql b/scripts/sql/profiles/mysql-database-not-specified/delta/v230-v240/apolloconfigdb-v230-v240.sql new file mode 100644 index 00000000000..c71b792aa6e --- /dev/null +++ b/scripts/sql/profiles/mysql-database-not-specified/delta/v230-v240/apolloconfigdb-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 config 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 `AccessKey` + ADD COLUMN `Mode` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '密钥模式,0: filter,1: observer' AFTER `Secret`; + +-- +-- =============================================================================== +-- == == +-- == 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/apolloconfigdb.sql b/scripts/sql/profiles/mysql-default/apolloconfigdb.sql index 3736ea599f3..9416af7380a 100644 --- a/scripts/sql/profiles/mysql-default/apolloconfigdb.sql +++ b/scripts/sql/profiles/mysql-default/apolloconfigdb.sql @@ -409,6 +409,7 @@ CREATE TABLE `AccessKey` ( `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键', `AppId` varchar(64) NOT NULL DEFAULT 'default' COMMENT 'AppID', `Secret` varchar(128) NOT NULL DEFAULT '' COMMENT 'Secret', + `Mode` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '密钥模式,0: filter,1: observer', `IsEnabled` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: enabled, 0: disabled', `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/apolloconfigdb-v230-v240.sql b/scripts/sql/profiles/mysql-default/delta/v230-v240/apolloconfigdb-v230-v240.sql new file mode 100644 index 00000000000..8bb5fdc1a0e --- /dev/null +++ b/scripts/sql/profiles/mysql-default/delta/v230-v240/apolloconfigdb-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 config 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 ApolloConfigDB; + +ALTER TABLE `AccessKey` + ADD COLUMN `Mode` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '密钥模式,0: filter,1: observer' AFTER `Secret`; + +-- +-- =============================================================================== +-- == == +-- == 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/apolloconfigdb.sql b/scripts/sql/src/apolloconfigdb.sql index 9f85a8791ce..216dfdd9a33 100644 --- a/scripts/sql/src/apolloconfigdb.sql +++ b/scripts/sql/src/apolloconfigdb.sql @@ -397,6 +397,7 @@ CREATE TABLE `AccessKey` ( `Id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增主键', `AppId` varchar(64) NOT NULL DEFAULT 'default' COMMENT 'AppID', `Secret` varchar(128) NOT NULL DEFAULT '' COMMENT 'Secret', + `Mode` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '密钥模式,0: filter,1: observer', `IsEnabled` bit(1) NOT NULL DEFAULT b'0' COMMENT '1: enabled, 0: disabled', `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/apolloconfigdb-v230-v240.sql b/scripts/sql/src/delta/v230-v240/apolloconfigdb-v230-v240.sql new file mode 100644 index 00000000000..2e3cddfbbea --- /dev/null +++ b/scripts/sql/src/delta/v230-v240/apolloconfigdb-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 config db from v2.3.0 to v2.4.0 + +-- ${gists.autoGeneratedDeclaration} +-- ${gists.h2Function} +-- ${gists.useDatabase} + +ALTER TABLE `AccessKey` + ADD COLUMN `Mode` tinyint unsigned NOT NULL DEFAULT '0' COMMENT '密钥模式,0: filter,1: observer' AFTER `Secret`; + +-- ${gists.autoGeneratedDeclaration} From e2e8c7f4bca7dd1db026431ada4e9b0279f02f6c Mon Sep 17 00:00:00 2001 From: liu Date: Mon, 26 Aug 2024 13:14:50 +0800 Subject: [PATCH 8/8] feat(): add apollo-client-observability docs --- docs/en/client/java-sdk-user-guide.md | 224 ++++++++++++++++++ ...client-how-to-use-custom-monitor-system.md | 141 +++++++++++ docs/zh/client/java-sdk-user-guide.md | 208 +++++++++++++--- ...client-how-to-use-custom-monitor-system.md | 4 +- 4 files changed, 542 insertions(+), 35 deletions(-) create mode 100644 docs/en/extension/java-client-how-to-use-custom-monitor-system.md diff --git a/docs/en/client/java-sdk-user-guide.md b/docs/en/client/java-sdk-user-guide.md index 2f2d887ef77..344e1d02a02 100644 --- a/docs/en/client/java-sdk-user-guide.md +++ b/docs/en/client/java-sdk-user-guide.md @@ -414,6 +414,26 @@ The configuration methods, in descending order of priority, are 3. Via the `app.properties` configuration file * You can specify `apollo.override-system-properties=true` in `classpath:/META-INF/app.properties` +### 1.2.4.9 Monitor-related Configuration + +> Applicable for version 2.4.0 and above +Starting from version 2.4.0, the observability of the client has been enhanced. Users can obtain the client status information directly through ConfigService by accessing ConfigMonitor and report the status as metrics to monitoring systems. Below are some related configurations. + +`apollo.client.monitor.enabled`: Enables the Monitor mechanism, i.e., whether ConfigMonitor is activated. The default is false. + +`apollo.client.monitor.jmx.enabled`: Exposes Monitor data in JMX format. If enabled, tools like J-console and Jprofiler can be used to view the relevant information. The default is false. + +![Monitor Configuration](https://cdn.jsdelivr.net/gh/Rawven/image@main/2024-08-24-14-59-01-image.png) + +`apollo.client.monitor.exception-queue-size`: Sets the maximum number of exceptions that the Monitor can store. The default value is 25. + +`apollo.client.monitor.external.type`: **Non-standard configuration item**, used to activate the corresponding monitoring system's Exporter when exporting metric data. For example, if the apollo-plugin-client-prometheus is introduced, "prometheus" can be specified to enable it. The values available for configuration depend on the MetricsExporter SPI introduced (official or custom implementations). This design allows for easy extensibility. If multiple, incorrect, or no values are set, no Exporter will be enabled. + +For specific usage, see the section on Extension Development - Java Client Access to Different Monitoring Systems. + +`apollo.client.monitor.external.export-period`: The Exporter exports status information (e.g., thread pools) from the Monitor and converts it into metric data through scheduled tasks. The export-period controls the frequency of these scheduled tasks. The default is 10 seconds. + + # II. Maven Dependency Apollo's client jar package has been uploaded to the central repository, the application only needs to be introduced in the following way when it is actually used. @@ -450,6 +470,8 @@ Apollo supports API approach and Spring integration approach, how to choose whic * For more interesting practical usage scenarios and sample code, please refer to [apollo-use-cases](https://github.com/ctripcorp/apollo-use-cases) + + ## 3.1 API Usage The API approach is the easiest and most efficient way to use Apollo configuration without relying on the Spring Framework to use it. @@ -519,6 +541,197 @@ String someNamespace = "test"; ConfigFile configFile = ConfigService.getConfigFile("test", ConfigFileFormat.XML); String content = configFile.getContent(); ``` +### 3.1.5 Using the Monitor Feature + +In version 2.4.0, apollo-client significantly enhanced observability by providing the ConfigMonitor API and metrics export options for JMX and Prometheus. + +To enable the monitor feature, you need to configure apollo.client.monitor.enabled to true. + + +```yaml +apollo: + client: + monitor: + enabled: true +``` + +#### 3.1.5.1 Exposing Status Information via JMX + +Enable apollo.client.monitor.jmx.enabled in the configuration. + +```yaml +apollo: + client: + monitor: + enabled: true + jmx: + enabled: true +``` + +After starting the application, you can view it using J-console or J-profiler; here, we use J-profiler as an example. + +![](https://raw.githubusercontent.com/Rawven/image/main/20240828003803.png) + +#### 3.1.5.2 Exporting Metrics via Prometheus + +Configure apollo.client.monitor.external.type to prometheus in the configuration (make sure to include apollo-client-plugin-prometheus and necessary dependencies!). +```yaml +apollo: + client: + monitor: + enabled: true + external: + type: prometheus +``` + +You can retrieve ExporterData via ConfigMonitor (the format depends on your configured monitoring system) and then expose the endpoint to Prometheus. +示例代码 + +```java +@RestController +@ResponseBody +public class TestController { + + @GetMapping("/metrics") + public String metrics() { + ConfigMonitor configMonitor = ConfigService.getConfigMonitor(); + return configMonitor.getExporterData(); + } +} +``` + +After starting the application, let Prometheus listen to this interface, and you will see request logs with a similar format. +``` +# TYPE apollo_client_thread_pool_queue_remaining_capacity gauge +# HELP apollo_client_thread_pool_queue_remaining_capacity apollo gauge metrics +apollo_client_thread_pool_queue_remaining_capacity{thread_pool_name="RemoteConfigRepository"} 2.147483647E9 +apollo_client_thread_pool_queue_remaining_capacity{thread_pool_name="AbstractApolloClientMetricsExporter"} 2.147483647E9 +apollo_client_thread_pool_queue_remaining_capacity{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_queue_remaining_capacity{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_thread_pool_core_pool_size gauge +# HELP apollo_client_thread_pool_core_pool_size apollo gauge metrics +apollo_client_thread_pool_core_pool_size{thread_pool_name="RemoteConfigRepository"} 1.0 +apollo_client_thread_pool_core_pool_size{thread_pool_name="AbstractApolloClientMetricsExporter"} 1.0 +apollo_client_thread_pool_core_pool_size{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_core_pool_size{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_thread_pool_largest_pool_size gauge +# HELP apollo_client_thread_pool_largest_pool_size apollo gauge metrics +apollo_client_thread_pool_largest_pool_size{thread_pool_name="RemoteConfigRepository"} 1.0 +apollo_client_thread_pool_largest_pool_size{thread_pool_name="AbstractApolloClientMetricsExporter"} 1.0 +apollo_client_thread_pool_largest_pool_size{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_largest_pool_size{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_thread_pool_queue_size gauge +# HELP apollo_client_thread_pool_queue_size apollo gauge metrics +apollo_client_thread_pool_queue_size{thread_pool_name="RemoteConfigRepository"} 2.0 +apollo_client_thread_pool_queue_size{thread_pool_name="AbstractApolloClientMetricsExporter"} 0.0 +apollo_client_thread_pool_queue_size{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_queue_size{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_thread_pool_pool_size gauge +# HELP apollo_client_thread_pool_pool_size apollo gauge metrics +apollo_client_thread_pool_pool_size{thread_pool_name="RemoteConfigRepository"} 1.0 +apollo_client_thread_pool_pool_size{thread_pool_name="AbstractApolloClientMetricsExporter"} 1.0 +apollo_client_thread_pool_pool_size{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_pool_size{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_namespace_item_num gauge +# HELP apollo_client_namespace_item_num apollo gauge metrics +apollo_client_namespace_item_num{namespace="application"} 8.0 +apollo_client_namespace_item_num{namespace="application1"} 2.0 +# TYPE apollo_client_thread_pool_completed_task_count gauge +# HELP apollo_client_thread_pool_completed_task_count apollo gauge metrics +apollo_client_thread_pool_completed_task_count{thread_pool_name="RemoteConfigRepository"} 2.0 +apollo_client_thread_pool_completed_task_count{thread_pool_name="AbstractApolloClientMetricsExporter"} 0.0 +apollo_client_thread_pool_completed_task_count{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_completed_task_count{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_namespace_not_found gauge +# HELP apollo_client_namespace_not_found apollo gauge metrics +apollo_client_namespace_not_found 0.0 +# TYPE apollo_client_thread_pool_total_task_count gauge +# HELP apollo_client_thread_pool_total_task_count apollo gauge metrics +apollo_client_thread_pool_total_task_count{thread_pool_name="RemoteConfigRepository"} 4.0 +apollo_client_thread_pool_total_task_count{thread_pool_name="AbstractApolloClientMetricsExporter"} 1.0 +apollo_client_thread_pool_total_task_count{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_total_task_count{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_namespace_usage counter +# HELP apollo_client_namespace_usage apollo counter metrics +apollo_client_namespace_usage_total{namespace="application"} 1.0 +apollo_client_namespace_usage_created{namespace="application"} 1.725899226271E9 +apollo_client_namespace_usage_total{namespace="application1"} 1.0 +apollo_client_namespace_usage_created{namespace="application1"} 1.72589922627E9 +# TYPE apollo_client_thread_pool_maximum_pool_size gauge +# HELP apollo_client_thread_pool_maximum_pool_size apollo gauge metrics +apollo_client_thread_pool_maximum_pool_size{thread_pool_name="RemoteConfigRepository"} 2.147483647E9 +apollo_client_thread_pool_maximum_pool_size{thread_pool_name="AbstractApolloClientMetricsExporter"} 2.147483647E9 +apollo_client_thread_pool_maximum_pool_size{thread_pool_name="AbstractConfigFile"} 2.147483647E9 +apollo_client_thread_pool_maximum_pool_size{thread_pool_name="AbstractConfig"} 2.147483647E9 +# TYPE apollo_client_namespace_first_load_time_spend_in_ms gauge +# HELP apollo_client_namespace_first_load_time_spend_in_ms apollo gauge metrics +apollo_client_namespace_first_load_time_spend_in_ms{namespace="application"} 99.0 +apollo_client_namespace_first_load_time_spend_in_ms{namespace="application1"} 40.0 +# TYPE apollo_client_thread_pool_active_task_count gauge +# HELP apollo_client_thread_pool_active_task_count apollo gauge metrics +apollo_client_thread_pool_active_task_count{thread_pool_name="RemoteConfigRepository"} 0.0 +apollo_client_thread_pool_active_task_count{thread_pool_name="AbstractApolloClientMetricsExporter"} 1.0 +apollo_client_thread_pool_active_task_count{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_active_task_count{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_namespace_timeout gauge +# HELP apollo_client_namespace_timeout apollo gauge metrics +apollo_client_namespace_timeout 0.0 +# EOF +``` + +You can also see similar information on the Prometheus console. + +![](https://raw.githubusercontent.com/Rawven/image/main/20240922125033.png) + +#### 3.1.5.3 Manually Calling the ConfigMonitor API to Retrieve Related Data (for example, when users need to manually process data to report to the monitoring system) + +```java + ConfigMonitor configMonitor = ConfigService.getConfigMonitor(); + // Error related monitoring API + ApolloClientExceptionMonitorApi exceptionMonitorApi = configMonitor.getExceptionMonitorApi(); + List apolloConfigExceptionList = exceptionMonitorApi.getApolloConfigExceptionList(); + // Namespace related monitoring API + ApolloClientNamespaceMonitorApi namespaceMonitorApi = configMonitor.getNamespaceMonitorApi(); + List namespace404 = namespaceMonitorApi.getNotFoundNamespaces(); + // Startup parameter related monitoring API + ApolloClientBootstrapArgsMonitorApi runningParamsMonitorApi = configMonitor.getRunningParamsMonitorApi(); + String bootstrapNamespaces = runningParamsMonitorApi.getBootstrapNamespaces(); + // Thread pool related monitoring API + ApolloClientThreadPoolMonitorApi threadPoolMonitorApi = configMonitor.getThreadPoolMonitorApi(); + ApolloThreadPoolInfo remoteConfigRepositoryThreadPoolInfo = threadPoolMonitorApi.getRemoteConfigRepositoryThreadPoolInfo(); +``` + +#### 3.1.5.4 Metrics Data Table + +## Namespace Metrics + +| Metric Name | Tags | +|-----------------------------------------------------|-----------| +| apollo_client_namespace_usage_total | namespace | +| apollo_client_namespace_item_num | namespace | +| apollo_client_namespace_not_found | | +| apollo_client_namespace_timeout | | +| apollo_client_namespace_first_load_time_spend_in_ms | namespace | + +## Thread Pool Metrics + +| Metric Name | Tags | +| -------------------------------------------------- | ---------------- | +| apollo_client_thread_pool_pool_size | thread_pool_name | +| apollo_client_thread_pool_maximum_pool_size | thread_pool_name | +| apollo_client_thread_pool_largest_pool_size | thread_pool_name | +| apollo_client_thread_pool_completed_task_count | thread_pool_name | +| apollo_client_thread_pool_queue_remaining_capacity | thread_pool_name | +| apollo_client_thread_pool_total_task_count | thread_pool_name | +| apollo_client_thread_pool_active_task_count | thread_pool_name | +| apollo_client_thread_pool_core_pool_size | thread_pool_name | +| apollo_client_thread_pool_queue_size | thread_pool_name | + +## Exception Metrics + +| Metric Name | Tags | +| --------------------------------- | ---- | +| apollo_client_exception_num_total | | ## 3.2 Spring integration approach @@ -1282,3 +1495,14 @@ The interface is `com.ctrip.framework.apollo.spi.ConfigServiceLoadBalancerClient The Input is multiple ConfigServices returned by meta server, and the output is a ConfigService selected. The default service provider is `com.ctrip.framework.apollo.spi.RandomConfigServiceLoadBalancerClient`, which chooses one ConfigService from multiple ConfigServices using random strategy . + +## 7.2 MetricsExporter Extension + +> from version 2.4.0 +To meet users' varying system requirements for metrics exporting (e.g., Prometheus, Skywalking) when using apollo-client, we have provided an SPI in the enhanced observability of version 2.4.0. + +The interface is com.ctrip.framework.apollo.monitor.internal.exporter.ApolloClientMetricsExporter. + +We offer an abstract class for a general metrics export framework, com.ctrip.framework.apollo.monitor.internal.exporter.AbstractApolloClientMetricsExporter. You just need to extend this class and implement the relevant methods to customize integration with other monitoring systems. You can refer to the implementation of apollo-plugin-client-prometheus. + +By default, there are no services provided, which means no metrics data will be exported. \ No newline at end of file diff --git a/docs/en/extension/java-client-how-to-use-custom-monitor-system.md b/docs/en/extension/java-client-how-to-use-custom-monitor-system.md new file mode 100644 index 00000000000..5bd8d6fbb0f --- /dev/null +++ b/docs/en/extension/java-client-how-to-use-custom-monitor-system.md @@ -0,0 +1,141 @@ +In version 2.4.0 and above of the Java client, support for metrics collection and export has been added, allowing users to extend and integrate with different monitoring systems. + +## Taking Prometheus Integration as an Example + +Create the `PrometheusApolloClientMetricsExporter` class, which extends `AbstractApolloClientMetricsExporter` (the generic metrics export framework). + +The code after extending is roughly as follows: + +```java + +public class PrometheusApolloClientMetricsExporter extends + AbstractApolloClientMetricsExporter implements ApolloClientMetricsExporter { + + @Override + public void doInit() { + + } + + @Override + public boolean isSupport(String form) { + + } + + + @Override + public void registerOrUpdateCounterSample(String name, Map tags, double incrValue) { + + } + + + @Override + public void registerOrUpdateGaugeSample(String name, Map tags, double value) { + + } + + @Override + public String response() { + + } +} + +``` + +The doInit method is provided for users to extend during initialization and will be called in the init method of AbstractApolloClientMetricsExporter. + +```java + @Override + public void init(List collectors, long collectPeriod) { + log.info("Initializing metrics exporter with {} collectors and collect period of {} seconds.", + collectors.size(), collectPeriod); + doInit(); + this.collectors = collectors; + initScheduleMetricsCollectSync(collectPeriod); + log.info("Metrics collection scheduled with a period of {} seconds.", collectPeriod); + } +``` + +Here, the Prometheus Java client is introduced, and the CollectorRegistry and cache map need to be initialized. + +```java + private CollectorRegistry registry; + private Map map; + + @Override + public void doInit() { + registry = new CollectorRegistry(); + map = new HashMap<>(); + } +``` + +The isSupport method will be called in DefaultApolloClientMetricsExporterFactory via SPI to check which MetricsExporter to enable, allowing accurate activation of the configured exporter when multiple SPI implementations exist. + +For example, if you want to enable Prometheus and specify the value as "prometheus," it should synchronize here: + +```java + @Override + public boolean isSupport(String form) { + return PROMETHEUS.equals(form); + } +``` + +The methods registerOrUpdateCounterSample and registerOrUpdateGaugeSample are used to register Counter and Gauge type metrics, simply registering based on the provided parameters. + +```java + @Override + public void registerOrUpdateCounterSample(String name, Map tags, + double incrValue) { + Counter counter = (Counter) map.get(name); + if (counter == null) { + counter = createCounter(name, tags); + map.put(name, counter); + } + counter.labels(tags.values().toArray(new String[0])).inc(incrValue); + } + + private Counter createCounter(String name, Map tags) { + return Counter.build() + .name(name) + .help("apollo") + .labelNames(tags.keySet().toArray(new String[0])) + .register(registry); + } + + @Override + public void registerOrUpdateGaugeSample(String name, Map tags, double value) { + Gauge gauge = (Gauge) map.get(name); + if (gauge == null) { + gauge = createGauge(name, tags); + map.put(name, gauge); + } + gauge.labels(tags.values().toArray(new String[0])).set(value); + } + + private Gauge createGauge(String name, Map tags) { + return Gauge.build() + .name(name) + .help("apollo") + .labelNames(tags.keySet().toArray(new String[0])) + .register(registry); + } +``` + +Finally, you need to implement the response method, which is used to export data in the format of the integrated monitoring system. It will ultimately be obtained in the getExporterData method of ConfigMonitor, allowing users to expose an endpoint for monitoring systems to pull data. + +```java + @Override + public String response() { + try (StringWriter writer = new StringWriter()) { + TextFormat.writeFormat(TextFormat.CONTENT_TYPE_OPENMETRICS_100, writer, + registry.metricFamilySamples()); + return writer.toString(); + } catch (IOException e) { + logger.error("Write metrics to Prometheus format failed", e); + return ""; + } + } +``` + +At this point, the client's metric data has been integrated with Prometheus. + +Full code:[code](https://github.com/apolloconfig/apollo-java/main/master/apollo-plugin/apollo-plugin-client-prometheus/src/main/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/PrometheusApolloClientMetricsExporter.java) \ No newline at end of file diff --git a/docs/zh/client/java-sdk-user-guide.md b/docs/zh/client/java-sdk-user-guide.md index e20f47952e9..92c549eb064 100644 --- a/docs/zh/client/java-sdk-user-guide.md +++ b/docs/zh/client/java-sdk-user-guide.md @@ -414,7 +414,7 @@ apollo.label=YOUR-APOLLO-LABEL `apollo.client.monitor.external.type`:**非常规配置项**,用于导出指标数据时启用对应监控系统的Exporter,如引入apollo-plugin-client-prometheus则可填写prometheus进行启用,可填配置取决于用户引入的MetricsExporter的SPI使可用官方提供的或自己实现),这种设计是为了用户能更方便的扩展。多填,错填和不填则不启用任何Exporter。 -具体使用:TODO-LINK +具体使用见 扩展开发-java客户端接入不同监控系统 `apollo.client.monitor.external.export-period`:Exporter从Monitor中导出状态信息(如线程池等)并转为指标数据是通过定时任务的方式,export-period可以控制定时任务的频率,默认为10秒 @@ -511,11 +511,151 @@ ConfigFile configFile = ConfigService.getConfigFile("test", ConfigFileFormat.XML String content = configFile.getContent(); ``` -### 3.1.5 使用Monitor-API +### 3.1.5 使用Monitor功能 -apollo-client在2.4.0版本里提供了Monitor-API,供用户可以对client有更强的可观测性 +apollo-client在2.4.0版本里大幅增强了可观测性,提供了ConfigMonitor-API以及JMX,Prometheus的指标导出方式 -#### 3.1.5.1 获取ConfigMonitor +若启用monitor功能,前提需要配置apollo.client.monitor.enabled为true + +```yaml +apollo: + client: + monitor: + enabled: true +``` + +#### 3.1.5.1 以JMX形式暴露状态信息 + +在配置中对apollo.client.monitor.jmx.enabled进行启用 + +```yaml +apollo: + client: + monitor: + enabled: true + jmx: + enabled: true +``` + +启动应用后,开启J-console或J-profiler即可查看,这里用J-profiler做例子 + +![](https://raw.githubusercontent.com/Rawven/image/main/20240828003803.png) + +#### 3.1.5.2 以Prometheus形式导出指标 + +在配置中对apollo.client.monitor.external.type中配置为prometheus(同时请确认是否引入了apollo-client-plugin-prometheus和必要依赖!) + +```yaml +apollo: + client: + monitor: + enabled: true + external: + type: prometheus +``` + +可以通过ConfigMonitor拿到ExporterData(格式取决于你配置的监控系统),然后暴露端点给Prometheus即可 + +示例代码 + +```java +@RestController +@ResponseBody +public class TestController { + + @GetMapping("/metrics") + public String metrics() { + ConfigMonitor configMonitor = ConfigService.getConfigMonitor(); + return configMonitor.getExporterData(); + } +} +``` + +启动应用后让Prometheus监听该接口,打印请求日志即可发现如下类似格式信息 + +``` +# TYPE apollo_client_thread_pool_queue_remaining_capacity gauge +# HELP apollo_client_thread_pool_queue_remaining_capacity apollo gauge metrics +apollo_client_thread_pool_queue_remaining_capacity{thread_pool_name="RemoteConfigRepository"} 2.147483647E9 +apollo_client_thread_pool_queue_remaining_capacity{thread_pool_name="AbstractApolloClientMetricsExporter"} 2.147483647E9 +apollo_client_thread_pool_queue_remaining_capacity{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_queue_remaining_capacity{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_thread_pool_core_pool_size gauge +# HELP apollo_client_thread_pool_core_pool_size apollo gauge metrics +apollo_client_thread_pool_core_pool_size{thread_pool_name="RemoteConfigRepository"} 1.0 +apollo_client_thread_pool_core_pool_size{thread_pool_name="AbstractApolloClientMetricsExporter"} 1.0 +apollo_client_thread_pool_core_pool_size{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_core_pool_size{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_thread_pool_largest_pool_size gauge +# HELP apollo_client_thread_pool_largest_pool_size apollo gauge metrics +apollo_client_thread_pool_largest_pool_size{thread_pool_name="RemoteConfigRepository"} 1.0 +apollo_client_thread_pool_largest_pool_size{thread_pool_name="AbstractApolloClientMetricsExporter"} 1.0 +apollo_client_thread_pool_largest_pool_size{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_largest_pool_size{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_thread_pool_queue_size gauge +# HELP apollo_client_thread_pool_queue_size apollo gauge metrics +apollo_client_thread_pool_queue_size{thread_pool_name="RemoteConfigRepository"} 2.0 +apollo_client_thread_pool_queue_size{thread_pool_name="AbstractApolloClientMetricsExporter"} 0.0 +apollo_client_thread_pool_queue_size{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_queue_size{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_thread_pool_pool_size gauge +# HELP apollo_client_thread_pool_pool_size apollo gauge metrics +apollo_client_thread_pool_pool_size{thread_pool_name="RemoteConfigRepository"} 1.0 +apollo_client_thread_pool_pool_size{thread_pool_name="AbstractApolloClientMetricsExporter"} 1.0 +apollo_client_thread_pool_pool_size{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_pool_size{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_namespace_item_num gauge +# HELP apollo_client_namespace_item_num apollo gauge metrics +apollo_client_namespace_item_num{namespace="application"} 8.0 +apollo_client_namespace_item_num{namespace="application1"} 2.0 +# TYPE apollo_client_thread_pool_completed_task_count gauge +# HELP apollo_client_thread_pool_completed_task_count apollo gauge metrics +apollo_client_thread_pool_completed_task_count{thread_pool_name="RemoteConfigRepository"} 2.0 +apollo_client_thread_pool_completed_task_count{thread_pool_name="AbstractApolloClientMetricsExporter"} 0.0 +apollo_client_thread_pool_completed_task_count{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_completed_task_count{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_namespace_not_found gauge +# HELP apollo_client_namespace_not_found apollo gauge metrics +apollo_client_namespace_not_found 0.0 +# TYPE apollo_client_thread_pool_total_task_count gauge +# HELP apollo_client_thread_pool_total_task_count apollo gauge metrics +apollo_client_thread_pool_total_task_count{thread_pool_name="RemoteConfigRepository"} 4.0 +apollo_client_thread_pool_total_task_count{thread_pool_name="AbstractApolloClientMetricsExporter"} 1.0 +apollo_client_thread_pool_total_task_count{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_total_task_count{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_namespace_usage counter +# HELP apollo_client_namespace_usage apollo counter metrics +apollo_client_namespace_usage_total{namespace="application"} 1.0 +apollo_client_namespace_usage_created{namespace="application"} 1.725899226271E9 +apollo_client_namespace_usage_total{namespace="application1"} 1.0 +apollo_client_namespace_usage_created{namespace="application1"} 1.72589922627E9 +# TYPE apollo_client_thread_pool_maximum_pool_size gauge +# HELP apollo_client_thread_pool_maximum_pool_size apollo gauge metrics +apollo_client_thread_pool_maximum_pool_size{thread_pool_name="RemoteConfigRepository"} 2.147483647E9 +apollo_client_thread_pool_maximum_pool_size{thread_pool_name="AbstractApolloClientMetricsExporter"} 2.147483647E9 +apollo_client_thread_pool_maximum_pool_size{thread_pool_name="AbstractConfigFile"} 2.147483647E9 +apollo_client_thread_pool_maximum_pool_size{thread_pool_name="AbstractConfig"} 2.147483647E9 +# TYPE apollo_client_namespace_first_load_time_spend_in_ms gauge +# HELP apollo_client_namespace_first_load_time_spend_in_ms apollo gauge metrics +apollo_client_namespace_first_load_time_spend_in_ms{namespace="application"} 99.0 +apollo_client_namespace_first_load_time_spend_in_ms{namespace="application1"} 40.0 +# TYPE apollo_client_thread_pool_active_task_count gauge +# HELP apollo_client_thread_pool_active_task_count apollo gauge metrics +apollo_client_thread_pool_active_task_count{thread_pool_name="RemoteConfigRepository"} 0.0 +apollo_client_thread_pool_active_task_count{thread_pool_name="AbstractApolloClientMetricsExporter"} 1.0 +apollo_client_thread_pool_active_task_count{thread_pool_name="AbstractConfigFile"} 0.0 +apollo_client_thread_pool_active_task_count{thread_pool_name="AbstractConfig"} 0.0 +# TYPE apollo_client_namespace_timeout gauge +# HELP apollo_client_namespace_timeout apollo gauge metrics +apollo_client_namespace_timeout 0.0 +# EOF +``` + +同时查看Prometheus控制台也能看到如下信息 + +![](https://raw.githubusercontent.com/Rawven/image/main/20240922125033.png) + +#### 3.1.5.3 用户手动调用ConfigMonitor-API获取相关数据(比如当用户需要手动加工数据上报到监控系统时) ```java ConfigMonitor configMonitor = ConfigService.getConfigMonitor(); @@ -533,35 +673,37 @@ apollo-client在2.4.0版本里提供了Monitor-API,供用户可以对client有 ApolloThreadPoolInfo remoteConfigRepositoryThreadPoolInfo = threadPoolMonitorApi.getRemoteConfigRepositoryThreadPoolInfo(); ``` -#### 3.1.5.2 指标数据表格 - -```java - //用户配置的监控系统形式的指标数据 - String exporterData = configMonitor.getExporterData(); -``` - -| 指标名称 | 类型 | 描述 | 标签 | -|------------------------------------------| --- | --- | --- | -| apollo_client_exception_num | counter | Apollo异常计数 | | -| apollo_client_exception_num_total | counter | 总异常计数 | | -| apollo_client_exception_num_created | counter | 创建时间戳 | | -| apollo_client_queueSize | gauge | Apollo队列大小 | ThreadPoolName | -| apollo_client_config_file_num | gauge | 配置文件数量 | namespace | -| apollo_client_poolSize | gauge | 线程池大小 | ThreadPoolName | -| apollo_client_currentLoad | gauge | 当前负载 | ThreadPoolName | -| apollo_client_namespace_first_load_spend | gauge | 首次加载时间 | namespace | -| apollo_client_namespace_item_num | gauge | 命名空间项数量 | namespace | -| apollo_client_namespace_usage | counter | 命名空间使用计数 | namespace | -| apollo_client_largestPoolSize | gauge | 最大线程池大小 | ThreadPoolName | -| apollo_client_maximumPoolSize | gauge | 最大线程池容量 | ThreadPoolName | -| apollo_client_corePoolSize | gauge | 核心线程池大小 | ThreadPoolName | -| apollo_client_queueRemainingCapacity | gauge | 队列剩余容量 | ThreadPoolName | -| apollo_client_namespace_not_found | gauge | 未找到的命名空间计数 | | -| apollo_client_namespace_timeout | gauge | 命名空间超时计数 | | -| apollo_client_activeTaskCount | gauge | 活动任务计数 | ThreadPoolName | -| apollo_client_queueCapacity | gauge | 队列容量 | ThreadPoolName | -| apollo_client_totalTaskCount | gauge | 总任务计数 | ThreadPoolName | -| apollo_client_completedTaskCount | gauge | 完成任务计数 | ThreadPoolName | +#### 3.1.5.4 指标数据表格 + +## Namespace Metrics + +| 指标名称 | 标签 | +| ---------------------------------------- | --------- | +| apollo_client_namespace_usage_total | namespace | +| apollo_client_namespace_item_num | namespace | +| apollo_client_namespace_not_found | | +| apollo_client_namespace_timeout | | +| apollo_client_namespace_first_load_time_spend_in_ms | namespace | + +## Thread Pool Metrics + +| 指标名称 | 标签 | +| -------------------------------------------------- | ---------------- | +| apollo_client_thread_pool_pool_size | thread_pool_name | +| apollo_client_thread_pool_maximum_pool_size | thread_pool_name | +| apollo_client_thread_pool_largest_pool_size | thread_pool_name | +| apollo_client_thread_pool_completed_task_count | thread_pool_name | +| apollo_client_thread_pool_queue_remaining_capacity | thread_pool_name | +| apollo_client_thread_pool_total_task_count | thread_pool_name | +| apollo_client_thread_pool_active_task_count | thread_pool_name | +| apollo_client_thread_pool_core_pool_size | thread_pool_name | +| apollo_client_thread_pool_queue_size | thread_pool_name | + +## Exception Metrics + +| 指标名称 | 标签 | +| --------------------------------- | ---- | +| apollo_client_exception_num_total | | ## 3.2 Spring整合方式 diff --git a/docs/zh/extension/java-client-how-to-use-custom-monitor-system.md b/docs/zh/extension/java-client-how-to-use-custom-monitor-system.md index 2b8bd47e41b..28f6c2b9947 100644 --- a/docs/zh/extension/java-client-how-to-use-custom-monitor-system.md +++ b/docs/zh/extension/java-client-how-to-use-custom-monitor-system.md @@ -1,4 +1,4 @@ -在2.4.0版本的java客户端中,增加了指标收集,导出的支持,用户可以自行扩展接入不同的监控系统。 +在2.4.0版本及以上的java客户端中,增加了指标收集,导出的支持,用户可以自行扩展接入不同的监控系统。 ## 以接入Prometheus为例 @@ -138,4 +138,4 @@ registerOrUpdateCounterSample,registerOrUpdateGaugeSample即是用来注册Count 至此,已经将Client的指标数据接入Prometheus。 -完整代码:TODO URL \ No newline at end of file +完整代码:[code]https://github.com/apolloconfig/apollo-java/main/master/apollo-plugin/apollo-plugin-client-prometheus/src/main/java/com/ctrip/framework/apollo/monitor/internal/exporter/impl/PrometheusApolloClientMetricsExporter.java \ No newline at end of file