Skip to content
Closed
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.apache.commons.collections4.CollectionUtils;
import org.apache.shenyu.admin.aspect.annotation.RestApi;
import org.apache.shenyu.admin.model.bean.DocInfo;
import org.apache.shenyu.admin.model.bean.DocItem;
import org.apache.shenyu.admin.model.result.ShenyuAdminResult;
import org.apache.shenyu.admin.model.vo.DocVO;
import org.apache.shenyu.admin.model.vo.MenuDocItemVO;
Expand All @@ -30,6 +31,7 @@
import org.apache.shenyu.admin.service.manager.DocManager;
import org.apache.shenyu.common.constant.AdminConstants;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.Collection;
import java.util.List;
Expand Down Expand Up @@ -83,6 +85,18 @@ public ShenyuAdminResult getAllDoc() {
return ShenyuAdminResult.success(docVO);
}

/**
* Get document content by the API id returned in the document menu.
*
* @param id API id
* @return document detail
*/
@GetMapping("/getDocItem")
public ShenyuAdminResult getDocItem(@RequestParam("id") final String id) {
DocItem docItem = docManager.getDocItem(id);
return ShenyuAdminResult.success(docItem);
}

private Function<DocInfo, MenuProjectVO> getMenuAndDocInfo() {
return docInfo -> {
MenuProjectVO menuProjectVO = new MenuProjectVO();
Expand All @@ -95,6 +109,7 @@ private Function<DocInfo, MenuProjectVO> getMenuAndDocInfo() {
List<MenuDocItemVO> docItems = docModule.getDocItems().stream()
.map(docItem -> {
MenuDocItemVO menuDocItemVO = new MenuDocItemVO();
menuDocItemVO.setId(docItem.getId());
menuDocItemVO.setLabel(docItem.getSummary());
menuDocItemVO.setName(docItem.getName());
return menuDocItemVO;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,13 @@ public interface ApiMapper extends ExistProvider {
*/
ApiDO selectByPrimaryKey(String id);

/**
* Select persisted APIs with document content for the document viewer.
*
* @return APIs including their documents
*/
List<ApiDO> selectAllWithDocument();

/**
* update record selective.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
*/
public class DocItem {

private String id;

private String module;

private String name;
Expand Down Expand Up @@ -67,6 +69,24 @@ public class DocItem {

private List<CustomCode> bizCodeList;

/**
* Get the persisted API id.
*
* @return API id
*/
public String getId() {
return id;
}

/**
* Set the persisted API id.
*
* @param id API id
*/
public void setId(final String id) {
this.id = id;
}

/**
* isUploadRequest.
* Whether it is a file upload request.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import java.util.Collection;
import java.util.function.Consumer;
import org.apache.shenyu.admin.model.bean.DocInfo;
import org.apache.shenyu.admin.model.bean.DocItem;
import org.apache.shenyu.admin.model.bean.UpstreamInstance;

/**
Expand All @@ -43,4 +44,12 @@ public interface DocManager {
* @return Collection
*/
Collection<DocInfo> listAll();

/**
* Get a document by its persisted API id.
*
* @param id API id
* @return document item, or null if it is not available
*/
DocItem getDocItem(String id);
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,21 +20,27 @@
import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.TreeMap;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import jakarta.annotation.Resource;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.shenyu.admin.mapper.ApiMapper;
import org.apache.shenyu.admin.model.bean.DocInfo;
import org.apache.shenyu.admin.model.bean.DocItem;
import org.apache.shenyu.admin.model.bean.DocModule;
import org.apache.shenyu.admin.model.bean.UpstreamInstance;
import org.apache.shenyu.admin.model.entity.ApiDO;
import org.apache.shenyu.admin.service.manager.DocManager;
import org.apache.shenyu.admin.service.manager.DocParser;
import org.apache.shenyu.admin.service.manager.RegisterApiDocService;
Expand Down Expand Up @@ -65,16 +71,14 @@ public class DocManagerImpl implements DocManager {
*/
private static final String HTTP = "http://";

/**
* key:title, value:docInfo.
*/
private static final Map<String, DocInfo> DOC_DEFINITION_MAP = new HashMap<>();

private static final DocParser SWAGGER_DOC_PARSER = new SwaggerDocParser();

@Resource
private RegisterApiDocService registerApiDocService;

@Resource
private ApiMapper apiMapper;

/**
* add docInfo.
*
Expand Down Expand Up @@ -179,6 +183,54 @@ private String buildExtJson(final UpstreamInstance instance, final DocItem docIt
*/
@Override
public Collection<DocInfo> listAll() {
return DOC_DEFINITION_MAP.values();
final Map<String, List<DocItem>> projects = new TreeMap<>();
for (ApiDO api : apiMapper.selectAllWithDocument()) {
final DocItem docItem = readDocItem(api);
if (Objects.nonNull(docItem)) {
final String contextPath = StringUtils.defaultIfBlank(api.getContextPath(), "/");
projects.computeIfAbsent(contextPath, key -> new ArrayList<>()).add(docItem);
}
}
return projects.entrySet().stream().map(entry -> buildDocInfo(entry.getKey(), entry.getValue())).collect(Collectors.toList());
}

@Override
public DocItem getDocItem(final String id) {
return StringUtils.isBlank(id) ? null : readDocItem(apiMapper.selectByPrimaryKey(id));
}

private DocItem readDocItem(final ApiDO api) {
if (Objects.isNull(api) || StringUtils.isBlank(api.getDocument())) {
return null;
}
try {
final DocItem docItem = GsonUtils.getInstance().fromJson(api.getDocument(), DocItem.class);
// Other API sources may store OpenAPI operations instead of legacy DocItem documents.
if (Objects.isNull(docItem) || StringUtils.isBlank(docItem.getName())) {
return null;
}
docItem.setId(api.getId());
docItem.setModule(StringUtils.defaultIfBlank(docItem.getModule(), "default"));
return docItem;
} catch (JsonParseException ex) {
LOG.warn("Unable to read API document for id={}", api.getId(), ex);
return null;
}
}

private DocInfo buildDocInfo(final String contextPath, final List<DocItem> docItems) {
docItems.sort(Comparator.comparingInt(DocItem::getApiOrder).thenComparing(DocItem::getName).thenComparing(DocItem::getId));
final List<DocModule> modules = docItems.stream().collect(Collectors.groupingBy(DocItem::getModule)).entrySet().stream().map(entry -> {
final DocModule module = new DocModule();
module.setModule(entry.getKey());
module.setDocItems(entry.getValue());
module.setOrder(entry.getValue().stream().mapToInt(DocItem::getModuleOrder).min().orElse(0));
return module;
}).sorted(Comparator.comparingInt(DocModule::getOrder).thenComparing(DocModule::getModule)).collect(Collectors.toList());
final DocInfo docInfo = new DocInfo();
docInfo.setTitle(contextPath);
docInfo.setContextPath(contextPath);
docInfo.setDocModuleList(modules);
return docInfo;
}
}
6 changes: 6 additions & 0 deletions shenyu-admin/src/main/resources/mappers/api-sqlmap.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,12 @@
delete from api
where id = #{id,jdbcType=VARCHAR}
</delete>
<select id="selectAllWithDocument" resultMap="BaseResultMap">
SELECT id, context_path, api_path, document
FROM api
WHERE document IS NOT NULL
ORDER BY context_path, api_path, id
</select>
<insert id="insert" parameterType="org.apache.shenyu.admin.model.entity.ApiDO">
<!--@mbg.generated-->
insert into api (id, context_path, api_path,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@

import java.util.ArrayList;
import java.util.List;
import org.apache.shenyu.admin.model.bean.DocInfo;
import org.apache.shenyu.admin.model.bean.DocItem;
import org.apache.shenyu.admin.model.bean.DocModule;
import org.apache.shenyu.admin.model.vo.ShenyuDictVO;
import org.apache.shenyu.admin.service.ShenyuDictService;
import org.apache.shenyu.admin.service.manager.DocManager;
Expand All @@ -36,6 +39,9 @@

import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

/**
Expand Down Expand Up @@ -70,4 +76,59 @@ public void testGetAllDoc() throws Exception {
.andExpect(status().isOk())
.andReturn();
}

@Test
public void testMenusIncludePersistedDocumentIds() throws Exception {
DocItem docItem = new DocItem();
docItem.setId("api-id");
docItem.setName("/orders/find");
docItem.setSummary("Find order");
DocModule module = new DocModule();
module.setModule("orders");
module.setDocItems(List.of(docItem));
DocInfo project = new DocInfo();
project.setTitle("/orders");
project.setDocModuleList(List.of(module));
given(docManager.listAll()).willReturn(List.of(project));
given(shenyuDictService.list(any())).willReturn(List.of());

mockMvc.perform(MockMvcRequestBuilders.get("/apidoc/getDocMenus"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.menuProjects[0].label").value("/orders"))
.andExpect(jsonPath("$.data.menuProjects[0].children[0].children[0].id").value("api-id"))
.andExpect(jsonPath("$.data.menuProjects[0].children[0].children[0].name").value("/orders/find"));
}

@Test
public void testGetDocItem() throws Exception {
DocItem docItem = new DocItem();
docItem.setId("api-id");
docItem.setName("/orders/find");
docItem.setDescription("Find an order by id");
given(docManager.getDocItem("api-id")).willReturn(docItem);

mockMvc.perform(MockMvcRequestBuilders.get("/apidoc/getDocItem").param("id", "api-id"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data.id").value("api-id"))
.andExpect(jsonPath("$.data.name").value("/orders/find"))
.andExpect(jsonPath("$.data.description").value("Find an order by id"));
verify(docManager).getDocItem("api-id");
}

@Test
public void testGetDocItemRequiresId() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/apidoc/getDocItem"))
.andExpect(status().isBadRequest());
verify(docManager, never()).getDocItem(any());
}

@Test
public void testGetDocItemNotFound() throws Exception {
mockMvc.perform(MockMvcRequestBuilders.get("/apidoc/getDocItem").param("id", "missing"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.code").value(200))
.andExpect(jsonPath("$.data").doesNotExist());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,16 @@
package org.apache.shenyu.admin.mapper;

import org.apache.shenyu.admin.AbstractSpringIntegrationTest;
import org.apache.shenyu.admin.model.bean.DocInfo;
import org.apache.shenyu.admin.model.bean.DocItem;
import org.apache.shenyu.admin.model.entity.ApiDO;
import org.apache.shenyu.admin.model.page.PageParameter;
import org.apache.shenyu.admin.model.query.ApiQuery;
import org.apache.shenyu.admin.model.page.CommonPager;
import org.apache.shenyu.admin.model.vo.ApiVO;
import org.apache.shenyu.admin.service.ApiService;
import org.apache.shenyu.admin.service.manager.DocManager;
import org.apache.shenyu.common.utils.JsonUtils;
import org.apache.shenyu.common.utils.UUIDUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
Expand All @@ -39,6 +43,7 @@
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

/**
* Test cases for ApiMapper.
Expand All @@ -51,6 +56,9 @@ public final class ApiMapperTest extends AbstractSpringIntegrationTest {
@Resource
private ApiService apiService;

@Resource
private DocManager docManager;

private final ApiDO apiDO = buildApiDO();

@BeforeEach
Expand Down Expand Up @@ -109,6 +117,38 @@ public void testSelectByPrimaryKey() {
assertNotNull(apiDO);
}

@Test
public void testDocumentViewerReadsPersistedApis() {
ApiDO api = buildApiDO();
String contextPath = "/document-viewer-" + api.getId();
api.setContextPath(contextPath);
api.setApiPath(contextPath + "/find");
DocItem document = new DocItem();
document.setModule("orders");
document.setName(api.getApiPath());
document.setSummary("Persisted document");
api.setDocument(JsonUtils.toJson(document));
apiMapper.insert(api);
try {
ApiDO stored = apiMapper.selectAllWithDocument().stream().filter(item -> api.getId().equals(item.getId())).findFirst().orElseThrow();
assertEquals(api.getDocument(), stored.getDocument());
DocInfo project = docManager.listAll().stream().filter(item -> contextPath.equals(item.getContextPath())).findFirst().orElseThrow();
assertEquals(api.getId(), project.getDocModuleList().get(0).getDocItems().get(0).getId());
assertEquals("Persisted document", docManager.getDocItem(api.getId()).getSummary());

document.setSummary("Updated document");
api.setDocument(JsonUtils.toJson(document));
apiMapper.updateByPrimaryKeySelective(api);
assertEquals("Updated document", docManager.getDocItem(api.getId()).getSummary());

apiMapper.deleteByPrimaryKey(api.getId());
assertNull(docManager.getDocItem(api.getId()));
assertTrue(docManager.listAll().stream().noneMatch(item -> contextPath.equals(item.getContextPath())));
} finally {
apiMapper.deleteByPrimaryKey(api.getId());
}
}

@Test
public void testUpdateByPrimaryKeySelective() {
Timestamp now = new Timestamp(System.currentTimeMillis());
Expand Down
Loading
Loading