This is an automated email from the ASF dual-hosted git repository.
xiaoyu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-shenyu.git
The following commit(s) were added to refs/heads/master by this push:
new e2ab98b03 [ISSUE #3239] [New features]Shenyu-admin add new module: API
document aggregation display & sandbox test. (#3378)
e2ab98b03 is described below
commit e2ab98b03993870863472541a6cb0c04fdabf799
Author: lianjunwei <[email protected]>
AuthorDate: Wed May 11 11:35:44 2022 +0800
[ISSUE #3239] [New features]Shenyu-admin add new module: API document
aggregation display & sandbox test. (#3378)
* <New features> Shenyu-admin add new module: API document aggregation
display & sandbox test.
* [New features] Shenyu-admin add new module: API document aggregation
display & sandbox test.
* [New features] Shenyu-admin add new module: API document.
* code polish.
* code polish.
* supports pulling API documents for new registration services.
* code style.
* code style.
* code style.
* code style.
* code polish.
* code polish && test case.
* test case.
* solve conficts,modify LICENSE.
Co-authored-by: lianjunwei <[email protected]>
---
shenyu-admin/pom.xml | 22 +
.../admin/config/properties/ApiDocProperties.java | 126 ++++
.../shenyu/admin/controller/ApiDocController.java | 108 ++++
.../shenyu/admin/controller/SandboxController.java | 181 ++++++
.../admin/controller/SelectorController.java | 2 +-
.../admin/listener/ApplicationStartListener.java | 6 +
.../admin/listener/DataChangedEventDispatcher.java | 2 +
.../apache/shenyu/admin/model/bean/CustomCode.java | 86 +++
.../apache/shenyu/admin/model/bean/DocInfo.java | 88 +++
.../apache/shenyu/admin/model/bean/DocItem.java | 320 ++++++++++
.../apache/shenyu/admin/model/bean/DocModule.java | 87 +++
.../shenyu/admin/model/bean/DocParameter.java | 209 +++++++
.../shenyu/admin/model/bean/UpstreamInstance.java | 159 +++++
.../org/apache/shenyu/admin/model/vo/DocVO.java | 145 +++++
.../apache/shenyu/admin/model/vo/MenuDocItem.java | 86 +++
.../apache/shenyu/admin/model/vo/MenuModule.java | 67 +++
.../apache/shenyu/admin/model/vo/MenuProject.java | 67 +++
.../shenyu/admin/service/SelectorService.java | 11 +-
.../admin/service/impl/SelectorServiceImpl.java | 11 +-
.../shenyu/admin/service/manager/DocManager.java | 76 +++
.../shenyu/admin/service/manager/DocParser.java | 35 ++
.../admin/service/manager/LoadServiceDocEntry.java | 41 ++
.../admin/service/manager/ServiceDocManager.java | 39 ++
.../admin/service/manager/impl/DocManagerImpl.java | 161 +++++
.../manager/impl/LoadServiceDocEntryImpl.java | 198 +++++++
.../manager/impl/ServiceDocManagerImpl.java | 100 ++++
.../service/manager/impl/SwaggerDocParser.java | 324 ++++++++++
.../org/apache/shenyu/admin/utils/HttpUtils.java | 649 +++++++++++++++++++++
.../shenyu/admin/utils/ShenyuSignatureUtils.java | 71 +++
.../org/apache/shenyu/admin/utils/UploadUtils.java | 57 ++
shenyu-admin/src/main/resources/application.yml | 9 +
.../admin/controller/SelectorControllerTest.java | 2 +-
.../listener/DataChangedEventDispatcherTest.java | 7 +
.../shenyu/admin/service/SelectorServiceTest.java | 2 +-
.../src/main/release-docs/LICENSE | 2 +
35 files changed, 3548 insertions(+), 8 deletions(-)
diff --git a/shenyu-admin/pom.xml b/shenyu-admin/pom.xml
index f650aad57..08ac126b2 100644
--- a/shenyu-admin/pom.xml
+++ b/shenyu-admin/pom.xml
@@ -25,6 +25,11 @@
<modelVersion>4.0.0</modelVersion>
<artifactId>shenyu-admin</artifactId>
+ <properties>
+ <fastjson.version>1.2.80</fastjson.version>
+ <commons-io.version>2.11.0</commons-io.version>
+ </properties>
+
<dependencies>
<dependency>
<groupId>org.apache.shenyu</groupId>
@@ -236,6 +241,23 @@
<artifactId>curator-test</artifactId>
<scope>test</scope>
</dependency>
+
+ <dependency>
+ <groupId>com.squareup.okhttp3</groupId>
+ <artifactId>okhttp</artifactId>
+ </dependency>
+
+ <dependency>
+ <groupId>com.alibaba</groupId>
+ <artifactId>fastjson</artifactId>
+ <version>${fastjson.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>commons-io</groupId>
+ <artifactId>commons-io</artifactId>
+ <version>${commons-io.version}</version>
+ </dependency>
</dependencies>
<profiles>
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/properties/ApiDocProperties.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/properties/ApiDocProperties.java
new file mode 100644
index 000000000..5bdc2d140
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/config/properties/ApiDocProperties.java
@@ -0,0 +1,126 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.config.properties;
+
+import java.util.List;
+import org.springframework.boot.context.properties.ConfigurationProperties;
+import org.springframework.stereotype.Component;
+
+/**
+ * ApiDoc properties.
+ */
+@Component
+@ConfigurationProperties(prefix = "shenyu.apidoc")
+public class ApiDocProperties {
+
+ private String gatewayUrl;
+
+ private List<EnvConfig> envProps;
+
+ /**
+ * getGatewayUrl.
+ * @return String
+ */
+ public String getGatewayUrl() {
+ return gatewayUrl;
+ }
+
+ /**
+ * setGatewayUrl.
+ * @param gatewayUrl gatewayUrl
+ */
+ public void setGatewayUrl(final String gatewayUrl) {
+ this.gatewayUrl = gatewayUrl;
+ }
+
+ /**
+ * getEnvProps.
+ * @return List
+ */
+ public List<EnvConfig> getEnvProps() {
+ return envProps;
+ }
+
+ /**
+ * setEnvProps.
+ * @param envProps envProps
+ */
+ public void setEnvProps(final List<EnvConfig> envProps) {
+ this.envProps = envProps;
+ }
+
+ /**
+ * environment config.
+ */
+ public static class EnvConfig {
+
+ private String envLabel;
+
+ private String addressLabel;
+
+ private String addressUrl;
+
+ /**
+ * getEnvLabel.
+ * @return String
+ */
+ public String getEnvLabel() {
+ return envLabel;
+ }
+
+ /**
+ * setEnvLabel.
+ * @param envLabel envLabel
+ */
+ public void setEnvLabel(final String envLabel) {
+ this.envLabel = envLabel;
+ }
+
+ /**
+ * getAddressLabel.
+ * @return String
+ */
+ public String getAddressLabel() {
+ return addressLabel;
+ }
+
+ /**
+ * setAddressLabel.
+ * @param addressLabel addressLabel
+ */
+ public void setAddressLabel(final String addressLabel) {
+ this.addressLabel = addressLabel;
+ }
+
+ /**
+ * getAddressUrl.
+ * @return String
+ */
+ public String getAddressUrl() {
+ return addressUrl;
+ }
+
+ /**
+ * setAddressUrl.
+ * @param addressUrl addressUrl
+ */
+ public void setAddressUrl(final String addressUrl) {
+ this.addressUrl = addressUrl;
+ }
+ }
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/ApiDocController.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/ApiDocController.java
new file mode 100644
index 000000000..d1765a8c7
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/ApiDocController.java
@@ -0,0 +1,108 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.controller;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.function.Function;
+import java.util.stream.Collectors;
+import javax.annotation.Resource;
+import org.apache.shenyu.admin.config.properties.ApiDocProperties;
+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.MenuDocItem;
+import org.apache.shenyu.admin.model.vo.MenuModule;
+import org.apache.shenyu.admin.model.vo.MenuProject;
+import org.apache.shenyu.admin.service.manager.DocManager;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+/**
+ * ApiDoc Controller.
+ */
+@RestController
+@RequestMapping("/apidoc")
+public class ApiDocController {
+
+ @Autowired
+ private DocManager docManager;
+
+ @Resource
+ private ApiDocProperties apiDocProperties;
+
+ /**
+ * Menu list of documents.
+ *
+ * @return ShenyuAdminResult
+ */
+ @GetMapping("/getDocMenus")
+ public ShenyuAdminResult getAllDoc() {
+ Collection<DocInfo> docInfos = docManager.listAll();
+ List<MenuProject> menuProjects = docInfos.stream()
+ .map(getMenuAndDocInfo())
+ .collect(Collectors.toList());
+ DocVO docVO = new DocVO();
+ docVO.setGatewayUrl(apiDocProperties.getGatewayUrl());
+ docVO.setMenuProjects(menuProjects);
+ docVO.setEnvProps(apiDocProperties.getEnvProps());
+ docVO.setCookie("Fill in the real cookie value.(signature
authentication and login free API ignore this item)");
+ docVO.setAppKey("");
+ return ShenyuAdminResult.success(docVO);
+ }
+
+ /**
+ * Query the document content according to the document ID.
+ * @param id docmentId
+ * @return ShenyuAdminResult
+ */
+ @GetMapping("/getDocItem")
+ public ShenyuAdminResult getDocItem(final String id) {
+ DocItem docItem = docManager.getDocItem(id);
+ return ShenyuAdminResult.success(docItem);
+ }
+
+ private Function<DocInfo, MenuProject> getMenuAndDocInfo() {
+ return docInfo -> {
+ MenuProject menuProject = new MenuProject();
+ menuProject.setLabel(docInfo.getTitle());
+ List<MenuModule> menuModules = docInfo.getDocModuleList()
+ .stream()
+ .map(docModule -> {
+ MenuModule menuModule = new MenuModule();
+ menuModule.setLabel(docModule.getModule());
+ List<MenuDocItem> docItems =
docModule.getDocItems().stream()
+ .map(docItem -> {
+ MenuDocItem menuDocItem = new MenuDocItem();
+ menuDocItem.setId(docItem.getId());
+ menuDocItem.setLabel(docItem.getSummary());
+ menuDocItem.setName(docItem.getName());
+ return menuDocItem;
+ }).collect(Collectors.toList());
+ menuModule.setChildren(docItems);
+ return menuModule;
+ }).collect(Collectors.toList());
+ menuProject.setChildren(menuModules);
+ return menuProject;
+ };
+ }
+
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/SandboxController.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/SandboxController.java
new file mode 100644
index 000000000..5bb8d5de2
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/SandboxController.java
@@ -0,0 +1,181 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.controller;
+
+import com.google.common.collect.Maps;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDateTime;
+import java.time.ZoneOffset;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+import javax.annotation.Resource;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import org.apache.commons.io.IOUtils;
+import org.apache.commons.lang3.StringEscapeUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shenyu.admin.mapper.AppAuthMapper;
+import org.apache.shenyu.admin.model.entity.AppAuthDO;
+import org.apache.shenyu.admin.utils.HttpUtils;
+import org.apache.shenyu.admin.utils.ShenyuSignatureUtils;
+import org.apache.shenyu.admin.utils.UploadUtils;
+import org.apache.shenyu.common.utils.JsonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.util.Assert;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.util.UriUtils;
+
+/**
+ * Sandbox environment.
+ */
+@RestController
+@RequestMapping("/sandbox")
+public class SandboxController {
+ private static final Logger LOG =
LoggerFactory.getLogger(SandboxController.class);
+
+ private static final HttpUtils HTTP_UTILS = new HttpUtils();
+
+ @Resource
+ private AppAuthMapper appAuthMapper;
+
+ /**
+ * proxyGateway.
+ *
+ * @param gatewayUrl gatewayUrl
+ * @param appKey appKey
+ * @param method method
+ * @param cookie cookie
+ * @param bizParam bizParam
+ * @param httpMethod httpMethod
+ * @param request request
+ * @param response response
+ * @throws IOException IOException
+ */
+ @RequestMapping("/proxyGateway")
+ public void proxyGateway(
+ @RequestParam(required = false) final String gatewayUrl,
+ @RequestParam final String appKey,
+ @RequestParam final String method,
+ @RequestParam final String cookie,
+ @RequestParam final String bizParam,
+ @RequestParam(defaultValue = "get") final String httpMethod,
+ final HttpServletRequest request,
+ final HttpServletResponse response) throws IOException {
+
+ Assert.isTrue(StringUtils.isNotBlank(method), "method cannot be
empty.");
+ Assert.isTrue(StringUtils.isNotBlank(gatewayUrl), "gatewayUrl cannot
be empty.");
+ String gatewayUrlStr = gatewayUrl + method;
+
+ // Public request parameters.
+ Map<String, String> params = new HashMap<String, String>();
+ try {
+ String bizParamStr = StringEscapeUtils.escapeHtml4(bizParam);
+ Map<String, String> map = (Map) JsonUtils.toMap(bizParamStr);
+ LOG.info("bizParam toMap= {}", JsonUtils.toJson(map));
+ if (map != null) {
+ params.putAll(map);
+ }
+ } catch (Exception e) {
+ LOG.error("JsonUtils.toMap error={}", e);
+ }
+
+ String paramsQuery = buildParamQuery(params);
+
+ Collection<MultipartFile> uploadFiles =
UploadUtils.getUploadFiles(request);
+ List<HttpUtils.UploadFile> files = uploadFiles.stream()
+ .map(multipartFile -> {
+ try {
+ return new HttpUtils.UploadFile(multipartFile.getName(),
multipartFile.getOriginalFilename(), multipartFile.getBytes());
+ } catch (IOException e) {
+ LOG.error("upload file fail", e);
+ return null;
+ }
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+
+ Map<String, String> header = new HashMap<>();
+ header.put("Cookie", cookie);
+
+ String signContent = null;
+ String sign = null;
+ if (StringUtils.isNotEmpty(appKey)) {
+ String timestamp =
String.valueOf(LocalDateTime.now().toInstant(ZoneOffset.of("+8")).toEpochMilli());
+ String secureKey = getSecureKey(appKey);
+ signContent = ShenyuSignatureUtils.getSignContent(secureKey,
timestamp, method);
+ sign = ShenyuSignatureUtils.generateSign(signContent);
+
+ header.put("timestamp", timestamp);
+ header.put("appKey", appKey);
+ header.put("sign", sign);
+ header.put("version", ShenyuSignatureUtils.VERSION);
+ }
+
+ try {
+ Response resp = HTTP_UTILS.requestCall(gatewayUrlStr, params,
header, HttpUtils.HTTPMethod.fromValue(httpMethod), files);
+ ResponseBody body = resp.body();
+ if (Objects.isNull(body)) {
+ return;
+ }
+ Map<String, List<String>> headersMap = resp.headers().toMultimap();
+ Map<String, String> targetHeaders =
Maps.newHashMapWithExpectedSize(headersMap.size());
+ headersMap.forEach((key, value) -> {
+ String headerValue = String.join(",", value);
+ response.setHeader(key, headerValue);
+ targetHeaders.put(key, headerValue);
+ });
+ response.addHeader("response-headers",
JsonUtils.toJson(targetHeaders));
+ response.addHeader("sendbox-params", UriUtils.encode(paramsQuery,
StandardCharsets.UTF_8));
+ response.addHeader("sendbox-beforesign",
UriUtils.encode(signContent, StandardCharsets.UTF_8));
+ response.addHeader("sendbox-sign", UriUtils.encode(sign,
StandardCharsets.UTF_8));
+ IOUtils.copy(body.byteStream(), response.getOutputStream());
+ response.flushBuffer();
+ } catch (Exception e) {
+ LOG.error("request error", e);
+ throw new RuntimeException(e.getMessage());
+ }
+ }
+
+ private String getSecureKey(final String appKey) {
+ AppAuthDO appAuthDO = appAuthMapper.findByAppKey(appKey);
+ if (Objects.isNull(appAuthDO) ||
StringUtils.isEmpty(appAuthDO.getAppSecret())) {
+ throw new RuntimeException("security key not found.");
+ }
+ return appAuthDO.getAppSecret();
+ }
+
+ protected String buildParamQuery(final Map<String, String> params) {
+ StringBuilder sb = new StringBuilder();
+ for (Map.Entry<String, String> entry : params.entrySet()) {
+
sb.append("&").append(entry.getKey()).append("=").append(entry.getValue());
+ }
+ return sb.substring(1);
+ }
+
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/SelectorController.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/SelectorController.java
index 9b8bef649..42787091a 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/SelectorController.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/controller/SelectorController.java
@@ -72,7 +72,7 @@ public class SelectorController implements
PagedController<SelectorQueryConditio
public ShenyuAdminResult querySelectors(final String pluginId, final
String name,
@RequestParam @NotNull final
Integer currentPage,
@RequestParam @NotNull final
Integer pageSize) {
- CommonPager<SelectorVO> commonPager = selectorService.listByPage(new
SelectorQuery(pluginId, name, new PageParameter(currentPage, pageSize)));
+ CommonPager<SelectorVO> commonPager =
selectorService.listByPageWithPermission(new SelectorQuery(pluginId, name, new
PageParameter(currentPage, pageSize)));
return ShenyuAdminResult.success(ShenyuResultMessage.QUERY_SUCCESS,
commonPager);
}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/ApplicationStartListener.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/ApplicationStartListener.java
index eda5f6286..e79383d2d 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/ApplicationStartListener.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/ApplicationStartListener.java
@@ -17,7 +17,9 @@
package org.apache.shenyu.admin.listener;
+import javax.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
+import org.apache.shenyu.admin.service.manager.LoadServiceDocEntry;
import org.apache.shenyu.admin.utils.ShenyuDomain;
import org.apache.shenyu.common.utils.IpUtils;
import org.springframework.boot.web.context.WebServerInitializedEvent;
@@ -31,6 +33,9 @@ import org.springframework.stereotype.Component;
@Component
public class ApplicationStartListener implements
ApplicationListener<WebServerInitializedEvent> {
+ @Resource
+ private LoadServiceDocEntry loadServiceDocEntry;
+
@Override
public void onApplicationEvent(final WebServerInitializedEvent event) {
int port = event.getWebServer().getPort();
@@ -41,5 +46,6 @@ public class ApplicationStartListener implements
ApplicationListener<WebServerIn
} else {
ShenyuDomain.getInstance().setHttpPath(domain);
}
+ loadServiceDocEntry.loadApiDocument();
}
}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcher.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcher.java
index 0c35ffa3d..f8922cdc5 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcher.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcher.java
@@ -17,6 +17,7 @@
package org.apache.shenyu.admin.listener;
+import org.apache.shenyu.admin.service.manager.LoadServiceDocEntry;
import org.apache.shenyu.common.dto.AppAuthData;
import org.apache.shenyu.common.dto.MetaData;
import org.apache.shenyu.common.dto.PluginData;
@@ -62,6 +63,7 @@ public class DataChangedEventDispatcher implements
ApplicationListener<DataChang
break;
case SELECTOR:
listener.onSelectorChanged((List<SelectorData>)
event.getSource(), event.getEventType());
+
applicationContext.getBean(LoadServiceDocEntry.class).loadDocOnSelectorChanged((List<SelectorData>)
event.getSource(), event.getEventType());
break;
case META_DATA:
listener.onMetaDataChanged((List<MetaData>)
event.getSource(), event.getEventType());
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/CustomCode.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/CustomCode.java
new file mode 100644
index 000000000..7bd732ebc
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/CustomCode.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.model.bean;
+
+
+/**
+ * CustomCode.
+ */
+public class CustomCode {
+
+ private String code;
+
+ private String message;
+
+ private String solution;
+
+
+ /**
+ * getCode.
+ *
+ * @return String
+ */
+ public String getCode() {
+ return code;
+ }
+
+ /**
+ * setCode.
+ *
+ * @param code code
+ */
+ public void setCode(final String code) {
+ this.code = code;
+ }
+
+ /**
+ * getMessage.
+ *
+ * @return String
+ */
+ public String getMessage() {
+ return message;
+ }
+
+ /**
+ * setMessage.
+ *
+ * @param message message
+ */
+ public void setMessage(final String message) {
+ this.message = message;
+ }
+
+ /**
+ * getSolution.
+ *
+ * @return String
+ */
+ public String getSolution() {
+ return solution;
+ }
+
+ /**
+ * setSolution.
+ *
+ * @param solution solution
+ */
+ public void setSolution(final String solution) {
+ this.solution = solution;
+ }
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/DocInfo.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/DocInfo.java
new file mode 100644
index 000000000..95d4da562
--- /dev/null
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/DocInfo.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.model.bean;
+
+import com.alibaba.fastjson.annotation.JSONField;
+import java.util.List;
+
+/**
+ * DocInfo.
+ */
+public class DocInfo {
+
+ private String title;
+
+ @JSONField(serialize = false)
+ private String clusterName;
+
+ private List<DocModule> docModuleList;
+
+ /**
+ * getTitle.
+ *
+ * @return String
+ */
+ public String getTitle() {
+ return title;
+ }
+
+ /**
+ * setTitle.
+ *
+ * @param title title
+ */
+ public void setTitle(final String title) {
+ this.title = title;
+ }
+
+ /**
+ * getClusterName.
+ *
+ * @return String
+ */
+ public String getClusterName() {
+ return clusterName;
+ }
+
+ /**
+ * setServiceId.
+ *
+ * @param clusterName clusterName
+ */
+ public void setClusterName(final String clusterName) {
+ this.clusterName = clusterName;
+ }
+
+ /**
+ * getDocModuleList.
+ *
+ * @return List
+ */
+ public List<DocModule> getDocModuleList() {
+ return docModuleList;
+ }
+
+ /**
+ * setDocModuleList.
+ *
+ * @param docModuleList docModuleList
+ */
+ public void setDocModuleList(final List<DocModule> docModuleList) {
+ this.docModuleList = docModuleList;
+ }
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/DocItem.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/DocItem.java
new file mode 100644
index 000000000..60f5fd706
--- /dev/null
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/DocItem.java
@@ -0,0 +1,320 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.model.bean;
+
+import java.util.Collection;
+import java.util.List;
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * DocItem.
+ */
+public class DocItem {
+
+ private String id;
+
+ private String module;
+
+ private String name;
+
+ private String summary = StringUtils.EMPTY;
+
+ private String description = StringUtils.EMPTY;
+
+ /**
+ * Whether to upload multiple files.
+ */
+ private boolean multiple;
+
+ /**
+ * http method list.
+ */
+ private Collection<String> httpMethodList;
+
+ private Collection<String> produces;
+
+ /**
+ * Module Order.
+ */
+ private int moduleOrder;
+
+ /**
+ * api doc Order.
+ */
+ private int apiOrder;
+
+ private List<DocParameter> requestParameters;
+
+ private List<DocParameter> responseParameters;
+
+ private List<CustomCode> bizCodeList;
+
+ /**
+ * isUploadRequest.
+ * Whether it is a file upload request.
+ *
+ * @return boolean
+ */
+ public boolean isUploadRequest() {
+ boolean upload = false;
+ if (requestParameters != null) {
+ for (DocParameter requestParameter : requestParameters) {
+ String type = requestParameter.getType();
+ if ("file".equalsIgnoreCase(type)) {
+ upload = true;
+ break;
+ }
+ }
+ }
+ return multiple || upload;
+ }
+
+ /**
+ * getId.
+ *
+ * @return String
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * setId.
+ *
+ * @param id id
+ */
+ public void setId(final String id) {
+ this.id = id;
+ }
+
+ /**
+ * getModule.
+ *
+ * @return String
+ */
+ public String getModule() {
+ return module;
+ }
+
+ /**
+ * setModule.
+ *
+ * @param module module
+ */
+ public void setModule(final String module) {
+ this.module = module;
+ }
+
+ /**
+ * getName.
+ *
+ * @return String
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * setName.
+ *
+ * @param name name
+ */
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * getSummary.
+ *
+ * @return String
+ */
+ public String getSummary() {
+ return summary;
+ }
+
+ /**
+ * setSummary.
+ *
+ * @param summary summary
+ */
+ public void setSummary(final String summary) {
+ this.summary = summary;
+ }
+
+ /**
+ * getDescription.
+ *
+ * @return String
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * setDescription.
+ *
+ * @param description description
+ */
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ /**
+ * isMultiple.
+ *
+ * @return String
+ */
+ public boolean isMultiple() {
+ return multiple;
+ }
+
+ /**
+ * setMultiple.
+ *
+ * @param multiple multiple
+ */
+ public void setMultiple(final boolean multiple) {
+ this.multiple = multiple;
+ }
+
+ /**
+ * getHttpMethodList.
+ *
+ * @return String
+ */
+ public Collection<String> getHttpMethodList() {
+ return httpMethodList;
+ }
+
+ /**
+ * setHttpMethodList.
+ *
+ * @param httpMethodList httpMethodList
+ */
+ public void setHttpMethodList(final Collection<String> httpMethodList) {
+ this.httpMethodList = httpMethodList;
+ }
+
+ /**
+ * getProduces.
+ *
+ * @return String
+ */
+ public Collection<String> getProduces() {
+ return produces;
+ }
+
+ /**
+ * setProduces.
+ *
+ * @param produces produces
+ */
+ public void setProduces(final Collection<String> produces) {
+ this.produces = produces;
+ }
+
+ /**
+ * getModuleOrder.
+ *
+ * @return String
+ */
+ public int getModuleOrder() {
+ return moduleOrder;
+ }
+
+ /**
+ * setModuleOrder.
+ *
+ * @param moduleOrder moduleOrder
+ */
+ public void setModuleOrder(final int moduleOrder) {
+ this.moduleOrder = moduleOrder;
+ }
+
+ /**
+ * getApiOrder.
+ *
+ * @return String
+ */
+ public int getApiOrder() {
+ return apiOrder;
+ }
+
+ /**
+ * setApiOrder.
+ *
+ * @param apiOrder apiOrder
+ */
+ public void setApiOrder(final int apiOrder) {
+ this.apiOrder = apiOrder;
+ }
+
+ /**
+ * getRequestParameters.
+ *
+ * @return List
+ */
+ public List<DocParameter> getRequestParameters() {
+ return requestParameters;
+ }
+
+ /**
+ * setRequestParameters.
+ *
+ * @param requestParameters requestParameters
+ */
+ public void setRequestParameters(final List<DocParameter>
requestParameters) {
+ this.requestParameters = requestParameters;
+ }
+
+ /**
+ * getResponseParameters.
+ *
+ * @return List
+ */
+ public List<DocParameter> getResponseParameters() {
+ return responseParameters;
+ }
+
+ /**
+ * setResponseParameters.
+ *
+ * @param responseParameters responseParameters
+ */
+ public void setResponseParameters(final List<DocParameter>
responseParameters) {
+ this.responseParameters = responseParameters;
+ }
+
+ /**
+ * getBizCodeList.
+ *
+ * @return List
+ */
+ public List<CustomCode> getBizCodeList() {
+ return bizCodeList;
+ }
+
+ /**
+ * setBizCodeList.
+ *
+ * @param bizCodeList bizCodeList
+ */
+ public void setBizCodeList(final List<CustomCode> bizCodeList) {
+ this.bizCodeList = bizCodeList;
+ }
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/DocModule.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/DocModule.java
new file mode 100644
index 000000000..1e78e4ea4
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/DocModule.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.model.bean;
+
+import java.util.List;
+
+/**
+ * DocModule.
+ *
+ */
+public class DocModule {
+
+ private String module;
+
+ private int order;
+
+ private List<DocItem> docItems;
+
+ /**
+ * getModule.
+ *
+ * @return String
+ */
+ public String getModule() {
+ return module;
+ }
+
+ /**
+ * setModule.
+ *
+ * @param module module
+ */
+ public void setModule(final String module) {
+ this.module = module;
+ }
+
+ /**
+ * getDocItems.
+ *
+ * @return List
+ */
+ public List<DocItem> getDocItems() {
+ return docItems;
+ }
+
+ /**
+ * setDocItems.
+ *
+ * @param docItems docItems
+ */
+ public void setDocItems(final List<DocItem> docItems) {
+ this.docItems = docItems;
+ }
+
+ /**
+ * getOrder.
+ *
+ * @return int
+ */
+ public int getOrder() {
+ return order;
+ }
+
+ /**
+ * setOrder.
+ *
+ * @param order order
+ */
+ public void setOrder(final int order) {
+ this.order = order;
+ }
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/DocParameter.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/DocParameter.java
new file mode 100644
index 000000000..b02f26f9b
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/DocParameter.java
@@ -0,0 +1,209 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.model.bean;
+
+import java.util.List;
+import java.util.concurrent.atomic.AtomicInteger;
+
+/**
+ * DocParameter.
+ * Parameter, type, required or not, maximum length, description, example
value.
+ */
+public class DocParameter {
+
+ private static final AtomicInteger GEN = new AtomicInteger();
+
+ private Integer id = GEN.incrementAndGet();
+
+ private String module;
+
+ private String name;
+
+ private String type;
+
+ private String maxLength = "-";
+
+ private boolean required;
+
+ private String description;
+
+ private String example = "";
+
+ private List<DocParameter> refs;
+
+ /**
+ * getId.
+ *
+ * @return Integer
+ */
+ public Integer getId() {
+ return id;
+ }
+
+ /**
+ * setId.
+ * @param id id
+ */
+ public void setId(final Integer id) {
+ this.id = id;
+ }
+
+ /**
+ * getModule.
+ *
+ * @return String
+ */
+ public String getModule() {
+ return module;
+ }
+
+ /**
+ * setModule.
+ *
+ * @param module module
+ */
+ public void setModule(final String module) {
+ this.module = module;
+ }
+
+ /**
+ * getName.
+ *
+ * @return String
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * setName.
+ *
+ * @param name name
+ */
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * getType.
+ *
+ * @return String
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * setType.
+ *
+ * @param type type
+ */
+ public void setType(final String type) {
+ this.type = type;
+ }
+
+ /**
+ * getMaxLength.
+ *
+ * @return String
+ */
+ public String getMaxLength() {
+ return maxLength;
+ }
+
+ /**
+ * setMaxLength.
+ *
+ * @param maxLength maxLength
+ */
+ public void setMaxLength(final String maxLength) {
+ this.maxLength = maxLength;
+ }
+
+ /**
+ * isRequired.
+ *
+ * @return boolean
+ */
+ public boolean isRequired() {
+ return required;
+ }
+
+ /**
+ * setRequired.
+ *
+ * @param required required
+ */
+ public void setRequired(final boolean required) {
+ this.required = required;
+ }
+
+ /**
+ * getDescription.
+ *
+ * @return String
+ */
+ public String getDescription() {
+ return description;
+ }
+
+ /**
+ * setDescription.
+ *
+ * @param description description
+ */
+ public void setDescription(final String description) {
+ this.description = description;
+ }
+
+ /**
+ * getExample.
+ *
+ * @return String
+ */
+ public String getExample() {
+ return example;
+ }
+
+ /**
+ * setExample.
+ *
+ * @param example example
+ */
+ public void setExample(final String example) {
+ this.example = example;
+ }
+
+ /**
+ * getRefs.
+ *
+ * @return List
+ */
+ public List<DocParameter> getRefs() {
+ return refs;
+ }
+
+ /**
+ * setRefs.
+ *
+ * @param refs refs
+ */
+ public void setRefs(final List<DocParameter> refs) {
+ this.refs = refs;
+ }
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/UpstreamInstance.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/UpstreamInstance.java
new file mode 100644
index 000000000..c0a9c7e6c
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/bean/UpstreamInstance.java
@@ -0,0 +1,159 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.model.bean;
+
+import org.apache.commons.lang3.StringUtils;
+
+/**
+ * UpstreamInstance.
+ */
+public class UpstreamInstance {
+
+ private String contextPath;
+
+ private String ip;
+
+ private int port;
+
+ private Long startupTime;
+
+ /**
+ * instance health status.
+ */
+ private boolean healthy = true;
+
+ /**
+ * If instance is enabled to accept request.
+ */
+ private boolean enabled = true;
+
+ /**
+ * getClusterName.
+ * @return String
+ */
+ public String getClusterName() {
+ if (StringUtils.isNotEmpty(contextPath)) {
+ return contextPath.substring(1);
+ }
+ return null;
+ }
+
+ /**
+ * getContextPath.
+ *
+ * @return String
+ */
+ public String getContextPath() {
+ return contextPath;
+ }
+
+ /**
+ * setContextPath.
+ *
+ * @param contextPath contextPath
+ */
+ public void setContextPath(final String contextPath) {
+ this.contextPath = contextPath;
+ }
+
+ /**
+ * getIp.
+ *
+ * @return String
+ */
+ public String getIp() {
+ return ip;
+ }
+
+ /**
+ * setIp.
+ *
+ * @param ip ip
+ */
+ public void setIp(final String ip) {
+ this.ip = ip;
+ }
+
+ /**
+ * getPort.
+ *
+ * @return int
+ */
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * setPort.
+ *
+ * @param port port
+ */
+ public void setPort(final int port) {
+ this.port = port;
+ }
+
+ /**
+ * getStartupTime.
+ *
+ * @return Long
+ */
+ public Long getStartupTime() {
+ return startupTime;
+ }
+
+ /**
+ * setStartupTime.
+ *
+ * @param startupTime startupTime
+ */
+ public void setStartupTime(final Long startupTime) {
+ this.startupTime = startupTime;
+ }
+
+ /**
+ * isHealthy.
+ * @return boolean
+ */
+ public boolean isHealthy() {
+ return healthy;
+ }
+
+ /**
+ * setHealthy.
+ * @param healthy healthy
+ */
+ public void setHealthy(final boolean healthy) {
+ this.healthy = healthy;
+ }
+
+ /**
+ * isEnabled.
+ * @return boolean
+ */
+ public boolean isEnabled() {
+ return enabled;
+ }
+
+ /**
+ * setEnabled.
+ * @param enabled enabled
+ */
+ public void setEnabled(final boolean enabled) {
+ this.enabled = enabled;
+ }
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/DocVO.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/DocVO.java
new file mode 100644
index 000000000..db7584f82
--- /dev/null
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/DocVO.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.model.vo;
+
+import java.util.Collection;
+import java.util.List;
+import org.apache.shenyu.admin.config.properties.ApiDocProperties;
+
+/**
+ * DocVO.
+ */
+public class DocVO {
+
+ private String cookie;
+
+ private String appKey;
+
+ private String gatewayUrl;
+
+ private List<ApiDocProperties.EnvConfig> envProps;
+
+ private Collection<MenuProject> menuProjects;
+
+ /**
+ * getGatewayUrl.
+ *
+ * @return String
+ */
+ public String getGatewayUrl() {
+ return gatewayUrl;
+ }
+
+ /**
+ * setGatewayUrl.
+ *
+ * @param gatewayUrl gatewayUrl
+ */
+ public void setGatewayUrl(final String gatewayUrl) {
+ this.gatewayUrl = gatewayUrl;
+ }
+
+
+ /**
+ * getEnvProps.
+ * @return List
+ */
+ public List<ApiDocProperties.EnvConfig> getEnvProps() {
+ return envProps;
+ }
+
+ /**
+ * setEnvProps.
+ * @param envProps envProps
+ */
+ public void setEnvProps(final List<ApiDocProperties.EnvConfig> envProps) {
+ this.envProps = envProps;
+ }
+
+ /**
+ * getCookie.
+ *
+ * @return String
+ */
+ public String getCookie() {
+ return cookie;
+ }
+
+ /**
+ * setCookie.
+ *
+ * @param cookie cookie
+ */
+ public void setCookie(final String cookie) {
+ this.cookie = cookie;
+ }
+
+ /**
+ * getAppKey.
+ *
+ * @return String
+ */
+ public String getAppKey() {
+ return appKey;
+ }
+
+ /**
+ * setAppKey.
+ *
+ * @param appKey appKey
+ */
+ public void setAppKey(final String appKey) {
+ this.appKey = appKey;
+ }
+
+// /**
+// * getAppType.
+// *
+// * @return String
+// */
+// public String getAppType() {
+// return appType;
+// }
+//
+// /**
+// * setAppType.
+// *
+// * @param appType appType
+// */
+// public void setAppType(final String appType) {
+// this.appType = appType;
+// }
+
+ /**
+ * getMenuProjects.
+ *
+ * @return Collection
+ */
+ public Collection<MenuProject> getMenuProjects() {
+ return menuProjects;
+ }
+
+ /**
+ * setMenuProjects.
+ *
+ * @param menuProjects menuProjects
+ */
+ public void setMenuProjects(final Collection<MenuProject> menuProjects) {
+ this.menuProjects = menuProjects;
+ }
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/MenuDocItem.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/MenuDocItem.java
new file mode 100644
index 000000000..ad06995d0
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/MenuDocItem.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.model.vo;
+
+
+/**
+ * MenuDocItem.
+ */
+public class MenuDocItem {
+
+ private String id;
+
+ private String label;
+
+ private String name;
+
+ /**
+ * getId.
+ *
+ * @return String
+ */
+ public String getId() {
+ return id;
+ }
+
+ /**
+ * setId.
+ *
+ * @param id id
+ */
+ public void setId(final String id) {
+ this.id = id;
+ }
+
+ /**
+ * getLabel.
+ *
+ * @return String
+ */
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * setLabel.
+ *
+ * @param label label
+ */
+ public void setLabel(final String label) {
+ this.label = label;
+ }
+
+ /**
+ * getName.
+ *
+ * @return String
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * setName.
+ *
+ * @param name name
+ */
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/MenuModule.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/MenuModule.java
new file mode 100644
index 000000000..c1fbd9707
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/MenuModule.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.model.vo;
+
+import java.util.List;
+
+/**
+ * MenuModule.
+ * MenuModule
+ */
+public class MenuModule {
+
+ private String label;
+
+ private List<MenuDocItem> children;
+
+ /**
+ * getLabel.
+ *
+ * @return String
+ */
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * setLabel.
+ *
+ * @param label label
+ */
+ public void setLabel(final String label) {
+ this.label = label;
+ }
+
+ /**
+ * getChildren.
+ *
+ * @return List
+ */
+ public List<MenuDocItem> getChildren() {
+ return children;
+ }
+
+ /**
+ * setChildren.
+ *
+ * @param children children
+ */
+ public void setChildren(final List<MenuDocItem> children) {
+ this.children = children;
+ }
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/MenuProject.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/MenuProject.java
new file mode 100644
index 000000000..bc6ae0b9b
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/model/vo/MenuProject.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.model.vo;
+
+import java.util.List;
+
+/**
+ * MenuProject.
+ *
+ */
+public class MenuProject {
+
+ private String label;
+
+ private List<MenuModule> children;
+
+ /**
+ * setLabel.
+ *
+ * @return String
+ */
+ public String getLabel() {
+ return label;
+ }
+
+ /**
+ * setLabel.
+ *
+ * @param label label
+ */
+ public void setLabel(final String label) {
+ this.label = label;
+ }
+
+ /**
+ * getChildren.
+ *
+ * @return List
+ */
+ public List<MenuModule> getChildren() {
+ return children;
+ }
+
+ /**
+ * setChildren.
+ *
+ * @param children children
+ */
+ public void setChildren(final List<MenuModule> children) {
+ this.children = children;
+ }
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/SelectorService.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/SelectorService.java
index 0594d76fb..2c989fced 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/SelectorService.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/SelectorService.java
@@ -142,15 +142,22 @@ public interface SelectorService extends
PageService<SelectorQueryCondition, Sel
* @return the selector data
*/
SelectorData buildByName(String name, String pluginName);
-
+
/**
* find page of selector by query.
*
* @param selectorQuery {@linkplain SelectorQuery}
* @return {@linkplain CommonPager}
*/
+ CommonPager<SelectorVO> listByPageWithPermission(SelectorQuery
selectorQuery);
+
+ /**
+ * find page of selector by query.
+ * @param selectorQuery selectorQuery
+ * @return CommonPager
+ */
CommonPager<SelectorVO> listByPage(SelectorQuery selectorQuery);
-
+
/**
* Find by plugin id list.
*
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SelectorServiceImpl.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SelectorServiceImpl.java
index 2772dc37c..2cfe85fa0 100644
---
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SelectorServiceImpl.java
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/SelectorServiceImpl.java
@@ -306,11 +306,16 @@ public class SelectorServiceImpl implements
SelectorService {
@Override
@DataPermission(dataType = AdminConstants.DATA_PERMISSION_SELECTOR)
@Pageable
+ public CommonPager<SelectorVO> listByPageWithPermission(final
SelectorQuery selectorQuery) {
+ return listByPage(selectorQuery);
+ }
+
+ @Override
public CommonPager<SelectorVO> listByPage(final SelectorQuery
selectorQuery) {
return PageResultUtils.result(selectorQuery.getPageParameter(), () ->
selectorMapper.selectByQuery(selectorQuery)
- .stream()
- .map(SelectorVO::buildSelectorVO)
- .collect(Collectors.toList()));
+ .stream()
+ .map(SelectorVO::buildSelectorVO)
+ .collect(Collectors.toList()));
}
@Override
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/DocManager.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/DocManager.java
new file mode 100644
index 000000000..a48e9b84a
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/DocManager.java
@@ -0,0 +1,76 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.service.manager;
+
+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;
+
+/**
+ * Doc Manager.
+ */
+public interface DocManager {
+
+ /**
+ * addDocInfo.
+ *
+ * @param serviceId serviceId
+ * @param docJson docJson
+ * @param callback callback
+ */
+ void addDocInfo(String serviceId, String docJson, Consumer<DocInfo>
callback);
+
+ /**
+ * get docInfo by title.
+ *
+ * @param title title
+ * @return DocInfo
+ */
+ DocInfo getByTitle(String title);
+
+ /**
+ * getDocItem.
+ *
+ * @param id id
+ * @return DocItem
+ */
+ DocItem getDocItem(String id);
+
+ /**
+ * listAll.
+ *
+ * @return Collection
+ */
+ Collection<DocInfo> listAll();
+
+ /**
+ * getDocMd5.
+ *
+ * @param serviceId serviceId
+ * @return String
+ */
+ String getDocMd5(String serviceId);
+
+ /**
+ * remove.
+ *
+ * @param serviceId serviceId
+ */
+ void remove(String serviceId);
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/DocParser.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/DocParser.java
new file mode 100644
index 000000000..fae7d3988
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/DocParser.java
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.service.manager;
+
+import com.alibaba.fastjson.JSONObject;
+import org.apache.shenyu.admin.model.bean.DocInfo;
+
+/**
+ * DocParser.
+ */
+public interface DocParser {
+
+ /**
+ * parseJson.
+ *
+ * @param docRoot docRoot
+ * @return DocInfo DocInfo
+ */
+ DocInfo parseJson(JSONObject docRoot);
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/LoadServiceDocEntry.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/LoadServiceDocEntry.java
new file mode 100644
index 000000000..40ec7bbd4
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/LoadServiceDocEntry.java
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.service.manager;
+
+import java.util.List;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.enums.DataEventTypeEnum;
+
+/**
+ * Load Service Doc Entry.
+ */
+public interface LoadServiceDocEntry {
+
+ /**
+ * pull and save API document.
+ */
+ void loadApiDocument();
+
+ /**
+ * pull and save API document on selector changed.
+ *
+ * @param changedList changedList
+ * @param eventType eventType
+ */
+ void loadDocOnSelectorChanged(List<SelectorData> changedList,
DataEventTypeEnum eventType);
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/ServiceDocManager.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/ServiceDocManager.java
new file mode 100644
index 000000000..d4b5eb68d
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/ServiceDocManager.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.service.manager;
+
+import java.util.Set;
+import org.apache.shenyu.admin.model.bean.UpstreamInstance;
+
+/**
+ * Service document Manager.
+ */
+public interface ServiceDocManager {
+
+ /**
+ * pull API document.
+ * @param currentServices currentServices
+ */
+ void pullApiDocument(Set<UpstreamInstance> currentServices);
+
+ /**
+ * pull API document.
+ * @param instance instance
+ */
+ void pullApiDocument(UpstreamInstance instance);
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/impl/DocManagerImpl.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/impl/DocManagerImpl.java
new file mode 100644
index 000000000..ae1e70d9c
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/impl/DocManagerImpl.java
@@ -0,0 +1,161 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.service.manager.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONObject;
+import com.alibaba.fastjson.parser.Feature;
+import java.nio.charset.StandardCharsets;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.function.Consumer;
+import org.apache.commons.collections4.CollectionUtils;
+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.service.manager.DocManager;
+import org.apache.shenyu.admin.service.manager.DocParser;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.util.DigestUtils;
+
+/**
+ * Doc Manager.
+ */
+@Service
+public class DocManagerImpl implements DocManager {
+ private static final Logger LOG =
LoggerFactory.getLogger(DocManagerImpl.class);
+
+ /**
+ * key:title, value:docInfo.
+ */
+ private static final Map<String, DocInfo> DOC_DEFINITION_MAP = new
HashMap<>();
+
+ /**
+ * KEY:clusterName, value: md5.
+ */
+ private static final Map<String, String> CLUSTER_MD5_MAP = new HashMap<>();
+
+ /**
+ * key: DocItem.id, value: docInfo.
+ */
+ private static final Map<String, DocItem> ITEM_DOC_MAP = new
ConcurrentHashMap<>(256);
+
+ private static final DocParser SWAGGER_DOC_PARSER = new SwaggerDocParser();
+
+ /**
+ * add docInfo.
+ *
+ * @param clusterName clusterName
+ * @param docInfoJson docInfoJson
+ * @param callback callback
+ */
+ @Override
+ public void addDocInfo(final String clusterName, final String docInfoJson,
final Consumer<DocInfo> callback) {
+ String newMd5 =
DigestUtils.md5DigestAsHex(docInfoJson.getBytes(StandardCharsets.UTF_8));
+ String oldMd5 = CLUSTER_MD5_MAP.get(clusterName);
+ if (Objects.equals(newMd5, oldMd5)) {
+ return;
+ }
+ CLUSTER_MD5_MAP.put(clusterName, newMd5);
+ DocInfo docInfo = getDocInfo(clusterName, docInfoJson);
+ if (Objects.isNull(docInfo) ||
CollectionUtils.isEmpty(docInfo.getDocModuleList())) {
+ return;
+ }
+ List<DocModule> docModules = docInfo.getDocModuleList();
+ DOC_DEFINITION_MAP.put(docInfo.getTitle(), docInfo);
+ docModules.forEach(docModule -> {
+ docModule.getDocItems().forEach(docItem -> {
+ ITEM_DOC_MAP.put(docItem.getId(), docItem);
+ });
+ });
+ callback.accept(docInfo);
+ }
+
+ private DocInfo getDocInfo(final String clusterName, final String
docInfoJson) {
+ try {
+ JSONObject docRoot = JSON.parseObject(docInfoJson,
Feature.OrderedField, Feature.DisableCircularReferenceDetect);
+ docRoot.put("basePath", "/" + clusterName);
+ DocInfo docInfo = SWAGGER_DOC_PARSER.parseJson(docRoot);
+ docInfo.setClusterName(clusterName);
+ return docInfo;
+ } catch (Exception e) {
+ LOG.error("getDocInfo error={}", e);
+ return null;
+ }
+ }
+
+ /**
+ * get doc By Title.
+ *
+ * @param title title
+ * @return DocInfo
+ */
+ @Override
+ public DocInfo getByTitle(final String title) {
+ return DOC_DEFINITION_MAP.get(title);
+ }
+
+ /**
+ * getDocItem.
+ *
+ * @param id id
+ * @return DocItem
+ */
+ @Override
+ public DocItem getDocItem(final String id) {
+ return ITEM_DOC_MAP.get(id);
+ }
+
+ /**
+ * get DocInfo.
+ *
+ * @return Collection
+ */
+ @Override
+ public Collection<DocInfo> listAll() {
+ return DOC_DEFINITION_MAP.values();
+ }
+
+ /**
+ * getDocMd5.
+ *
+ * @param clusterName clusterName
+ * @return String
+ */
+ @Override
+ public String getDocMd5(final String clusterName) {
+ return CLUSTER_MD5_MAP.get(clusterName);
+ }
+
+ /**
+ * remove doc.
+ *
+ * @param clusterName clusterName
+ */
+ @Override
+ public void remove(final String clusterName) {
+ CLUSTER_MD5_MAP.remove(clusterName);
+ DOC_DEFINITION_MAP.entrySet().removeIf(entry ->
clusterName.equalsIgnoreCase(entry.getValue().getClusterName()));
+ }
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/impl/LoadServiceDocEntryImpl.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/impl/LoadServiceDocEntryImpl.java
new file mode 100644
index 000000000..6017726e4
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/impl/LoadServiceDocEntryImpl.java
@@ -0,0 +1,198 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.service.manager.impl;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
+import javax.annotation.Resource;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.shenyu.admin.model.bean.UpstreamInstance;
+import org.apache.shenyu.admin.model.page.CommonPager;
+import org.apache.shenyu.admin.model.page.PageParameter;
+import org.apache.shenyu.admin.model.query.SelectorQuery;
+import org.apache.shenyu.admin.model.vo.SelectorVO;
+import org.apache.shenyu.admin.service.SelectorService;
+import org.apache.shenyu.admin.service.manager.LoadServiceDocEntry;
+import org.apache.shenyu.common.dto.SelectorData;
+import org.apache.shenyu.common.dto.convert.selector.CommonUpstream;
+import org.apache.shenyu.common.enums.DataEventTypeEnum;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.apache.shenyu.common.utils.JsonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Load Service Doc Entry.
+ */
+@Service
+public class LoadServiceDocEntryImpl implements LoadServiceDocEntry {
+ private static final Logger LOG =
LoggerFactory.getLogger(LoadServiceDocEntryImpl.class);
+
+ @Resource
+ private SelectorService selectorService;
+
+ @Resource
+ private ServiceDocManagerImpl serviceDocManager;
+
+ @Override
+ public synchronized void loadApiDocument() {
+ List<UpstreamInstance> serviceList =
this.getAllClusterLastUpdateInstanceList();
+ if (CollectionUtils.isEmpty(serviceList)) {
+ LOG.info("loadApiDocument No service registered.");
+ return;
+ }
+ final Set<UpstreamInstance> currentServices = new
HashSet<>(serviceList);
+ LOG.info("loadApiDocument serviceList={}",
JsonUtils.toJson(currentServices));
+ serviceDocManager.pullApiDocument(currentServices);
+ }
+
+ @Override
+ public void loadDocOnSelectorChanged(final List<SelectorData> changedList,
final DataEventTypeEnum eventType) {
+ if (Objects.nonNull(eventType) && (eventType ==
DataEventTypeEnum.CREATE || eventType == DataEventTypeEnum.UPDATE)) {
+ List<UpstreamInstance> serviceList =
this.getLastUpdateInstanceList(changedList);
+ if (CollectionUtils.isEmpty(serviceList)) {
+ LOG.info("loadApiDocument No service registered.");
+ return;
+ }
+ final Set<UpstreamInstance> currentServices = new
HashSet<>(serviceList);
+ LOG.info("loadDocOnSelectorChanged serviceList={}",
JsonUtils.toJson(currentServices));
+ serviceDocManager.pullApiDocument(currentServices);
+ }
+ }
+
+ private List<UpstreamInstance> getLastUpdateInstanceList(final
List<SelectorData> changedList) {
+ if (CollectionUtils.isEmpty(changedList)) {
+ LOG.info("getLastUpdateInstanceList, changedList is empty.");
+ return Collections.EMPTY_LIST;
+ }
+ return changedList.parallelStream()
+ .map(service -> {
+ return getClusterLastUpdateInstance(service);
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ /**
+ * Get the last started healthy instance of each cluster.
+ *
+ * @return List
+ */
+ private List<UpstreamInstance> getAllClusterLastUpdateInstanceList() {
+ List<SelectorVO> clusterList = null;
+ try {
+ CommonPager<SelectorVO> commonPager =
selectorService.listByPage(new SelectorQuery("5", null, new PageParameter(1,
Integer.MAX_VALUE)));
+ clusterList = commonPager.getDataList();
+ } catch (Exception e) {
+ LOG.error("getAllClusterLastUpdateInstanceList fail. error={}", e);
+ }
+ if (CollectionUtils.isEmpty(clusterList)) {
+ LOG.info("getAllClusterLastUpdateInstanceList, Not loaded into
available backend services.");
+ return Collections.EMPTY_LIST;
+ }
+ return clusterList.parallelStream()
+ .map(service -> {
+ return getClusterLastUpdateInstance(service);
+ })
+ .filter(Objects::nonNull)
+ .collect(Collectors.toList());
+ }
+
+ private UpstreamInstance getClusterLastUpdateInstance(final SelectorVO
selectorVO) {
+ List<UpstreamInstance> allInstances = null;
+ // Get service instance.
+ String handle = selectorVO.getHandle();
+ if (StringUtils.isNotEmpty(handle)) {
+ allInstances = new ArrayList<>();
+ try {
+ List<CommonUpstream> upstreamList = this.convert(handle);
+ for (CommonUpstream upstream : upstreamList) {
+ String[] upstreamUrlArr =
upstream.getUpstreamUrl().split(":");
+ UpstreamInstance instance = new UpstreamInstance();
+ instance.setContextPath(selectorVO.getName());
+ instance.setIp(upstreamUrlArr[0]);
+ instance.setPort(Integer.parseInt(upstreamUrlArr[1]));
+ instance.setEnabled(selectorVO.getEnabled());
+ instance.setHealthy(true);
+ instance.setStartupTime(upstream.getTimestamp());
+ allInstances.add(instance);
+ }
+ } catch (Exception e) {
+ LOG.error("Error getting cluster instance list. serviceName={}
error={}", selectorVO.getName(), e);
+ return null;
+ }
+ }
+
+ return getClusterLastUpdateInstance(allInstances);
+ }
+
+ private UpstreamInstance getClusterLastUpdateInstance(final SelectorData
selectorData) {
+ if (!selectorData.getPluginId().equals("5")) {
+ LOG.info("getClusterLastUpdateInstance. pluginNae={} does not
support pulling API documents.", selectorData.getPluginName());
+ return null;
+ }
+ List<UpstreamInstance> allInstances = null;
+ // Get service instance.
+ String handle = selectorData.getHandle();
+ if (StringUtils.isNotEmpty(handle)) {
+ allInstances = new ArrayList<>();
+ try {
+ List<CommonUpstream> upstreamList = this.convert(handle);
+ for (CommonUpstream upstream : upstreamList) {
+ String[] upstreamUrlArr =
upstream.getUpstreamUrl().split(":");
+ UpstreamInstance instance = new UpstreamInstance();
+ instance.setContextPath(selectorData.getName());
+ instance.setIp(upstreamUrlArr[0]);
+ instance.setPort(Integer.parseInt(upstreamUrlArr[1]));
+ instance.setEnabled(selectorData.getEnabled());
+ instance.setHealthy(true);
+ instance.setStartupTime(upstream.getTimestamp());
+ allInstances.add(instance);
+ }
+ } catch (Exception e) {
+ LOG.error("Error getting cluster instance list. serviceName={}
error={}", selectorData.getName(), e);
+ return null;
+ }
+ }
+ return getClusterLastUpdateInstance(allInstances);
+ }
+
+ private UpstreamInstance getClusterLastUpdateInstance(final
List<UpstreamInstance> allInstances) {
+ if (CollectionUtils.isEmpty(allInstances)) {
+ return null;
+ }
+ return allInstances.stream()
+ .filter(UpstreamInstance::isHealthy)
+ .filter(Objects::nonNull)
+ .max(Comparator.comparing(UpstreamInstance::getStartupTime))
+ .orElse(null);
+ }
+
+ private List<CommonUpstream> convert(final String handle) {
+ return GsonUtils.getInstance().fromList(handle, CommonUpstream.class);
+ }
+
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/impl/ServiceDocManagerImpl.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/impl/ServiceDocManagerImpl.java
new file mode 100644
index 000000000..6a802e2b0
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/impl/ServiceDocManagerImpl.java
@@ -0,0 +1,100 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.service.manager.impl;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import javax.annotation.Resource;
+import org.apache.shenyu.admin.model.bean.UpstreamInstance;
+import org.apache.shenyu.admin.service.manager.DocManager;
+import org.apache.shenyu.admin.service.manager.ServiceDocManager;
+import org.apache.shenyu.admin.utils.HttpUtils;
+import org.apache.shenyu.common.utils.JsonUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.stereotype.Service;
+
+/**
+ * ServiceDocManagerImpl.
+ */
+@Service
+public class ServiceDocManagerImpl implements ServiceDocManager {
+ private static final Logger LOG =
LoggerFactory.getLogger(ServiceDocManagerImpl.class);
+
+ private static final HttpUtils HTTP_UTILS = new HttpUtils();
+
+ private static final Map<String, Long> CLUSTER_LASTSTARTUPTIME_MAP = new
HashMap<>();
+
+ private static final String SWAGGER_V2_PATH = "/v2/api-docs";
+
+ @Resource
+ private DocManager docManager;
+
+ @Override
+ public void pullApiDocument(final Set<UpstreamInstance> currentServices) {
+ currentServices.forEach(instance -> {
+ this.pullApiDocument(instance);
+ });
+ }
+
+ /**
+ * pullApiDocument.
+ *
+ * @param instance UpstreamInstance.
+ */
+ @Override
+ public void pullApiDocument(final UpstreamInstance instance) {
+ String clusterName = instance.getClusterName();
+ if (!canPull(instance)) {
+ LOG.info("api document has been pulled and cannot be pulled
againl,instance={}", JsonUtils.toJson(instance));
+ return;
+ }
+ String url = getSwaggerRequestUrl(instance);
+ try {
+ String body = HTTP_UTILS.get(url, Collections.EMPTY_MAP);
+ docManager.addDocInfo(
+ clusterName,
+ body,
+ callback -> {
+ LOG.info("load api document successful,clusterName={},
iPandPort={}",
+ clusterName, instance.getIp() + ":" +
instance.getPort());
+ }
+ );
+ CLUSTER_LASTSTARTUPTIME_MAP.put(clusterName,
instance.getStartupTime());
+ } catch (Exception e) {
+ LOG.error("add api document fail. url={} error={}", url, e);
+ }
+ }
+
+ private boolean canPull(final UpstreamInstance instance) {
+ boolean canPull = false;
+ Long cacheLastStartUpTime =
CLUSTER_LASTSTARTUPTIME_MAP.get(instance.getClusterName());
+ if (cacheLastStartUpTime == null || instance.getStartupTime() >
cacheLastStartUpTime) {
+ canPull = true;
+ }
+ return canPull;
+ }
+
+ private String getSwaggerRequestUrl(final UpstreamInstance instance) {
+ return "http://" + instance.getIp() + ":" + instance.getPort() +
SWAGGER_V2_PATH;
+
+ }
+
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/impl/SwaggerDocParser.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/impl/SwaggerDocParser.java
new file mode 100644
index 000000000..8365eb76b
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/manager/impl/SwaggerDocParser.java
@@ -0,0 +1,324 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.service.manager.impl;
+
+import com.alibaba.fastjson.JSON;
+import com.alibaba.fastjson.JSONArray;
+import com.alibaba.fastjson.JSONObject;
+import com.google.common.collect.Sets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.Set;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import org.apache.commons.lang3.math.NumberUtils;
+import org.apache.shenyu.admin.model.bean.CustomCode;
+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.DocParameter;
+import org.apache.shenyu.admin.service.manager.DocParser;
+import org.springframework.beans.BeanUtils;
+import org.springframework.util.CollectionUtils;
+
+/**
+ * Parse the JSON content of swagger.
+ */
+public class SwaggerDocParser implements DocParser {
+
+ /**
+ * parseJson.
+ *
+ * @param docRoot docRoot
+ * @return DocInfo
+ */
+ @Override
+ public DocInfo parseJson(final JSONObject docRoot) {
+ final String basePath = docRoot.getString("basePath");
+ final String title = docRoot.getJSONObject("info").getString("title");
+ final List<DocItem> docItems = new ArrayList<>();
+
+ JSONObject paths = docRoot.getJSONObject("paths");
+ if (paths == null) {
+ paths = new JSONObject();
+ }
+ Set<String> pathNameSet = paths.keySet();
+ for (String apiPath : pathNameSet) {
+ JSONObject pathInfo = paths.getJSONObject(apiPath);
+ // key: get,post,head...
+ Collection<String> httpMethodList = getHttpMethods(pathInfo);
+ Optional<String> first = httpMethodList.stream().findFirst();
+ if (first.isPresent()) {
+ String method = first.get();
+ JSONObject docInfo = pathInfo.getJSONObject(method);
+ docInfo.putIfAbsent("real_req_path", apiPath);
+ docInfo.put("basePath", basePath);
+ DocItem docItem = buildDocItem(docInfo, docRoot);
+ if (Objects.isNull(docItem)) {
+ continue;
+ }
+ if (docItem.isUploadRequest()) {
+ docItem.setHttpMethodList(Sets.newHashSet("post"));
+ } else {
+ docItem.setHttpMethodList(httpMethodList);
+ }
+ docItems.add(docItem);
+ }
+ }
+
+
docItems.sort(Comparator.comparing(DocItem::getApiOrder).thenComparing(DocItem::getName));
+
+ List<DocModule> docModuleList = docItems.stream()
+ .collect(Collectors.groupingBy(DocItem::getModule))
+ .entrySet()
+ .stream()
+ .map(entry -> {
+ List<DocItem> docItemList = entry.getValue();
+ DocModule docModule = new DocModule();
+ docModule.setModule(entry.getKey());
+ docModule.setDocItems(docItemList);
+ docModule.setOrder(getMuduleOrder(docItemList));
+ return docModule;
+ })
+ .sorted(Comparator.comparing(DocModule::getOrder))
+ .collect(Collectors.toList());
+
+ DocInfo docInfo = new DocInfo();
+ docInfo.setTitle(title);
+ docInfo.setDocModuleList(docModuleList);
+ return docInfo;
+ }
+
+ private int getMuduleOrder(final List<DocItem> items) {
+ if (CollectionUtils.isEmpty(items)) {
+ return Integer.MAX_VALUE;
+ }
+ List<DocItem> docItemList = new ArrayList<>(items);
+ docItemList.sort(Comparator.comparing(DocItem::getModuleOrder));
+ return docItemList.get(0).getModuleOrder();
+ }
+
+ protected Collection<String> getHttpMethods(final JSONObject pathInfo) {
+ // key: get,post,head...
+ List<String> retList;
+ Set<String> httpMethodList = pathInfo.keySet();
+ if (httpMethodList.size() <= 2) {
+ retList = new ArrayList<>(httpMethodList);
+ } else {
+// Set<String> ignoreHttpMethods =
DocParserContext.ignoreHttpMethods;
+// retList = httpMethodList.stream()
+// .filter(method ->
!ignoreHttpMethods.contains(method.toLowerCase()))
+// .collect(Collectors.toList());
+ retList = new ArrayList<>(httpMethodList);
+ }
+ Collections.sort(retList);
+ return retList;
+ }
+
+ protected DocItem buildDocItem(final JSONObject docInfo, final JSONObject
docRoot) {
+ String apiName = docInfo.getString("real_req_path");
+ String basePath = docInfo.getString("basePath");
+ apiName = basePath + apiName;
+
+ DocItem docItem = new DocItem();
+ docItem.setId(UUID.randomUUID().toString());
+ docItem.setName(apiName);
+ docItem.setSummary(docInfo.getString("summary"));
+ docItem.setDescription(docInfo.getString("description"));
+
docItem.setProduces(docInfo.getJSONArray("produces").toJavaList(String.class));
+ docItem.setMultiple(docInfo.getString("multiple") != null);
+ String apiResponseStr = docInfo.getString("apiResponse");
+ if (apiResponseStr != null) {
+ docItem.setBizCodeList(JSON.parseArray(apiResponseStr,
CustomCode.class));
+ }
+
docItem.setModuleOrder(NumberUtils.toInt(docInfo.getString("module_order"), 0));
+ docItem.setApiOrder(NumberUtils.toInt(docInfo.getString("api_order"),
0));
+ String moduleName = this.buildModuleName(docInfo, docRoot);
+ docItem.setModule(moduleName);
+ List<DocParameter> docParameterList =
this.buildRequestParameterList(docInfo, docRoot);
+ docItem.setRequestParameters(docParameterList);
+
+ List<DocParameter> responseParameterList =
this.buildResponseParameterList(docInfo, docRoot);
+ docItem.setResponseParameters(responseParameterList);
+ return docItem;
+ }
+
+ protected String buildModuleName(final JSONObject docInfo, final
JSONObject docRoot) {
+ String title = docRoot.getJSONObject("info").getString("title");
+ JSONArray tags = docInfo.getJSONArray("tags");
+ if (Objects.nonNull(tags) && tags.size() > 0) {
+ return tags.getString(0);
+ }
+ return title;
+ }
+
+ protected List<DocParameter> buildRequestParameterList(final JSONObject
docInfo, final JSONObject docRoot) {
+ Optional<JSONArray> parametersOptional =
Optional.ofNullable(docInfo.getJSONArray("parameters"));
+ JSONArray parameters = parametersOptional.orElse(new JSONArray());
+ List<DocParameter> docParameterList = new ArrayList<>();
+ for (int i = 0; i < parameters.size(); i++) {
+ JSONObject fieldJson = parameters.getJSONObject(i);
+ JSONObject schema = fieldJson.getJSONObject("schema");
+ if (Objects.nonNull(schema)) {
+ RefInfo refInfo = getRefInfo(schema);
+ if (Objects.nonNull(refInfo)) {
+ List<DocParameter> parameterList =
this.buildDocParameters(refInfo.ref, docRoot, true);
+ docParameterList.addAll(parameterList);
+ }
+ } else {
+ DocParameter docParameter =
fieldJson.toJavaObject(DocParameter.class);
+ docParameterList.add(docParameter);
+ }
+ }
+
+ Map<String, List<DocParameter>> collect = docParameterList.stream()
+ .filter(docParameter -> docParameter.getName().contains("."))
+ .map(docParameter -> {
+ String name = docParameter.getName();
+ int index = name.indexOf('.');
+ String module = name.substring(0, index);
+ String newName = name.substring(index + 1);
+ DocParameter ret = new DocParameter();
+ BeanUtils.copyProperties(docParameter, ret);
+ ret.setName(newName);
+ ret.setModule(module);
+ return ret;
+ })
+ .collect(Collectors.groupingBy(DocParameter::getModule));
+
+ collect.forEach((key, value) -> {
+ DocParameter moduleDoc = new DocParameter();
+ moduleDoc.setName(key);
+ moduleDoc.setType("object");
+ moduleDoc.setRefs(value);
+ docParameterList.add(moduleDoc);
+ });
+
+ return docParameterList.stream()
+ .filter(docParameter -> !docParameter.getName().contains("."))
+ .collect(Collectors.toList());
+ }
+
+ protected List<DocParameter> buildResponseParameterList(final JSONObject
docInfo, final JSONObject docRoot) {
+ RefInfo refInfo = getResponseRefInfo(docInfo);
+ List<DocParameter> respParameterList = Collections.emptyList();
+ if (refInfo != null) {
+ String responseRef = refInfo.ref;
+ respParameterList = this.buildDocParameters(responseRef, docRoot,
true);
+ // If an array is returned.
+ if (refInfo.isArray) {
+ DocParameter docParameter = new DocParameter();
+ docParameter.setName("items");
+ docParameter.setType("array");
+ docParameter.setRefs(respParameterList);
+ respParameterList = Collections.singletonList(docParameter);
+ }
+ }
+ return respParameterList;
+ }
+
+ protected List<DocParameter> buildDocParameters(final String ref, final
JSONObject docRoot, final boolean doSubRef) {
+ JSONObject responseObject =
docRoot.getJSONObject("definitions").getJSONObject(ref);
+ String className = responseObject.getString("title");
+ JSONObject extProperties = docRoot.getJSONObject(className);
+ JSONArray requiredProperties = responseObject.getJSONArray("required");
+ JSONObject properties = responseObject.getJSONObject("properties");
+ List<DocParameter> docParameterList = new ArrayList<>();
+ if (Objects.isNull(properties)) {
+ return docParameterList;
+ }
+ Set<String> fieldNames = properties.keySet();
+ for (String fieldName : fieldNames) {
+ JSONObject fieldInfo = properties.getJSONObject(fieldName);
+ DocParameter docParameter =
fieldInfo.toJavaObject(DocParameter.class);
+ docParameter.setName(fieldName);
+ docParameter.setRequired(
+ !CollectionUtils.isEmpty(requiredProperties) &&
requiredProperties.contains(fieldName));
+ if (Objects.nonNull(extProperties)) {
+ JSONObject prop = extProperties.getJSONObject(fieldName);
+ if (Objects.nonNull(prop)) {
+ String maxLength = prop.getString("maxLength");
+ docParameter.setMaxLength(Objects.isNull(maxLength) ? "-"
: maxLength);
+ String required = prop.getString("required");
+ if (Objects.nonNull(required)) {
+
docParameter.setRequired(Boolean.parseBoolean(required));
+ }
+ }
+ }
+ docParameterList.add(docParameter);
+ RefInfo refInfo = this.getRefInfo(fieldInfo);
+ if (Objects.nonNull(refInfo) && doSubRef) {
+ String subRef = refInfo.ref;
+ boolean nextDoRef = !Objects.equals(ref, subRef);
+ List<DocParameter> refs = buildDocParameters(subRef, docRoot,
nextDoRef);
+ docParameter.setRefs(refs);
+ }
+ }
+ return docParameterList;
+ }
+
+ /**
+ * Simple object return, pure array return.
+
+ * @param docInfo docInfo
+ * @return RefInfo
+ */
+ protected RefInfo getResponseRefInfo(final JSONObject docInfo) {
+ return Optional.ofNullable(docInfo.getJSONObject("responses"))
+ .flatMap(jsonObject ->
Optional.ofNullable(jsonObject.getJSONObject("200")))
+ .flatMap(jsonObject ->
Optional.ofNullable(jsonObject.getJSONObject("schema")))
+ .map(this::getRefInfo)
+ .orElse(null);
+ }
+
+ private RefInfo getRefInfo(final JSONObject jsonObject) {
+ String ref;
+ boolean isArray = "array".equals(jsonObject.getString("type"));
+ if (isArray) {
+ ref = jsonObject.getJSONObject("items").getString("$ref");
+ } else {
+ // #/definitions/xxx
+ ref = jsonObject.getString("$ref");
+ }
+ if (Objects.isNull(ref)) {
+ return null;
+ }
+ int index = ref.lastIndexOf("/");
+ if (index > -1) {
+ ref = ref.substring(index + 1);
+ }
+ RefInfo refInfo = new RefInfo();
+ refInfo.isArray = isArray;
+ refInfo.ref = ref;
+ return refInfo;
+ }
+
+ private static class RefInfo {
+
+ private boolean isArray;
+
+ private String ref;
+ }
+
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/HttpUtils.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/HttpUtils.java
new file mode 100644
index 000000000..1c01647e1
--- /dev/null
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/HttpUtils.java
@@ -0,0 +1,649 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.utils;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileNotFoundException;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.Serializable;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import okhttp3.Cookie;
+import okhttp3.CookieJar;
+import okhttp3.FormBody;
+import okhttp3.HttpUrl;
+import okhttp3.MediaType;
+import okhttp3.MultipartBody;
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+import okhttp3.RequestBody;
+import okhttp3.Response;
+import okhttp3.ResponseBody;
+import org.apache.commons.codec.digest.DigestUtils;
+
+/**
+ * HTTP request tool, based on okhttp3.
+ */
+public class HttpUtils {
+ private static final MediaType MEDIA_TYPE_JSON =
MediaType.parse("application/json; charset=utf-8");
+
+ private Map<String, List<Cookie>> cookieStore = new HashMap<String,
List<Cookie>>();
+
+ private OkHttpClient httpClient;
+
+ /**
+ * HttpUtils.
+ */
+ public HttpUtils() {
+ this(new HttpToolConfig());
+ }
+
+ /**
+ * HttpUtils.
+ *
+ * @param httpToolConfig httpToolConfig
+ */
+ public HttpUtils(final HttpToolConfig httpToolConfig) {
+ this.initHttpClient(httpToolConfig);
+ }
+
+ /**
+ * buildRequestBuilder.
+ *
+ * @param url url
+ * @param form form
+ * @param method method
+ * @return Request
+ */
+ public static Request.Builder buildRequestBuilder(final String url, final
Map<String, ?> form,
+ final HTTPMethod method) {
+ switch (method) {
+ case GET:
+ return new Request.Builder()
+ .url(buildHttpUrl(url, form))
+ .get();
+ case HEAD:
+ return new Request.Builder()
+ .url(buildHttpUrl(url, form))
+ .head();
+ case PUT:
+ return new Request.Builder()
+ .url(url)
+ .put(buildFormBody(form));
+ case DELETE:
+ return new Request.Builder()
+ .url(url)
+ .delete(buildFormBody(form));
+ default:
+ return new Request.Builder()
+ .url(url)
+ .post(buildFormBody(form));
+ }
+ }
+
+ /**
+ * buildHttpUrl.
+ *
+ * @param url url
+ * @param form form
+ * @return HttpUrl
+ */
+ public static HttpUrl buildHttpUrl(final String url, final Map<String, ?>
form) {
+ HttpUrl.Builder urlBuilder = HttpUrl.parse(url).newBuilder();
+ for (Map.Entry<String, ?> entry : form.entrySet()) {
+ urlBuilder.addQueryParameter(entry.getKey(),
String.valueOf(entry.getValue()));
+ }
+ return urlBuilder.build();
+ }
+
+ /**
+ * buildFormBody.
+ *
+ * @param form form
+ * @return FormBody
+ */
+ public static FormBody buildFormBody(final Map<String, ?> form) {
+ FormBody.Builder paramBuilder = new
FormBody.Builder(StandardCharsets.UTF_8);
+ for (Map.Entry<String, ?> entry : form.entrySet()) {
+ paramBuilder.add(entry.getKey(), String.valueOf(entry.getValue()));
+ }
+ return paramBuilder.build();
+ }
+
+ protected void initHttpClient(final HttpToolConfig httpToolConfig) {
+ httpClient = new OkHttpClient.Builder()
+ .connectTimeout(httpToolConfig.connectTimeoutSeconds,
TimeUnit.SECONDS)
+ .readTimeout(httpToolConfig.readTimeoutSeconds, TimeUnit.SECONDS)
+ .writeTimeout(httpToolConfig.writeTimeoutSeconds, TimeUnit.SECONDS)
+ .cookieJar(new CookieJar() {
+ @Override
+ public void saveFromResponse(final HttpUrl httpUrl, final
List<Cookie> list) {
+ cookieStore.put(httpUrl.host(), list);
+ }
+
+ @Override
+ public List<Cookie> loadForRequest(final HttpUrl httpUrl) {
+ List<Cookie> cookies = cookieStore.get(httpUrl.host());
+ return cookies != null ? cookies : new ArrayList<Cookie>();
+ }
+ }).build();
+ }
+
+ /**
+ * get request.
+ *
+ * @param url url
+ * @param header header
+ * @return String
+ * @throws IOException IOException
+ */
+ public String get(final String url, final Map<String, String> header)
throws IOException {
+ Request.Builder builder = new Request.Builder().url(url).get();
+ addHeader(builder, header);
+
+ Request request = builder.build();
+ Response response = httpClient.newCall(request).execute();
+ return response.body().string();
+ }
+
+ /**
+ * Submit the form.
+ *
+ * @param url url
+ * @param form param
+ * @param header header
+ * @param method http method
+ * @return String
+ * @throws IOException IOException
+ */
+ public String request(final String url, final Map<String, ?> form, final
Map<String, String> header,
+ final HTTPMethod method) throws IOException {
+ Request.Builder requestBuilder = buildRequestBuilder(url, form,
method);
+ addHeader(requestBuilder, header);
+
+ Request request = requestBuilder.build();
+ Response response = httpClient
+ .newCall(request)
+ .execute();
+ try {
+ return response.body().string();
+ } finally {
+ response.close();
+ }
+ }
+
+ /**
+ * request json data,contentType=application/json.
+ *
+ * @param url url
+ * @param json json
+ * @param header header
+ * @return String
+ * @throws IOException IOException
+ */
+ public String requestJson(final String url, final String json,
+ final Map<String, String> header) throws IOException {
+ RequestBody body = RequestBody.create(MEDIA_TYPE_JSON, json);
+ Request.Builder requestBuilder = new Request.Builder()
+ .url(url)
+ .post(body);
+ addHeader(requestBuilder, header);
+
+ Request request = requestBuilder.build();
+ Response response = httpClient
+ .newCall(request)
+ .execute();
+ try {
+ return response.body().string();
+ } finally {
+ response.close();
+ }
+ }
+
+ /**
+ * requestFileString.
+ *
+ * @param url url
+ * @param form form
+ * @param header header
+ * @param files files
+ * @return String
+ * @throws IOException IOException
+ */
+ public String requestFileString(final String url, final Map<String, ?>
form, final Map<String, String> header,
+ final List<UploadFile> files) throws IOException {
+ return requestFile(url, form, header, files).body().string();
+ }
+
+ /**
+ * Submit the form and upload the file.
+ *
+ * @param url url
+ * @param form form
+ * @param header header
+ * @param files files
+ * @return Response
+ * @throws IOException IOException
+ */
+ public Response requestFile(final String url, final Map<String, ?> form,
final Map<String, String> header,
+ final List<UploadFile> files)
+ throws IOException {
+ MultipartBody.Builder bodyBuilder = new MultipartBody.Builder();
+ bodyBuilder.setType(MultipartBody.FORM);
+
+ for (UploadFile uploadFile : files) {
+ bodyBuilder.addFormDataPart(uploadFile.getName(),
+ // The name of the file, which is used by the server for
parsing.
+ uploadFile.getFileName(),
+ // Create the requestbody and put the uploaded file into the.
+ RequestBody.create(null, uploadFile.getFileData())
+ );
+ }
+
+ for (Map.Entry<String, ?> entry : form.entrySet()) {
+ bodyBuilder.addFormDataPart(entry.getKey(),
String.valueOf(entry.getValue()));
+ }
+
+ RequestBody requestBody = bodyBuilder.build();
+ Request.Builder builder = new
Request.Builder().url(url).post(requestBody);
+ addHeader(builder, header);
+
+ Request request = builder.build();
+ return httpClient.newCall(request).execute();
+ }
+
+ /**
+ * request.
+ *
+ * @param url url
+ * @param form form
+ * @param header header
+ * @param method method
+ * @param files files
+ * @return Response
+ * @throws IOException IOException
+ */
+ public Response requestCall(final String url, final Map<String, ?> form,
final Map<String, String> header,
+ final HTTPMethod method, final List<UploadFile> files) throws
IOException {
+ if (Objects.nonNull(files) && files.size() > 0) {
+ return requestFile(url, form, header, files);
+ } else {
+ return requestForResponse(url, form, header, method);
+ }
+ }
+
+ /**
+ * request data.
+ *
+ * @param url request url
+ * @param form request data
+ * @param header header
+ * @param method method
+ * @return Response Response
+ * @throws IOException IOException
+ */
+ public Response requestForResponse(final String url, final Map<String, ?>
form, final Map<String, String> header,
+ final HTTPMethod method) throws IOException {
+ Request.Builder requestBuilder = buildRequestBuilder(url, form,
method);
+ addHeader(requestBuilder, header);
+ Request request = requestBuilder.build();
+ return httpClient
+ .newCall(request)
+ .execute();
+ }
+
+ /**
+ * download file.
+ *
+ * @param url request url
+ * @param form request data
+ * @param header header
+ * @return InputStream
+ * @throws IOException IOException
+ */
+ public InputStream downloadFile(final String url, final Map<String, ?>
form,
+ final Map<String, String> header) throws IOException {
+ Request.Builder requestBuilder = buildRequestBuilder(url, form,
HTTPMethod.GET);
+ addHeader(requestBuilder, header);
+
+ Request request = requestBuilder.build();
+ Response response = httpClient
+ .newCall(request)
+ .execute();
+ if (response.isSuccessful()) {
+ ResponseBody body = response.body();
+ return body == null ? null : body.byteStream();
+ }
+ return null;
+ }
+
+ /**
+ * setCookieStore.
+ *
+ * @param cookieStore cookieStore
+ */
+ public void setCookieStore(final Map<String, List<Cookie>> cookieStore) {
+ this.cookieStore = cookieStore;
+ }
+
+ /**
+ * setHttpClient.
+ *
+ * @param httpClient httpClient
+ */
+ public void setHttpClient(final OkHttpClient httpClient) {
+ this.httpClient = httpClient;
+ }
+
+ private void addHeader(final Request.Builder builder, final Map<String,
String> header) {
+ if (header != null) {
+ Set<Map.Entry<String, String>> entrySet = header.entrySet();
+ for (Map.Entry<String, String> entry : entrySet) {
+ builder.addHeader(entry.getKey(),
String.valueOf(entry.getValue()));
+ }
+ }
+ }
+
+ public enum HTTPMethod {
+ GET,
+ POST,
+ PUT,
+ HEAD,
+ DELETE;
+
+ HTTPMethod() {
+ }
+
+ /**
+ * fromValue.
+ *
+ * @param v v
+ * @return HTTPMethod
+ */
+ public static HTTPMethod fromValue(final String v) {
+ return valueOf(v.toUpperCase());
+ }
+
+ /**
+ * value().
+ *
+ * @return String
+ */
+ public String value() {
+ return this.name();
+ }
+ }
+
+ public static class HttpToolConfig {
+
+ private int connectTimeoutSeconds = 10;
+
+ private int readTimeoutSeconds = 10;
+
+ private int writeTimeoutSeconds = 10;
+
+ /**
+ * Request timeout.
+ *
+ * @return int
+ */
+ public int getConnectTimeoutSeconds() {
+ return connectTimeoutSeconds;
+ }
+
+ /**
+ * setConnectTimeoutSeconds.
+ *
+ * @param connectTimeoutSeconds connectTimeoutSeconds
+ */
+ public void setConnectTimeoutSeconds(final int connectTimeoutSeconds) {
+ this.connectTimeoutSeconds = connectTimeoutSeconds;
+ }
+
+ /**
+ * HTTP read timeout.
+ *
+ * @return int
+ */
+ public int getReadTimeoutSeconds() {
+ return readTimeoutSeconds;
+ }
+
+ /**
+ * setReadTimeoutSeconds.
+ *
+ * @param readTimeoutSeconds readTimeoutSeconds
+ */
+ public void setReadTimeoutSeconds(final int readTimeoutSeconds) {
+ this.readTimeoutSeconds = readTimeoutSeconds;
+ }
+
+ /**
+ * HTTP write timeout.
+ *
+ * @return int
+ */
+ public int getWriteTimeoutSeconds() {
+ return writeTimeoutSeconds;
+ }
+
+ /**
+ * setWriteTimeoutSeconds.
+ *
+ * @param writeTimeoutSeconds writeTimeoutSeconds
+ */
+ public void setWriteTimeoutSeconds(final int writeTimeoutSeconds) {
+ this.writeTimeoutSeconds = writeTimeoutSeconds;
+ }
+ }
+
+ /**
+ * Upload File bean.
+ */
+ public static class UploadFile implements Serializable {
+
+ private static final long serialVersionUID = -1100614660944996398L;
+
+ private String name;
+
+ private String fileName;
+
+ private byte[] fileData;
+
+ private String md5;
+
+ /**
+ * Upload File.
+ * @param name The form name cannot be duplicate.
+ * @param file file
+ * @throws IOException IOException
+ */
+ public UploadFile(final String name, final File file) throws
IOException {
+ this(name, file.getName(), FileUtils.toBytes(file));
+ }
+
+ /**
+ * UploadFile.
+ *
+ * @param name The form name cannot be duplicate.
+ * @param fileName fileName
+ * @param input inputStream
+ * @throws IOException IOException
+ */
+ public UploadFile(final String name, final String fileName, final
InputStream input) throws IOException {
+ this(name, fileName, FileUtils.toBytes(input));
+ }
+
+ /**
+ * The form name cannot be duplicate.
+ *
+ * @param name The form name cannot be duplicate.
+ * @param fileName fileName
+ * @param fileData fileData
+ */
+ public UploadFile(final String name, final String fileName, final
byte[] fileData) {
+ super();
+ this.name = name;
+ this.fileName = fileName;
+ this.fileData = fileData;
+ this.md5 = DigestUtils.md5Hex(fileData);
+ }
+
+ /**
+ * getName.
+ *
+ * @return String
+ */
+ public String getName() {
+ return name;
+ }
+
+ /**
+ * setName.
+ *
+ * @param name name
+ */
+ public void setName(final String name) {
+ this.name = name;
+ }
+
+ /**
+ * getFileName.
+ *
+ * @return String
+ */
+ public String getFileName() {
+ return fileName;
+ }
+
+ /**
+ * setFileName.
+ *
+ * @param fileName fileName
+ */
+ public void setFileName(final String fileName) {
+ this.fileName = fileName;
+ }
+
+ /**
+ * getFileData.
+ *
+ * @return byte[]
+ */
+ public byte[] getFileData() {
+ return fileData;
+ }
+
+ /**
+ * setFileData.
+ *
+ * @param fileData fileData
+ */
+ public void setFileData(final byte[] fileData) {
+ this.fileData = fileData;
+ }
+
+ /**
+ * getMd5.
+ *
+ * @return String
+ */
+ public String getMd5() {
+ return md5;
+ }
+
+ /**
+ * setMd5.
+ *
+ * @param md5 md5
+ */
+ public void setMd5(final String md5) {
+ this.md5 = md5;
+ }
+ }
+
+ public static class FileUtils {
+
+ /**
+ * The default buffer size to use.
+ */
+ private static final int DEFAULT_BUFFER_SIZE = 1024 * 4;
+
+ private static final int EOF = -1;
+
+ /**
+ * InputStream to byte[].
+ *
+ * @param input input
+ * @return byte
+ * @throws IOException IOException
+ */
+ public static byte[] toBytes(final InputStream input) throws
IOException {
+ ByteArrayOutputStream output = new ByteArrayOutputStream();
+ int n = 0;
+ byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];
+
+ while (EOF != (n = input.read(buffer))) {
+ output.write(buffer, 0, n);
+ }
+ return output.toByteArray();
+ }
+
+ /**
+ * file to bytes.
+ *
+ * @param file file
+ * @return byte
+ * @throws IOException IOException
+ */
+ public static byte[] toBytes(final File file) throws IOException {
+ if (file.exists()) {
+ if (file.isDirectory()) {
+ throw new IOException("File '" + file + "' exists but is a
directory");
+ }
+ if (!file.canRead()) {
+ throw new IOException("File '" + file + "' cannot be
read");
+ }
+ } else {
+ throw new FileNotFoundException("File '" + file + "' does not
exist");
+ }
+ InputStream input = null;
+ try {
+ input = new FileInputStream(file);
+ return toBytes(input);
+ } finally {
+ try {
+ if (input != null) {
+ input.close();
+ }
+ } catch (IOException ioe) {
+ System.err.println(ioe);
+ }
+ }
+ }
+ }
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/ShenyuSignatureUtils.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/ShenyuSignatureUtils.java
new file mode 100644
index 000000000..45a21636b
--- /dev/null
+++
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/ShenyuSignatureUtils.java
@@ -0,0 +1,71 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.utils;
+
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+import org.springframework.util.DigestUtils;
+
+/**
+ * Shenyu Signature tool.
+ */
+public class ShenyuSignatureUtils {
+
+ /**
+ * At present, it is positioned as 1.0.0 write dead, string type.
+ */
+ public static final String VERSION = "1.0.0";
+
+ /**
+ * generate Sign.
+ * @param sign sign
+ * @return String
+ */
+ public static String generateSign(final String sign) {
+ return DigestUtils.md5DigestAsHex(sign.getBytes()).toUpperCase();
+ }
+
+ /**
+ * getSignContent.
+ * @param secureKey secureKey
+ * @param timestamp timestamp
+ * @param path path
+ * @return String
+ */
+ public static String getSignContent(final String secureKey, final String
timestamp, final String path) {
+ Map<String, String> map = new HashMap<>(3);
+ map.put("timestamp", timestamp);
+ map.put("path", path);
+ map.put("version", VERSION);
+
+ List<String> storedKeys = Arrays.stream(map.keySet()
+ .toArray(new String[] {}))
+ .sorted(Comparator.naturalOrder())
+ .collect(Collectors.toList());
+ final String signContent = storedKeys.stream()
+ .map(key -> String.join("", key, map.get(key)))
+ .collect(Collectors.joining()).trim()
+ .concat(secureKey);
+ return signContent;
+ }
+
+}
diff --git
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/UploadUtils.java
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/UploadUtils.java
new file mode 100644
index 000000000..61571115c
--- /dev/null
+++ b/shenyu-admin/src/main/java/org/apache/shenyu/admin/utils/UploadUtils.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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 org.apache.shenyu.admin.utils;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import javax.servlet.http.HttpServletRequest;
+import org.springframework.util.MultiValueMap;
+import org.springframework.web.multipart.MultipartFile;
+import org.springframework.web.multipart.MultipartHttpServletRequest;
+
+/**
+ * File upload tool.
+ */
+public class UploadUtils {
+
+ /**
+ * getUploadFiles.
+ *
+ * @param request request
+ * @return Collection
+ */
+ public static Collection<MultipartFile> getUploadFiles(final
HttpServletRequest request) {
+ MultiValueMap<String, MultipartFile> fileMap = null;
+ //Check whether there is:enctype="multipart/form-data" in the form.
+ String contentType = request.getContentType();
+ if (contentType != null &&
contentType.toLowerCase().contains("multipart")) {
+ //Change the request into a multipart request.
+ MultipartHttpServletRequest multiRequest =
(MultipartHttpServletRequest) request;
+ fileMap = multiRequest.getMultiFileMap();
+ }
+ return Optional.ofNullable(fileMap)
+ .map(Map::entrySet)
+ .map(entry -> entry.stream()
+ .flatMap(e -> e.getValue().stream())
+ .collect(Collectors.toList()))
+ .orElse(Collections.emptyList());
+ }
+}
diff --git a/shenyu-admin/src/main/resources/application.yml
b/shenyu-admin/src/main/resources/application.yml
index 5db569a48..2544c86a5 100644
--- a/shenyu-admin/src/main/resources/application.yml
+++ b/shenyu-admin/src/main/resources/application.yml
@@ -94,6 +94,15 @@ shenyu:
- /csrf
swagger:
enable: true
+ apidoc:
+ gatewayUrl: http://127.0.0.1:9195
+ envProps:
+ - envLabel: Test environment
+ addressLabel: Request Address
+ addressUrl: http://127.0.0.1:9195
+ - envLabel: Prod environment
+ addressLabel: Request Address
+ addressUrl: http://127.0.0.1:9195
logging:
level:
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/controller/SelectorControllerTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/controller/SelectorControllerTest.java
index 3329486ab..7e3d0ee1b 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/controller/SelectorControllerTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/controller/SelectorControllerTest.java
@@ -94,7 +94,7 @@ public final class SelectorControllerTest {
@Test
public void querySelectors() throws Exception {
-
given(this.selectorService.listByPage(selectorQuery)).willReturn(commonPager);
+
given(this.selectorService.listByPageWithPermission(selectorQuery)).willReturn(commonPager);
String urlTemplate =
"/selector?pluginId={pluginId}&name={name}¤tPage={currentPage}&pageSize={pageSize}";
this.mockMvc.perform(MockMvcRequestBuilders.get(urlTemplate, "2",
"selector-1", 1, 12))
.andExpect(status().isOk())
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcherTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcherTest.java
index 2e5a8a49c..9d72d2400 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcherTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/listener/DataChangedEventDispatcherTest.java
@@ -21,6 +21,7 @@ import
org.apache.shenyu.admin.listener.http.HttpLongPollingDataChangedListener;
import org.apache.shenyu.admin.listener.nacos.NacosDataChangedListener;
import org.apache.shenyu.admin.listener.websocket.WebsocketDataChangedListener;
import org.apache.shenyu.admin.listener.zookeeper.ZookeeperDataChangedListener;
+import org.apache.shenyu.admin.service.manager.LoadServiceDocEntry;
import org.apache.shenyu.common.enums.ConfigGroupEnum;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -69,6 +70,9 @@ public final class DataChangedEventDispatcherTest {
@Mock
private ZookeeperDataChangedListener zookeeperDataChangedListener;
+ @Mock
+ private LoadServiceDocEntry loadServiceDocEntry;
+
@BeforeEach
public void setUp() {
Map<String, DataChangedListener> listenerMap = new HashMap<>();
@@ -77,6 +81,9 @@ public final class DataChangedEventDispatcherTest {
listenerMap.put("websocketDataChangedListener",
websocketDataChangedListener);
listenerMap.put("zookeeperDataChangedListener",
zookeeperDataChangedListener);
when(applicationContext.getBeansOfType(DataChangedListener.class)).thenReturn(listenerMap);
+
+
when(applicationContext.getBean(LoadServiceDocEntry.class)).thenReturn(loadServiceDocEntry);
+ applicationContext.getBean(LoadServiceDocEntry.class);
dataChangedEventDispatcher.afterPropertiesSet();
}
diff --git
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SelectorServiceTest.java
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SelectorServiceTest.java
index 0de94846e..c229381c9 100644
---
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SelectorServiceTest.java
+++
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/SelectorServiceTest.java
@@ -189,7 +189,7 @@ public final class SelectorServiceTest {
final List<SelectorDO> selectorDOs = buildSelectorDOList();
given(this.selectorMapper.selectByQuery(any())).willReturn(selectorDOs);
SelectorQuery params = buildSelectorQuery();
- final CommonPager<SelectorVO> result =
this.selectorService.listByPage(params);
+ final CommonPager<SelectorVO> result =
this.selectorService.listByPageWithPermission(params);
assertThat(result, notNullValue());
assertEquals(selectorDOs.size(), result.getDataList().size());
}
diff --git a/shenyu-dist/shenyu-admin-dist/src/main/release-docs/LICENSE
b/shenyu-dist/shenyu-admin-dist/src/main/release-docs/LICENSE
index 9eba26333..866d96319 100644
--- a/shenyu-dist/shenyu-admin-dist/src/main/release-docs/LICENSE
+++ b/shenyu-dist/shenyu-admin-dist/src/main/release-docs/LICENSE
@@ -224,11 +224,13 @@ The text of each license is the standard Apache 2.0
license.
commons-beanutils 1.9.4: https://github.com/apache/commons-beanutils,
Apache 2.0
commons-codec 1.13: https://github.com/apache/commons-codec, Apache 2.0
commons-collections4 4.1: https://github.com/apache/commons-collections,
Apache 2.0
+ commons-io 2.11.0:https://commons.apache.org/proper/commons-io, Apache 2.0
commons-lang3 3.3.2: https://github.com/apache/commons-lang, Apache 2.0
disruptor 3.4.0: https://github.com/LMAX-Exchange/disruptor , Apache 2.0
error_prone_annotations 2.5.1: https://github.com/google/error-prone,
Apache 2.0
failsafe 2.3.3: https://github.com/jhalterman/failsafe, Apache 2.0
failureaccess 1.0.1:https://github.com/google/guava, Apache 2.0
+ fastjson 1.2.80: https://github.com/alibaba/fastjson, Apache 2.0
grpc-all 1.33.1 https://github.com/grpc/grpc, Apache 2.0
grpc-context 1.27.1: https://github.com/grpc/grpc, Apache 2.0
grpc-core 1.9.1: https://github.com/grpc/grpc, Apache 2.0