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}&currentPage={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

Reply via email to