Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

添加了管理员全局搜索Value值的功能 #5182

Merged
merged 20 commits into from
Sep 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)
11 changes: 3 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -201,6 +202,14 @@ public List<ItemDTO> findDeletedItems(@PathVariable("appId") String appId,
return Collections.emptyList();
}

@GetMapping("/items-search/key-and-value")
public PageDTO<ItemInfoDTO> getItemInfoBySearch(@RequestParam(value = "key", required = false) String key,
@RequestParam(value = "value", required = false) String value,
Pageable limit) {
Page<ItemInfoDTO> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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)
Expand All @@ -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";
Expand All @@ -68,12 +72,12 @@ public void testCreate() {
item.setDataChangeLastModifiedBy("apollo");

ResponseEntity<ItemDTO> 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<Commit> 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);
Expand All @@ -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");

Expand All @@ -115,7 +119,7 @@ public void testUpdate() {
});

List<Commit> commitList = commitRepository.findByAppIdAndClusterNameAndNamespaceNameOrderByIdDesc(app.getAppId(), cluster.getName(), namespace.getNamespaceName(),
Pageable.ofSize(10));
Pageable.ofSize(10));
assertThat(commitList).hasSize(2);
}

Expand All @@ -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<Commit> 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<ItemInfoDTO> itemInfoDTOS = itemService.getItemInfoBySearch(itemKey, itemValue, PageRequest.of(0, 200));
HttpHeaders headers = new HttpHeaders();
HttpEntity<Void> entity = new HttpEntity<>(headers);
ResponseEntity<PageDTO<ItemInfoDTO>> response = restTemplate.exchange(
url("/items-search/key-and-value?key={key}&value={value}&page={page}&size={size}"),
HttpMethod.GET,
entity,
new ParameterizedTypeReference<PageDTO<ItemInfoDTO>>() {},
itemKey, itemValue, 0, 200
);
assertThat(itemInfoDTOS.getContent().toString()).isEqualTo(response.getBody().getContent().toString());
}
klboke marked this conversation as resolved.
Show resolved Hide resolved
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -43,6 +45,21 @@ public interface ItemRepository extends PagingAndSortingRepository<Item, Long> {

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<ItemInfoDTO> 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<ItemInfoDTO> 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<ItemInfoDTO> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down Expand Up @@ -146,6 +144,18 @@ public Page<Item> findItemsByNamespace(String appId, String clusterName, String
return itemRepository.findByNamespaceId(namespace.getId(), pageable);
}

public Page<ItemInfoDTO> getItemInfoBySearch(String key, String value, Pageable limit) {
Page<ItemInfoDTO> 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;
}
klboke marked this conversation as resolved.
Show resolved Hide resolved

@Transactional
public Item save(Item entity) {
checkItemKeyLength(entity.getKey());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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<ItemInfoDTO> ExpectedItemInfoDTOSByKeyAndValue = itemService.getItemInfoBySearch(itemKey, itemValue, PageRequest.of(0,200));
Page<ItemInfoDTO> ExpectedItemInfoDTOSByKey = itemService.getItemInfoBySearch(itemKey,"", PageRequest.of(0,200));
Page<ItemInfoDTO> 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());

}

}
Loading
Loading