This is an automated email from the ASF dual-hosted git repository.

Aias00 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/shenyu.git


The following commit(s) were added to refs/heads/master by this push:
     new ca83c6974d Fix RocketMQ logging e2e bootstrap startup (#6379)
ca83c6974d is described below

commit ca83c6974d69e96c63f5a1d42272e9dcc738fd2b
Author: aias00 <[email protected]>
AuthorDate: Fri Jun 12 14:53:29 2026 +0800

    Fix RocketMQ logging e2e bootstrap startup (#6379)
    
    * feat(api-doc): support request parameter parsing for RPC types 
(Dubbo/SOFA/TARS/gRPC)
    
    * fix license
    
    * fix checkstyle exclude generated source
    
    * fix checkstyle
    
    * fix review
    
    * Fix Protobuf field schema parsing logic
    
    * fix use common version
    
    * chore: trigger CI retest
    
    * Recover RocketMQ e2e bootstrap after delayed admin health
    
    The RocketMQ logging e2e script runs three sync modes in sequence. In the 
zookeeper round, Docker Compose can return before shenyu-admin has recovered 
from an initial unhealthy state, leaving shenyu-bootstrap created but not 
started. The script previously ignored both that compose failure and later 
healthcheck failures, so Maven reported the gateway as unavailable instead of 
recovering the service stack first.
    
    Constraint: CI builds the required latest Docker images before this script 
runs
    
    Rejected: Increase fixed sleeps only | it would hide the dependency failure 
without ensuring bootstrap starts
    
    Confidence: medium
    
    Scope-risk: narrow
    
    Directive: Keep bootstrap startup gated on admin health for this e2e script
    
    Tested: bash -n e2e-logging-rocketmq-compose.sh; e2e RocketMQ test-compile; 
RocketMQ logging plugin test-compile; docker compose config for zookeeper, 
RocketMQ, and HTTP example compose files; git diff --check
    
    Not-tested: Full Docker Compose e2e locally because CI-built 
apache/shenyu-admin:latest, apache/shenyu-bootstrap:latest, and 
shenyu-examples-http:latest images are not present
    
    ---------
    
    Co-authored-by: eye-gu <[email protected]>
---
 pom.xml                                            |   4 +-
 .../shenyu/admin/service/impl/ApiServiceImpl.java  |  14 +-
 .../shenyu/admin/service/ApiServiceTest.java       |  54 ++
 shenyu-client/shenyu-client-core/pom.xml           |  50 ++
 .../AbstractContextRefreshedEventListener.java     |  14 +-
 .../registrar/AbstractApiDocRegistrar.java         |  12 +-
 .../register/registrar/ApiDocRegistrarImpl.java    |  13 +-
 .../shenyu/client/core/utils/OpenApiUtils.java     | 834 ++++++++++++++++++---
 .../registrar/ApiDocRegistrarImplTest.java         | 263 +++++++
 .../registrar/NoHttpApiDocRegistrarTest.java       | 276 ++++++-
 .../shenyu/client/core/utils/OpenApiUtilsTest.java | 519 +++++++++++++
 .../shenyu-client-core/src/test/proto/test.proto   |  41 +
 .../client/sofa/SofaServiceEventListener.java      |  77 +-
 .../client/sofa/SofaServiceEventListenerTest.java  |   3 +-
 .../compose/script/e2e-logging-rocketmq-compose.sh |  12 +-
 .../annotation/impl/DubboProtobufServiceImpl.java  |   6 +
 .../service/xml/impl/DubboProtobufServiceImpl.java |   6 +
 .../service/impl/DubboProtobufServiceImpl.java     |   6 +
 18 files changed, 2002 insertions(+), 202 deletions(-)

diff --git a/pom.xml b/pom.xml
index 7fdbee3836..fb743a5a6e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -103,6 +103,8 @@
         <maven-shade-plugin.version>3.5.1</maven-shade-plugin.version>
         <docker-maven-plugin.version>0.40.1</docker-maven-plugin.version>
         <maven-assembly-plugin.version>3.5.0</maven-assembly-plugin.version>
+        <protobuf-maven-plugin.version>0.6.1</protobuf-maven-plugin.version>
+        <os-maven-plugin.version>1.6.2</os-maven-plugin.version>
         <!-- maven plugin version end -->
 
         <!-- dependency version start -->
@@ -735,7 +737,7 @@
                     
<configLocation>/script/shenyu_checkstyle.xml</configLocation>
                     
<headerLocation>/script/checkstyle-header.txt</headerLocation>
                     
<includeTestSourceDirectory>true</includeTestSourceDirectory>
-                    <excludes>**/transfer/**/*</excludes>
+                    <excludes>**/transfer/**/*,**/generated*/**/*</excludes>
                 </configuration>
                 <executions>
                     <execution>
diff --git 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ApiServiceImpl.java
 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ApiServiceImpl.java
index 979e6d7e1a..ad05dbc788 100644
--- 
a/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ApiServiceImpl.java
+++ 
b/shenyu-admin/src/main/java/org/apache/shenyu/admin/service/impl/ApiServiceImpl.java
@@ -21,6 +21,7 @@ import com.google.common.collect.Lists;
 import java.util.ArrayList;
 import org.apache.commons.collections4.CollectionUtils;
 import org.apache.commons.lang3.StringUtils;
+import java.util.Objects;
 import 
org.apache.shenyu.admin.disruptor.RegisterClientServerDisruptorPublisher;
 import org.apache.shenyu.admin.mapper.ApiMapper;
 import org.apache.shenyu.admin.mapper.TagMapper;
@@ -40,7 +41,6 @@ import org.apache.shenyu.admin.model.vo.ApiVO;
 import org.apache.shenyu.admin.model.vo.RuleVO;
 import org.apache.shenyu.admin.model.vo.TagVO;
 import org.apache.shenyu.admin.service.ApiService;
-import org.apache.shenyu.common.enums.ApiSourceEnum;
 import org.apache.shenyu.common.enums.PluginEnum;
 import org.apache.shenyu.common.utils.JsonUtils;
 import org.apache.shenyu.common.utils.ListUtil;
@@ -243,12 +243,14 @@ public class ApiServiceImpl implements ApiService {
                 tagVOs = 
tagDOS.stream().map(TagVO::buildTagVO).collect(Collectors.toList());
             }
             ApiVO apiVO = ApiVO.buildApiVO(item, tagVOs);
-            if (apiVO.getApiSource().equals(ApiSourceEnum.SWAGGER.getValue())) 
{
+            if (StringUtils.isNotBlank(apiVO.getDocument())) {
                 DocItem docItem = JsonUtils.jsonToObject(apiVO.getDocument(), 
DocItem.class);
-                apiVO.setRequestHeaders(docItem.getRequestHeaders());
-                apiVO.setRequestParameters(docItem.getRequestParameters());
-                apiVO.setResponseParameters(docItem.getResponseParameters());
-                apiVO.setBizCustomCodeList(docItem.getBizCodeList());
+                if (Objects.nonNull(docItem)) {
+                    apiVO.setRequestHeaders(docItem.getRequestHeaders());
+                    apiVO.setRequestParameters(docItem.getRequestParameters());
+                    
apiVO.setResponseParameters(docItem.getResponseParameters());
+                    apiVO.setBizCustomCodeList(docItem.getBizCodeList());
+                }
             }
             return apiVO;
 
diff --git 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ApiServiceTest.java
 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ApiServiceTest.java
index dd7b719e76..7ffc2c87b0 100644
--- 
a/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ApiServiceTest.java
+++ 
b/shenyu-admin/src/test/java/org/apache/shenyu/admin/service/ApiServiceTest.java
@@ -108,6 +108,60 @@ public final class ApiServiceTest {
         assertNotNull(byId);
     }
 
+    @Test
+    public void testFindByIdWithDocumentNotBlank() {
+        String id = "456";
+        ApiDTO apiDTO = new ApiDTO();
+        apiDTO.setId(id);
+        apiDTO.setContextPath("string");
+        apiDTO.setApiPath("string");
+        apiDTO.setHttpMethod(0);
+        apiDTO.setConsume("string");
+        apiDTO.setProduce("string");
+        apiDTO.setVersion("string");
+        apiDTO.setRpcType("string");
+        apiDTO.setState(0);
+        apiDTO.setApiOwner("string");
+        apiDTO.setApiDesc("string");
+        apiDTO.setApiSource(0);
+        
apiDTO.setDocument("{\"module\":\"test-module\",\"requestParameters\":[],\"responseParameters\":[]}");
+        ApiDO apiDO = ApiDO.buildApiDO(apiDTO);
+        Timestamp now = Timestamp.valueOf(LocalDateTime.now());
+        apiDO.setDateCreated(now);
+        apiDO.setDateUpdated(now);
+        given(this.apiMapper.selectByPrimaryKey(eq(id))).willReturn(apiDO);
+        ApiVO byId = this.apiService.findById(id);
+        assertNotNull(byId);
+        assertNotNull(byId.getRequestParameters());
+        assertNotNull(byId.getResponseParameters());
+    }
+
+    @Test
+    public void testFindByIdWithBlankDocument() {
+        String id = "789";
+        ApiDTO apiDTO = new ApiDTO();
+        apiDTO.setId(id);
+        apiDTO.setContextPath("string");
+        apiDTO.setApiPath("string");
+        apiDTO.setHttpMethod(0);
+        apiDTO.setConsume("string");
+        apiDTO.setProduce("string");
+        apiDTO.setVersion("string");
+        apiDTO.setRpcType("string");
+        apiDTO.setState(0);
+        apiDTO.setApiOwner("string");
+        apiDTO.setApiDesc("string");
+        apiDTO.setApiSource(0);
+        apiDTO.setDocument("");
+        ApiDO apiDO = ApiDO.buildApiDO(apiDTO);
+        Timestamp now = Timestamp.valueOf(LocalDateTime.now());
+        apiDO.setDateCreated(now);
+        apiDO.setDateUpdated(now);
+        given(this.apiMapper.selectByPrimaryKey(eq(id))).willReturn(apiDO);
+        ApiVO byId = this.apiService.findById(id);
+        assertNotNull(byId);
+    }
+
     @Test
     public void testListByPage() {
         PageParameter pageParameter = new PageParameter();
diff --git a/shenyu-client/shenyu-client-core/pom.xml 
b/shenyu-client/shenyu-client-core/pom.xml
index 683afdcc7c..c6e70b6d32 100644
--- a/shenyu-client/shenyu-client-core/pom.xml
+++ b/shenyu-client/shenyu-client-core/pom.xml
@@ -81,5 +81,55 @@
             <artifactId>spring-boot-starter-tomcat</artifactId>
             <scope>test</scope>
         </dependency>
+        <dependency>
+            <groupId>com.google.protobuf</groupId>
+            <artifactId>protobuf-java</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>io.grpc</groupId>
+            <artifactId>grpc-stub</artifactId>
+            <scope>test</scope>
+        </dependency>
     </dependencies>
+
+    <build>
+        <extensions>
+            <extension>
+                <groupId>kr.motd.maven</groupId>
+                <artifactId>os-maven-plugin</artifactId>
+                <version>${os-maven-plugin.version}</version>
+            </extension>
+        </extensions>
+        <plugins>
+            <plugin>
+                <groupId>org.xolstice.maven.plugins</groupId>
+                <artifactId>protobuf-maven-plugin</artifactId>
+                <version>${protobuf-maven-plugin.version}</version>
+                <extensions>true</extensions>
+                <configuration>
+                    
<protocArtifact>com.google.protobuf:protoc:${protobuf-java.version}:exe:${os.detected.classifier}</protocArtifact>
+                    
<protoSourceRoot>${project.basedir}/src/test/proto</protoSourceRoot>
+                    
<outputDirectory>${project.build.directory}/generated-test-sources/protobuf/java</outputDirectory>
+                    <clearOutputDirectory>false</clearOutputDirectory>
+                </configuration>
+                <executions>
+                    <execution>
+                        <goals>
+                            <goal>test-compile</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+            <plugin>
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-checkstyle-plugin</artifactId>
+                <configuration>
+                    <testSourceDirectories>
+                        
<testSourceDirectory>${project.basedir}/src/test/java</testSourceDirectory>
+                    </testSourceDirectories>
+                </configuration>
+            </plugin>
+        </plugins>
+    </build>
 </project>
diff --git 
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/client/AbstractContextRefreshedEventListener.java
 
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/client/AbstractContextRefreshedEventListener.java
index 32ef07fcda..1aef7389b7 100644
--- 
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/client/AbstractContextRefreshedEventListener.java
+++ 
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/client/AbstractContextRefreshedEventListener.java
@@ -17,7 +17,6 @@
 
 package org.apache.shenyu.client.core.client;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import org.apache.commons.collections4.MapUtils;
 import org.apache.commons.lang3.StringUtils;
@@ -213,7 +212,7 @@ public abstract class 
AbstractContextRefreshedEventListener<T, A extends Annotat
             String apiPath = pathJoin(contextPath, superPath, value);
             ApiHttpMethodEnum[] value3 = sextet.getValue3();
             for (ApiHttpMethodEnum apiHttpMethodEnum : value3) {
-                String documentJson = buildDocumentJson(pairs.getRight(), 
apiPath, method);
+                String documentJson = buildDocumentJson(pairs.getRight(), 
apiPath, method, sextet.getValue4());
                 String extJson = buildExtJson(method);
                 ApiDocRegisterDTO build = ApiDocRegisterDTO.builder()
                         .consume(sextet.getValue1())
@@ -258,15 +257,8 @@ public abstract class 
AbstractContextRefreshedEventListener<T, A extends Annotat
         return ext;
     }
 
-    private String buildDocumentJson(final List<String> tags, final String 
path, final Method method) {
-        Map<String, Object> documentMap = ImmutableMap.<String, 
Object>builder()
-                .put("tags", tags)
-                .put("operationId", path)
-                .put("parameters", 
OpenApiUtils.generateDocumentParameters(path, method))
-                .put("responses", OpenApiUtils.generateDocumentResponse(path))
-                .put("responseType", 
Collections.singletonList(OpenApiUtils.parseReturnType(method)))
-                .build();
-        return GsonUtils.getInstance().toJson(documentMap);
+    private String buildDocumentJson(final List<String> tags, final String 
path, final Method method, final RpcTypeEnum rpcTypeEnum) {
+        return OpenApiUtils.buildDocumentJson(tags, path, method, rpcTypeEnum);
     }
 
     protected abstract Sextet<String[], String, String, ApiHttpMethodEnum[], 
RpcTypeEnum, String> buildApiDocSextet(Method method, Annotation annotation, 
Map<String, T> beans);
diff --git 
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/AbstractApiDocRegistrar.java
 
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/AbstractApiDocRegistrar.java
index 6d2e5358b3..30ddb88286 100644
--- 
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/AbstractApiDocRegistrar.java
+++ 
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/AbstractApiDocRegistrar.java
@@ -17,7 +17,6 @@
 
 package org.apache.shenyu.client.core.register.registrar;
 
-import com.google.common.collect.ImmutableMap;
 import com.google.common.collect.Lists;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.shenyu.client.apidocs.annotations.ApiDoc;
@@ -42,9 +41,7 @@ import org.apache.shenyu.register.common.enums.EventType;
 import java.lang.reflect.Method;
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 
 public abstract class AbstractApiDocRegistrar extends 
AbstractApiRegistrar<ApiDocRegisterDTO> {
     
@@ -128,14 +125,7 @@ public abstract class AbstractApiDocRegistrar extends 
AbstractApiRegistrar<ApiDo
     }
     
     private String buildDocumentJson(final List<String> tags, final String 
path, final Method method) {
-        Map<String, Object> documentMap = ImmutableMap.<String, 
Object>builder()
-                .put("tags", tags)
-                .put("operationId", path)
-                .put("parameters", 
OpenApiUtils.generateDocumentParameters(path, method))
-                .put("responses", OpenApiUtils.generateDocumentResponse(path))
-                .put("responseType", 
Collections.singletonList(OpenApiUtils.parseReturnType(method)))
-                .build();
-        return GsonUtils.getInstance().toJson(documentMap);
+        return OpenApiUtils.buildDocumentJson(tags, path, method, rpcTypeEnum);
     }
     
     private String buildExtJson(final ApiBean.ApiDefinition apiDefinition) {
diff --git 
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/ApiDocRegistrarImpl.java
 
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/ApiDocRegistrarImpl.java
index 91b842f97f..10d07877a4 100644
--- 
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/ApiDocRegistrarImpl.java
+++ 
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/register/registrar/ApiDocRegistrarImpl.java
@@ -17,7 +17,6 @@
 
 package org.apache.shenyu.client.core.register.registrar;
 
-import com.google.common.collect.ImmutableMap;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.shenyu.client.core.constant.ShenyuClientConstants;
 import 
org.apache.shenyu.client.core.disruptor.ShenyuClientRegisterEventPublisher;
@@ -38,7 +37,6 @@ import org.jetbrains.annotations.NotNull;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
-import java.util.Map;
 import java.util.Objects;
 import java.util.stream.Collectors;
 
@@ -132,14 +130,9 @@ public class ApiDocRegistrarImpl extends 
BaseApiRegistrarImpl {
             return document;
         }
         final String path = getPath(api);
-        final Map<String, Object> documentMap = ImmutableMap.<String, 
Object>builder()
-                .put("tags", buildTags(api))
-                .put("operationId", path)
-                .put("parameters", 
OpenApiUtils.generateDocumentParameters(path, api.getApiMethod()))
-                .put("responses", OpenApiUtils.generateDocumentResponse(path))
-                .put("responseType", 
Collections.singletonList(OpenApiUtils.parseReturnType(api.getApiMethod())))
-                .build();
-        return GsonUtils.getInstance().toJson(documentMap);
+        final String rpcType = getRpcType(api);
+        RpcTypeEnum rpcTypeEnum = RpcTypeEnum.acquireByName(rpcType);
+        return OpenApiUtils.buildDocumentJson(buildTags(api), path, 
api.getApiMethod(), rpcTypeEnum);
     }
     
     private String getRpcType(final ApiBean.ApiDefinition api) {
diff --git 
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/utils/OpenApiUtils.java
 
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/utils/OpenApiUtils.java
index 5affbce77a..d74ba59b69 100644
--- 
a/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/utils/OpenApiUtils.java
+++ 
b/shenyu-client/shenyu-client-core/src/main/java/org/apache/shenyu/client/core/utils/OpenApiUtils.java
@@ -21,6 +21,8 @@ import com.google.common.collect.ImmutableMap;
 import org.apache.commons.lang3.StringUtils;
 import org.apache.commons.lang3.tuple.Pair;
 import org.apache.shenyu.client.core.constant.ShenyuClientConstants;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.apache.shenyu.common.utils.GsonUtils;
 import org.springframework.web.bind.annotation.RequestParam;
 import org.springframework.web.bind.annotation.RequestPart;
 
@@ -37,6 +39,7 @@ import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.LocalTime;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
@@ -58,16 +61,73 @@ public class OpenApiUtils {
 
     private static final String[] QUERY_CLASSES = new 
String[]{"org.springframework.web.bind.annotation.RequestParam", 
"org.springframework.web.bind.annotation.RequestPart"};
 
+    /**
+     * Check if the given RPC type uses Spring MVC parameter parsing.
+     * HTTP, WebSocket and Spring Cloud types use Spring MVC annotations for 
parameter resolution,
+     * while other RPC types (Dubbo, gRPC, etc.) parse parameters from Java 
method signatures directly.
+     *
+     * @param rpcTypeEnum the RPC type enum
+     * @return true if Spring MVC parameter parsing should be used
+     */
+    public static boolean useSpringMvcParamParsing(final RpcTypeEnum 
rpcTypeEnum) {
+        return rpcTypeEnum == RpcTypeEnum.HTTP
+                || rpcTypeEnum == RpcTypeEnum.WEB_SOCKET
+                || rpcTypeEnum == RpcTypeEnum.SPRING_CLOUD;
+    }
 
     /**
-     * generateDocumentParameters.
+     * Build document JSON string for the given API method.
+     * Dispatches to the appropriate parameter/response generation based on 
RPC type.
+     *
+     * @param tags       the API tags
+     * @param path       the API path
+     * @param method     the Java method
+     * @param rpcTypeEnum the RPC type
+     * @return document JSON string
+     */
+    public static String buildDocumentJson(final List<String> tags, final 
String path,
+                                           final Method method, final 
RpcTypeEnum rpcTypeEnum) {
+        boolean useSpringMvcParamParsing = 
useSpringMvcParamParsing(rpcTypeEnum);
+        Map<String, Object> documentMap;
+        if (useSpringMvcParamParsing) {
+            documentMap = ImmutableMap.<String, Object>builder()
+                    .put("tags", tags)
+                    .put("operationId", path)
+                    .put("requestParameters", 
generateRequestDocParameters(path, method))
+                    .put("responseParameters", 
Collections.singletonList(parseReturnType(method)))
+                    .put("responses", generateDocumentResponse(path))
+                    .build();
+        } else if (rpcTypeEnum == RpcTypeEnum.GRPC) {
+            documentMap = ImmutableMap.<String, Object>builder()
+                    .put("tags", tags)
+                    .put("operationId", path)
+                    .put("requestParameters", 
generateGrpcRequestDocParameters(method))
+                    .put("responseParameters", 
Collections.singletonList(parseGrpcReturnType(method)))
+                    .put("responses", generateGrpcDocumentResponse(path, 
method))
+                    .build();
+        } else {
+            documentMap = ImmutableMap.<String, Object>builder()
+                    .put("tags", tags)
+                    .put("operationId", path)
+                    .put("requestParameters", 
generateRpcRequestDocParameters(method))
+                    .put("responseParameters", 
Collections.singletonList(parseReturnType(method)))
+                    .put("responses", generateRpcDocumentResponse(path, 
method))
+                    .build();
+        }
+        return GsonUtils.getInstance().toJson(documentMap);
+    }
+
+
+    /**
+     * Generate request parameters for HTTP methods.
+     * This produces Parameter objects with name, in, type, and refs fields.
      *
      * @param path   the api path
      * @param method the method
-     * @return documentParameters
+     * @return request parameters
      */
-    public static List<Parameter> generateDocumentParameters(final String 
path, final Method method) {
-        ArrayList<Parameter> list = new ArrayList<>();
+    public static List<Parameter> generateRequestDocParameters(final String 
path, final Method method) {
+        List<Parameter> list = new ArrayList<>();
         Pair<Boolean, Annotation[][]> query = isQuery(method);
         if (query.getLeft()) {
             for (Annotation[] annotations : query.getRight()) {
@@ -89,34 +149,182 @@ public class OpenApiUtils {
                         parameter.setIn("query");
                         parameter.setRequired(required);
                         parameter.setName(name);
-                        parameter.setSchema(new Schema("string", null));
+                        parameter.setType("string");
                         list.add(parameter);
                     }
                 }
             }
-        } else {
-            List<String> segments = UrlPathUtils.getSegments(path);
-            for (String segment : segments) {
-                if (EVERY_PATH.equals(segment)) {
-                    Parameter parameter = new Parameter();
-                    parameter.setIn("path");
-                    parameter.setName(segment);
-                    parameter.setRequired(true);
-                    parameter.setSchema(new Schema("string", null));
-                    list.add(parameter);
+        }
+        List<String> segments = UrlPathUtils.getSegments(path);
+        for (String segment : segments) {
+            if (EVERY_PATH.equals(segment)) {
+                Parameter parameter = new Parameter();
+                parameter.setIn("path");
+                parameter.setName(segment);
+                parameter.setRequired(true);
+                parameter.setType("string");
+                list.add(parameter);
+            }
+            if (segment.startsWith(LEFT_ANGLE_BRACKETS) && 
segment.endsWith(RIGHT_ANGLE_BRACKETS)) {
+                String name = segment.substring(1, segment.length() - 1);
+                Parameter parameter = new Parameter();
+                parameter.setIn("path");
+                parameter.setName(name);
+                parameter.setRequired(true);
+                parameter.setType("string");
+                list.add(parameter);
+            }
+        }
+        return list;
+    }
+
+    /**
+     * Generate request parameters for RPC methods.
+     * Unlike HTTP methods that use Spring annotations, RPC method parameters
+     * are parsed from Java method parameter types directly.
+
+     * @param method the method
+     * @return request parameters
+     */
+    public static List<Parameter> generateRpcRequestDocParameters(final Method 
method) {
+        List<Parameter> list = new ArrayList<>();
+        java.lang.reflect.Parameter[] methodParams = method.getParameters();
+        for (java.lang.reflect.Parameter methodParam : methodParams) {
+            Type paramType = methodParam.getParameterizedType();
+            Schema schema = parseSchema(paramType, 0, new HashMap<>(16));
+            Parameter parameter = 
convertSchemaToParameter(methodParam.getName(), schema);
+            parameter.setRequired(true);
+            list.add(parameter);
+        }
+        return list;
+    }
+
+    /**
+     * Generate request parameters for gRPC methods.
+     * gRPC method signatures differ from other RPC types:
+     * - Unary/ServerStreaming: void method(Request req, 
StreamObserver{Response} observer)
+     * - ClientStreaming/BidiStreaming: StreamObserver{Request} 
method(StreamObserver{Response} observer)
+     * StreamObserver parameters are excluded from request parameters.
+     *
+     * @param method the method
+     * @return request parameters
+     */
+    public static List<Parameter> generateGrpcRequestDocParameters(final 
Method method) {
+        List<Parameter> list = new ArrayList<>();
+        java.lang.reflect.Parameter[] methodParams = method.getParameters();
+        for (java.lang.reflect.Parameter methodParam : methodParams) {
+            if (isStreamObserver(methodParam.getType())) {
+                continue;
+            }
+            Type paramType = methodParam.getParameterizedType();
+            Schema schema = parseSchema(paramType, 0, new HashMap<>(16));
+            Parameter parameter = 
convertSchemaToParameter(methodParam.getName(), schema);
+            parameter.setRequired(true);
+            list.add(parameter);
+        }
+        if (list.isEmpty()) {
+            Type returnType = method.getGenericReturnType();
+            Type actualType = extractStreamObserverTypeParam(returnType);
+            if (Objects.nonNull(actualType)) {
+                Schema schema = parseSchema(actualType, 0, new HashMap<>(16));
+                Parameter parameter = convertSchemaToParameter("request", 
schema);
+                parameter.setRequired(true);
+                list.add(parameter);
+            }
+        }
+        return list;
+    }
+
+    private static Parameter convertSchemaToParameter(final String name, final 
Schema schema) {
+        Parameter parameter = new Parameter();
+        parameter.setName(name);
+        parameter.setType(schema.getType());
+        if (Objects.nonNull(schema.getRefs()) && !schema.getRefs().isEmpty()) {
+            List<Parameter> refs = new ArrayList<>();
+            for (Schema ref : schema.getRefs()) {
+                refs.add(convertSchemaToParameter(ref.getName(), ref));
+            }
+            parameter.setRefs(refs);
+        }
+        return parameter;
+    }
+
+    /**
+     * Parse return type for gRPC methods.
+     * - Unary/ServerStreaming: response type is extracted from 
StreamObserver{Response} parameter
+     * - ClientStreaming/BidiStreaming: response type is extracted from 
StreamObserver{Response} parameter
+     *
+     * @param method the method
+     * @return response type
+     */
+    public static ResponseType parseGrpcReturnType(final Method method) {
+        java.lang.reflect.Parameter[] methodParams = method.getParameters();
+        for (java.lang.reflect.Parameter methodParam : methodParams) {
+            if (isStreamObserver(methodParam.getType())) {
+                Type paramType = methodParam.getParameterizedType();
+                Type actualType = extractStreamObserverTypeParam(paramType);
+                if (Objects.nonNull(actualType)) {
+                    return parseType("ROOT", actualType, 0, new HashMap<>(16));
                 }
-                if (segment.startsWith(LEFT_ANGLE_BRACKETS) && 
segment.endsWith(RIGHT_ANGLE_BRACKETS)) {
-                    String name = segment.substring(1, segment.length() - 1);
-                    Parameter parameter = new Parameter();
-                    parameter.setIn("path");
-                    parameter.setName(name);
-                    parameter.setRequired(true);
-                    parameter.setSchema(new Schema("string", null));
-                    list.add(parameter);
+            }
+        }
+        ResponseType voidType = new ResponseType();
+        voidType.setName("ROOT");
+        voidType.setType("void");
+        return voidType;
+    }
+
+    /**
+     * Generate document response for gRPC methods.
+     *
+     * @param path   the api path
+     * @param method the method
+     * @return documentResponseMap
+     */
+    public static Map<String, Object> generateGrpcDocumentResponse(final 
String path, final Method method) {
+        String returnTypeStr = "void";
+        java.lang.reflect.Parameter[] methodParams = method.getParameters();
+        for (java.lang.reflect.Parameter methodParam : methodParams) {
+            if (isStreamObserver(methodParam.getType())) {
+                Type paramType = methodParam.getParameterizedType();
+                Type actualType = extractStreamObserverTypeParam(paramType);
+                if (Objects.nonNull(actualType)) {
+                    returnTypeStr = resolveTypeName(actualType);
                 }
+                break;
             }
         }
-        return list;
+        ImmutableMap<Object, Object> contentMap = ImmutableMap.builder()
+                .put(ShenyuClientConstants.MEDIA_TYPE_ALL_VALUE, 
ImmutableMap.of("schema", ImmutableMap.of("type", returnTypeStr)))
+                .build();
+        ImmutableMap<Object, Object> successMap = ImmutableMap.builder()
+                .put("description", path)
+                .put("content", contentMap).build();
+        ImmutableMap<Object, Object> notFoundMap = ImmutableMap.builder()
+                .put("description", StringUtils.join("the path [", path, "] 
not found"))
+                .put("content", 
ImmutableMap.of(ShenyuClientConstants.MEDIA_TYPE_ALL_VALUE,
+                        ImmutableMap.of("schema", ImmutableMap.of("type", 
"string")))).build();
+        return ImmutableMap.<String, Object>builder()
+                .put("200", successMap)
+                .put("404", notFoundMap)
+                .build();
+    }
+
+    private static boolean isStreamObserver(final Class<?> clazz) {
+        return "io.grpc.stub.StreamObserver".equals(clazz.getName());
+    }
+
+    private static Type extractStreamObserverTypeParam(final Type type) {
+        if (type instanceof ParameterizedType) {
+            ParameterizedType pt = (ParameterizedType) type;
+            if ("io.grpc.stub.StreamObserver".equals(((Class<?>) 
pt.getRawType()).getName())) {
+                Type[] actualTypes = pt.getActualTypeArguments();
+                if (actualTypes.length > 0) {
+                    return actualTypes[0];
+                }
+            }
+        }
+        return null;
     }
 
     private static Pair<Boolean, Annotation[][]> isQuery(final Method method) {
@@ -125,7 +333,6 @@ public class OpenApiUtils {
             if (parameterAnnotation.length > 0 && 
isQueryName(parameterAnnotation[0].annotationType().getName(), QUERY_CLASSES)) {
                 return Pair.of(true, parameterAnnotations);
             }
-            return Pair.of(false, null);
         }
         return Pair.of(false, null);
     }
@@ -139,6 +346,248 @@ public class OpenApiUtils {
         return false;
     }
 
+    /**
+     * Generate document response for RPC methods with actual return type info.
+     *
+     * @param path   the api path
+     * @param method the method
+     * @return documentResponseMap
+     */
+    public static Map<String, Object> generateRpcDocumentResponse(final String 
path, final Method method) {
+        Type returnType = method.getGenericReturnType();
+        String returnTypeStr = "void".equals(returnType.getTypeName()) ? 
"void" : resolveTypeName(returnType);
+        ImmutableMap<Object, Object> contentMap = ImmutableMap.builder()
+                .put(ShenyuClientConstants.MEDIA_TYPE_ALL_VALUE, 
ImmutableMap.of("schema", ImmutableMap.of("type", returnTypeStr)))
+                .build();
+        ImmutableMap<Object, Object> successMap = ImmutableMap.builder()
+                .put("description", path)
+                .put("content", contentMap).build();
+        ImmutableMap<Object, Object> notFoundMap = ImmutableMap.builder()
+                .put("description", StringUtils.join("the path [", path, "] 
not found"))
+                .put("content", 
ImmutableMap.of(ShenyuClientConstants.MEDIA_TYPE_ALL_VALUE,
+                        ImmutableMap.of("schema", ImmutableMap.of("type", 
"string")))).build();
+        return ImmutableMap.<String, Object>builder()
+                .put("200", successMap)
+                .put("404", notFoundMap)
+                .build();
+    }
+
+    private static String resolveTypeName(final Type type) {
+        if (type instanceof Class) {
+            return resolveTypeName((Class<?>) type);
+        }
+        if (type instanceof ParameterizedType) {
+            Class<?> rawType = (Class<?>) ((ParameterizedType) 
type).getRawType();
+            if (Collection.class.isAssignableFrom(rawType)) {
+                return "array";
+            }
+            return "object";
+        }
+        return "object";
+    }
+
+    private static String resolveTypeName(final Class<?> clazz) {
+        if (isBooleanType(clazz)) {
+            return "boolean";
+        } else if (isIntegerType(clazz)) {
+            return "integer";
+        } else if (isNumberType(clazz)) {
+            return "number";
+        } else if (isStringType(clazz)) {
+            return "string";
+        } else if (isDateType(clazz)) {
+            return "string";
+        } else if (clazz.isArray() || 
Collection.class.isAssignableFrom(clazz)) {
+            return "array";
+        } else if (clazz.isEnum()) {
+            return "string";
+        } else if (isProtobufMessage(clazz)) {
+            return "object";
+        } else if (Map.class.isAssignableFrom(clazz)) {
+            return "object";
+        } else {
+            return "object";
+        }
+    }
+
+    /**
+     * Check if the class is a protobuf-generated message class.
+     * Uses reflection to avoid direct protobuf dependency.
+     *
+     * @param clazz the class to check
+     * @return true if it's a protobuf message class
+     */
+    static boolean isProtobufMessage(final Class<?> clazz) {
+        if (Objects.isNull(clazz)) {
+            return false;
+        }
+        Class<?> current = clazz;
+        while (Objects.nonNull(current) && current != Object.class) {
+            String className = current.getName();
+            if ("com.google.protobuf.GeneratedMessageV3".equals(className)
+                    || "com.google.protobuf.GeneratedMessage".equals(className)
+                    || 
"com.google.protobuf.GeneratedMessageLite".equals(className)) {
+                return true;
+            }
+            current = current.getSuperclass();
+        }
+        return false;
+    }
+
+    /**
+     * Check if the class is com.google.protobuf.Empty.
+     *
+     * @param clazz the class to check
+     * @return true if it's protobuf Empty
+     */
+    private static boolean isProtobufEmpty(final Class<?> clazz) {
+        return Objects.nonNull(clazz) && 
"com.google.protobuf.Empty".equals(clazz.getName());
+    }
+
+    /**
+     * Parse protobuf message fields using reflection on getDescriptor().
+     * Protobuf descriptor types are mapped to OpenAPI types.
+     *
+     * @param responseType    the response type to populate
+     * @param clazz           the protobuf message class
+     * @param depth           current recursion depth
+     * @param typeVariableMap type variable map
+     * @return populated ResponseType
+     */
+    private static ResponseType parseProtobufClass(final ResponseType 
responseType, final Class<?> clazz,
+                                                   final int depth, final 
Map<TypeVariable<?>, Type> typeVariableMap) {
+        if (isProtobufEmpty(clazz)) {
+            responseType.setType("object");
+            return responseType;
+        }
+        try {
+            java.lang.reflect.Method getDescriptorMethod = 
clazz.getMethod("getDescriptor");
+            Object descriptor = getDescriptorMethod.invoke(null);
+            java.lang.reflect.Method getFieldsMethod = 
descriptor.getClass().getMethod("getFields");
+            @SuppressWarnings("unchecked")
+            List<?> fields = (List<?>) getFieldsMethod.invoke(descriptor);
+            List<ResponseType> refs = parseProtobufFields(fields, clazz, 
depth, typeVariableMap);
+            responseType.setType("object");
+            responseType.setRefs(refs);
+        } catch (Exception e) {
+            responseType.setType("object");
+        }
+        return responseType;
+    }
+
+    private static List<ResponseType> parseProtobufFields(final List<?> 
fields, final Class<?> clazz,
+                                                          final int depth, 
final Map<TypeVariable<?>, Type> typeVariableMap) throws Exception {
+        List<ResponseType> refs = new ArrayList<>();
+        java.lang.reflect.Method getNameMethod = null;
+        java.lang.reflect.Method getTypeMethod = null;
+        java.lang.reflect.Method isRepeatedMethod = null;
+        java.lang.reflect.Method getMessageTypeMethod = null;
+        for (Object field : fields) {
+            if (Objects.isNull(getNameMethod)) {
+                getNameMethod = field.getClass().getMethod("getName");
+                getTypeMethod = field.getClass().getMethod("getType");
+                isRepeatedMethod = field.getClass().getMethod("isRepeated");
+                getMessageTypeMethod = 
field.getClass().getMethod("getMessageType");
+            }
+            String fieldName = (String) getNameMethod.invoke(field);
+            Object fieldType = getTypeMethod.invoke(field);
+            boolean isRepeated = (boolean) isRepeatedMethod.invoke(field);
+            refs.add(parseProtobufField(fieldName, fieldType, isRepeated,
+                    getMessageTypeMethod, field, clazz, depth, 
typeVariableMap));
+        }
+        return refs;
+    }
+
+    private static ResponseType parseProtobufField(final String fieldName, 
final Object fieldType, final boolean isRepeated,
+                                                   final 
java.lang.reflect.Method getMessageTypeMethod, final Object field,
+                                                   final Class<?> clazz, final 
int depth,
+                                                   final Map<TypeVariable<?>, 
Type> typeVariableMap) throws Exception {
+        if (isRepeated) {
+            return parseRepeatedProtobufField(fieldName, fieldType, 
getMessageTypeMethod, field);
+        }
+        String fieldTypeName = fieldType.toString();
+        ResponseType fieldResponse = new ResponseType();
+        fieldResponse.setName(fieldName);
+        if ("MESSAGE".equals(fieldTypeName)) {
+            resolveProtobufMessageField(fieldResponse, getMessageTypeMethod, 
field, clazz, depth, typeVariableMap);
+        } else if ("ENUM".equals(fieldTypeName)) {
+            fieldResponse.setType("string");
+        } else {
+            fieldResponse.setType(mapProtobufTypeToOpenApi(fieldTypeName));
+        }
+        return fieldResponse;
+    }
+
+    private static ResponseType parseRepeatedProtobufField(final String 
fieldName, final Object fieldType,
+                                                           final 
java.lang.reflect.Method getMessageTypeMethod,
+                                                           final Object field) 
throws Exception {
+        ResponseType arrayType = new ResponseType();
+        arrayType.setName(fieldName);
+        arrayType.setType("array");
+        String fieldTypeName = fieldType.toString();
+        ResponseType elementType = new ResponseType();
+        elementType.setName("ITEMS");
+        if ("MESSAGE".equals(fieldTypeName)) {
+            Object msgDescriptor = getMessageTypeMethod.invoke(field);
+            java.lang.reflect.Method getFullNameMethod = 
msgDescriptor.getClass().getMethod("getFullName");
+            String fullMsgName = (String) 
getFullNameMethod.invoke(msgDescriptor);
+            elementType.setType("object");
+            elementType.setDescription(fullMsgName);
+        } else {
+            elementType.setType(mapProtobufTypeToOpenApi(fieldTypeName));
+        }
+        arrayType.setRefs(Collections.singletonList(elementType));
+        return arrayType;
+    }
+
+    private static void resolveProtobufMessageField(final ResponseType 
fieldResponse,
+                                                    final 
java.lang.reflect.Method getMessageTypeMethod,
+                                                    final Object field, final 
Class<?> clazz,
+                                                    final int depth,
+                                                    final Map<TypeVariable<?>, 
Type> typeVariableMap) throws Exception {
+        Object msgDescriptor = getMessageTypeMethod.invoke(field);
+        java.lang.reflect.Method getFullNameMethod = 
msgDescriptor.getClass().getMethod("getFullName");
+        String fullMsgName = (String) getFullNameMethod.invoke(msgDescriptor);
+        fieldResponse.setType("object");
+        fieldResponse.setDescription(fullMsgName);
+        if (depth < 5) {
+            try {
+                Class<?> nestedClass = Class.forName(toJavaClassName(clazz, 
fullMsgName));
+                List<ResponseType> nestedRefs = parseProtobufClass(new 
ResponseType(), nestedClass, depth + 1, typeVariableMap).getRefs();
+                if (Objects.nonNull(nestedRefs) && !nestedRefs.isEmpty()) {
+                    fieldResponse.setRefs(nestedRefs);
+                }
+            } catch (ClassNotFoundException ignored) {
+            }
+        }
+    }
+
+    private static String mapProtobufTypeToOpenApi(final String protobufType) {
+        switch (protobufType) {
+            case "INT32":
+            case "INT64":
+            case "UINT32":
+            case "UINT64":
+            case "SINT32":
+            case "SINT64":
+            case "FIXED32":
+            case "FIXED64":
+            case "SFIXED32":
+            case "SFIXED64":
+                return "integer";
+            case "FLOAT":
+            case "DOUBLE":
+                return "number";
+            case "BOOL":
+                return "boolean";
+            case "STRING":
+            case "BYTES":
+                return "string";
+            default:
+                return "string";
+        }
+    }
+
     /**
      * generateDocumentResponse.
      *
@@ -236,6 +685,8 @@ public class OpenApiUtils {
         } else if (isDateType(clazz)) {
             responseType.setType("date");
             return responseType;
+        } else if (isProtobufMessage(clazz)) {
+            return parseProtobufClass(responseType, clazz, depth, 
typeVariableMap);
         } else {
             List<ResponseType> refs = new ArrayList<>();
             for (Field field : clazz.getDeclaredFields()) {
@@ -296,6 +747,228 @@ public class OpenApiUtils {
         return responseType;
     }
 
+    private static Schema parseSchema(final Type type, final int depth, final 
Map<TypeVariable<?>, Type> typeVariableMap) {
+        if (depth > 5) {
+            return new Schema("object", null);
+        }
+        if (type instanceof Class) {
+            return parseClassSchema((Class<?>) type, depth, typeVariableMap);
+        } else if (type instanceof ParameterizedType) {
+            return parseParameterizedTypeSchema((ParameterizedType) type, 
depth, typeVariableMap);
+        } else if (type instanceof GenericArrayType) {
+            Schema elementSchema = parseSchema(((GenericArrayType) 
type).getGenericComponentType(), depth + 1, typeVariableMap);
+            Schema schema = new Schema("array", null);
+            schema.setRefs(Collections.singletonList(elementSchema));
+            return schema;
+        } else if (type instanceof TypeVariable) {
+            Type actualType = typeVariableMap.get(type);
+            if (Objects.nonNull(actualType)) {
+                return parseSchema(actualType, depth, typeVariableMap);
+            } else if (((TypeVariable<?>) type).getBounds().length > 0) {
+                return parseSchema(((TypeVariable<?>) type).getBounds()[0], 
depth, typeVariableMap);
+            } else {
+                return new Schema("object", null);
+            }
+        } else {
+            return new Schema("object", null);
+        }
+    }
+
+    private static Schema parseClassSchema(final Class<?> clazz, final int 
depth, final Map<TypeVariable<?>, Type> typeVariableMap) {
+        if (clazz.isArray()) {
+            Schema elementSchema = parseSchema(clazz.getComponentType(), depth 
+ 1, typeVariableMap);
+            Schema schema = new Schema("array", null);
+            schema.setRefs(Collections.singletonList(elementSchema));
+            return schema;
+        } else if (clazz.isEnum()) {
+            return new Schema("string", null);
+        } else if (isBooleanType(clazz)) {
+            return new Schema("boolean", null);
+        } else if (isIntegerType(clazz)) {
+            return new Schema("integer", null);
+        } else if (isNumberType(clazz)) {
+            return new Schema("number", null);
+        } else if (isStringType(clazz)) {
+            return new Schema("string", null);
+        } else if (isDateType(clazz)) {
+            return new Schema("string", "date");
+        } else if (Collection.class.isAssignableFrom(clazz)) {
+            return new Schema("array", null);
+        } else if (Map.class.isAssignableFrom(clazz)) {
+            return new Schema("object", null);
+        } else if (isProtobufMessage(clazz)) {
+            return parseProtobufClassSchema(clazz, depth, typeVariableMap);
+        } else {
+            List<Schema> refs = new ArrayList<>();
+            for (Field field : clazz.getDeclaredFields()) {
+                if (Modifier.isStatic(field.getModifiers())) {
+                    continue;
+                }
+                Schema fieldSchema = parseSchema(field.getGenericType(), depth 
+ 1, typeVariableMap);
+                fieldSchema.setName(field.getName());
+                refs.add(fieldSchema);
+            }
+            Schema schema = new Schema("object", null);
+            schema.setRefs(refs);
+            return schema;
+        }
+    }
+
+    private static Schema parseParameterizedTypeSchema(final ParameterizedType 
type, final int depth, final Map<TypeVariable<?>, Type> typeVariableMap) {
+        Class<?> rawType = (Class<?>) type.getRawType();
+        Type[] actualTypeArguments = type.getActualTypeArguments();
+        TypeVariable<?>[] typeVariables = rawType.getTypeParameters();
+        Map<TypeVariable<?>, Type> newTypeVariableMap = new 
HashMap<>(typeVariableMap);
+        for (int i = 0; i < typeVariables.length; i++) {
+            newTypeVariableMap.put(typeVariables[i], actualTypeArguments[i]);
+        }
+        if (Collection.class.isAssignableFrom(rawType)) {
+            Schema elementSchema = parseSchema(actualTypeArguments[0], depth + 
1, newTypeVariableMap);
+            elementSchema.setName("items");
+            Schema schema = new Schema("array", null);
+            schema.setRefs(Collections.singletonList(elementSchema));
+            return schema;
+        } else if (Map.class.isAssignableFrom(rawType)) {
+            Schema keySchema = parseSchema(actualTypeArguments[0], depth + 1, 
newTypeVariableMap);
+            keySchema.setName("key");
+            Schema valueSchema = parseSchema(actualTypeArguments[1], depth + 
1, newTypeVariableMap);
+            valueSchema.setName("value");
+            Schema schema = new Schema("object", null);
+            schema.setRefs(Arrays.asList(keySchema, valueSchema));
+            return schema;
+        } else {
+            List<Schema> refs = new ArrayList<>();
+            for (Field field : rawType.getDeclaredFields()) {
+                if (Modifier.isStatic(field.getModifiers())) {
+                    continue;
+                }
+                Schema fieldSchema = parseSchema(field.getGenericType(), depth 
+ 1, newTypeVariableMap);
+                fieldSchema.setName(field.getName());
+                refs.add(fieldSchema);
+            }
+            Schema schema = new Schema("object", null);
+            schema.setRefs(refs);
+            return schema;
+        }
+    }
+
+    private static Schema parseProtobufClassSchema(final Class<?> clazz, final 
int depth, final Map<TypeVariable<?>, Type> typeVariableMap) {
+        if (isProtobufEmpty(clazz)) {
+            return new Schema("object", null);
+        }
+        try {
+            java.lang.reflect.Method getDescriptorMethod = 
clazz.getMethod("getDescriptor");
+            Object descriptor = getDescriptorMethod.invoke(null);
+            java.lang.reflect.Method getFieldsMethod = 
descriptor.getClass().getMethod("getFields");
+            @SuppressWarnings("unchecked")
+            List<?> fields = (List<?>) getFieldsMethod.invoke(descriptor);
+            List<Schema> refs = parseProtobufFieldsSchema(fields, clazz, 
depth, typeVariableMap);
+            Schema schema = new Schema("object", null);
+            schema.setRefs(refs);
+            return schema;
+        } catch (Exception e) {
+            return new Schema("object", null);
+        }
+    }
+
+    private static List<Schema> parseProtobufFieldsSchema(final List<?> 
fields, final Class<?> clazz,
+                                                          final int depth, 
final Map<TypeVariable<?>, Type> typeVariableMap) throws Exception {
+        List<Schema> refs = new ArrayList<>();
+        java.lang.reflect.Method getNameMethod = null;
+        java.lang.reflect.Method getTypeMethod = null;
+        java.lang.reflect.Method isRepeatedMethod = null;
+        java.lang.reflect.Method getMessageTypeMethod = null;
+        for (Object field : fields) {
+            if (Objects.isNull(getNameMethod)) {
+                getNameMethod = field.getClass().getMethod("getName");
+                getTypeMethod = field.getClass().getMethod("getType");
+                isRepeatedMethod = field.getClass().getMethod("isRepeated");
+                getMessageTypeMethod = 
field.getClass().getMethod("getMessageType");
+            }
+            String fieldName = (String) getNameMethod.invoke(field);
+            Object fieldType = getTypeMethod.invoke(field);
+            boolean isRepeated = (boolean) isRepeatedMethod.invoke(field);
+            refs.add(parseProtobufFieldSchema(fieldName, fieldType, isRepeated,
+                    getMessageTypeMethod, field, clazz, depth, 
typeVariableMap));
+        }
+        return refs;
+    }
+
+    private static Schema parseProtobufFieldSchema(final String fieldName, 
final Object fieldType, final boolean isRepeated,
+                                                   final 
java.lang.reflect.Method getMessageTypeMethod, final Object field,
+                                                   final Class<?> clazz, final 
int depth,
+                                                   final Map<TypeVariable<?>, 
Type> typeVariableMap) throws Exception {
+        if (isRepeated) {
+            return parseRepeatedProtobufFieldSchema(fieldName, fieldType, 
getMessageTypeMethod, field, clazz, depth, typeVariableMap);
+        }
+        String fieldTypeName = fieldType.toString();
+        Schema schema;
+        if ("MESSAGE".equals(fieldTypeName)) {
+            schema = resolveProtobufMessageFieldSchema(getMessageTypeMethod, 
field, clazz, depth, typeVariableMap);
+        } else if ("ENUM".equals(fieldTypeName)) {
+            schema = new Schema("string", null);
+        } else {
+            schema = new Schema(mapProtobufTypeToOpenApi(fieldTypeName), null);
+        }
+        schema.setName(fieldName);
+        return schema;
+    }
+
+    private static Schema parseRepeatedProtobufFieldSchema(final String 
fieldName, final Object fieldType,
+                                                           final 
java.lang.reflect.Method getMessageTypeMethod,
+                                                           final Object field,
+                                                           final Class<?> 
clazz, final int depth,
+                                                           final 
Map<TypeVariable<?>, Type> typeVariableMap) throws Exception {
+        String fieldTypeName = fieldType.toString();
+        Schema elementSchema;
+        if ("MESSAGE".equals(fieldTypeName)) {
+            elementSchema = 
resolveProtobufMessageFieldSchema(getMessageTypeMethod, field, clazz, depth, 
typeVariableMap);
+        } else if ("ENUM".equals(fieldTypeName)) {
+            elementSchema = new Schema("string", null);
+        } else {
+            elementSchema = new 
Schema(mapProtobufTypeToOpenApi(fieldTypeName), null);
+        }
+        elementSchema.setName("items");
+        Schema schema = new Schema("array", null);
+        schema.setName(fieldName);
+        schema.setRefs(Collections.singletonList(elementSchema));
+        return schema;
+    }
+
+    private static Schema resolveProtobufMessageFieldSchema(final 
java.lang.reflect.Method getMessageTypeMethod,
+                                                            final Object 
field, final Class<?> clazz,
+                                                            final int depth,
+                                                            final 
Map<TypeVariable<?>, Type> typeVariableMap) throws Exception {
+        Object msgDescriptor = getMessageTypeMethod.invoke(field);
+        java.lang.reflect.Method getFullNameMethod = 
msgDescriptor.getClass().getMethod("getFullName");
+        String fullMsgName = (String) getFullNameMethod.invoke(msgDescriptor);
+        Schema schema = new Schema("object", null);
+        if (depth < 5) {
+            try {
+                Class<?> nestedClass = Class.forName(toJavaClassName(clazz, 
fullMsgName));
+                List<Schema> nestedRefs = 
parseProtobufClassSchema(nestedClass, depth + 1, typeVariableMap).getRefs();
+                if (Objects.nonNull(nestedRefs) && !nestedRefs.isEmpty()) {
+                    schema.setRefs(nestedRefs);
+                }
+            } catch (ClassNotFoundException ignored) {
+            }
+        }
+        return schema;
+    }
+
+    private static String toJavaClassName(final Class<?> contextClass, final 
String protobufFullName) {
+        String packageName = contextClass.getPackage().getName();
+        if (protobufFullName.startsWith(packageName + ".")) {
+            String relativeName = 
protobufFullName.substring(packageName.length() + 1).replace('.', '$');
+            Class<?> declaringClass = contextClass.getDeclaringClass();
+            if (Objects.nonNull(declaringClass)) {
+                return declaringClass.getName() + "$" + relativeName;
+            }
+            return packageName + "." + relativeName;
+        }
+        return protobufFullName.replace('.', '$');
+    }
+
     private static boolean isDateType(final Class<?> clazz) {
         return clazz == Date.class || clazz == LocalDate.class
                 || clazz == LocalDateTime.class || clazz == LocalTime.class;
@@ -368,7 +1041,6 @@ public class OpenApiUtils {
         }
     }
 
-
     public static class Parameter {
 
         private String name;
@@ -379,145 +1051,105 @@ public class OpenApiUtils {
 
         private boolean required;
 
-        private Schema schema;
+        private String type;
+
+        private List<Parameter> refs;
 
-        /**
-         * get name.
-         *
-         * @return name
-         */
         public String getName() {
             return name;
         }
 
-        /**
-         * set name.
-         *
-         * @param name name
-         */
         public void setName(final String name) {
             this.name = name;
         }
 
-        /**
-         * get in.
-         *
-         * @return in
-         */
         public String getIn() {
             return in;
         }
 
-        /**
-         * set in.
-         *
-         * @param in in
-         */
         public void setIn(final String in) {
             this.in = in;
         }
 
-        /**
-         * get description.
-         *
-         * @return description
-         */
         public String getDescription() {
             return description;
         }
 
-        /**
-         * set description.
-         *
-         * @param description description
-         */
         public void setDescription(final String description) {
             this.description = description;
         }
 
-        /**
-         * get required.
-         *
-         * @return required
-         */
         public boolean isRequired() {
             return required;
         }
 
-        /**
-         * set required.
-         *
-         * @param required required
-         */
         public void setRequired(final boolean required) {
             this.required = required;
         }
 
-        /**
-         * get schema.
-         *
-         * @return schema
-         */
-        public Schema getSchema() {
-            return schema;
+        public String getType() {
+            return type;
         }
 
-        /**
-         * set schema.
-         *
-         * @param schema schema
-         */
-        public void setSchema(final Schema schema) {
-            this.schema = schema;
+        public void setType(final String type) {
+            this.type = type;
+        }
+
+        public List<Parameter> getRefs() {
+            return refs;
+        }
+
+        public void setRefs(final List<Parameter> refs) {
+            this.refs = refs;
         }
     }
 
     public static class Schema {
 
+        private String name;
+
         private String type;
 
         private String format;
 
+        private List<Schema> refs;
+
         public Schema(final String type, final String format) {
             this.type = type;
             this.format = format;
         }
 
-        /**
-         * get type.
-         *
-         * @return type
-         */
+        public String getName() {
+            return name;
+        }
+
+        public void setName(final String name) {
+            this.name = name;
+        }
+
         public String getType() {
             return type;
         }
 
-        /**
-         * set type.
-         *
-         * @param type type
-         */
         public void setType(final String type) {
             this.type = type;
         }
 
-        /**
-         * get format.
-         *
-         * @return format
-         */
         public String getFormat() {
             return format;
         }
 
-        /**
-         * set format.
-         *
-         * @param format format
-         */
         public void setFormat(final String format) {
             this.format = format;
         }
+
+        public List<Schema> getRefs() {
+            return refs;
+        }
+
+        public void setRefs(final List<Schema> refs) {
+            this.refs = refs;
+        }
     }
 
 }
diff --git 
a/shenyu-client/shenyu-client-core/src/test/java/org/apache/shenyu/client/core/register/registrar/ApiDocRegistrarImplTest.java
 
b/shenyu-client/shenyu-client-core/src/test/java/org/apache/shenyu/client/core/register/registrar/ApiDocRegistrarImplTest.java
new file mode 100644
index 0000000000..d3b3857de0
--- /dev/null
+++ 
b/shenyu-client/shenyu-client-core/src/test/java/org/apache/shenyu/client/core/register/registrar/ApiDocRegistrarImplTest.java
@@ -0,0 +1,263 @@
+/*
+ * 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.client.core.register.registrar;
+
+import org.apache.shenyu.client.core.register.ApiBean;
+import org.apache.shenyu.client.core.register.ClientRegisterConfig;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.apache.shenyu.common.utils.GsonUtils;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+
+public class ApiDocRegistrarImplTest {
+
+    private ApiDocRegistrarImpl dubboRegistrar;
+
+    private ApiDocRegistrarImpl httpRegistrar;
+
+    private ApiDocRegistrarImpl grpcRegistrar;
+
+    @BeforeEach
+    public void init() {
+        dubboRegistrar = new ApiDocRegistrarImpl(new 
DubboClientRegisterConfig());
+        httpRegistrar = new ApiDocRegistrarImpl(new 
HttpClientRegisterConfig());
+        grpcRegistrar = new ApiDocRegistrarImpl(new 
GrpcClientRegisterConfig());
+    }
+
+    @Test
+    void testGetDocumentForRpcType() throws Exception {
+        ApiBean apiBean = new ApiBean(RpcTypeEnum.DUBBO.getName(),
+                TestDubboService.class.getName(),
+                TestDubboService.class.getDeclaredConstructor().newInstance(),
+                "dubboTestService");
+
+        apiBean.addApiDefinition(TestDubboService.class.getMethod("findById", 
String.class), "/findById");
+
+        String document = invokeGetDocument(dubboRegistrar, 
apiBean.getApiDefinitions().get(0));
+
+        // RPC document should use requestParameters/responseParameters (not 
parameters/responseType)
+        Map<String, Object> docMap = 
GsonUtils.getInstance().toObjectMap(document);
+        assertThat("RPC document should contain requestParameters", 
docMap.containsKey("requestParameters"), is(true));
+        assertThat("RPC document should contain responseParameters", 
docMap.containsKey("responseParameters"), is(true));
+        assertThat("RPC document should NOT contain parameters (old field)", 
docMap.containsKey("parameters"), is(false));
+        assertThat("RPC document should NOT contain responseType (old field)", 
docMap.containsKey("responseType"), is(false));
+    }
+
+    @Test
+    void testGetDocumentForHttpType() throws Exception {
+        ApiBean apiBean = new ApiBean(RpcTypeEnum.HTTP.getName(),
+                TestHttpService.class.getName(),
+                TestHttpService.class.getDeclaredConstructor().newInstance(),
+                "httpTestService");
+
+        apiBean.addApiDefinition(TestHttpService.class.getMethod("findById", 
String.class), "/findById");
+
+        String document = invokeGetDocument(httpRegistrar, 
apiBean.getApiDefinitions().get(0));
+
+        // HTTP document should also use requestParameters/responseParameters
+        Map<String, Object> docMap = 
GsonUtils.getInstance().toObjectMap(document);
+        assertThat("HTTP document should contain requestParameters", 
docMap.containsKey("requestParameters"), is(true));
+        assertThat("HTTP document should contain responseParameters", 
docMap.containsKey("responseParameters"), is(true));
+        assertThat("HTTP document should NOT contain parameters (old field)", 
docMap.containsKey("parameters"), is(false));
+        assertThat("HTTP document should NOT contain responseType (old 
field)", docMap.containsKey("responseType"), is(false));
+    }
+
+    @Test
+    void testGetDocumentForGrpcType() throws Exception {
+        ApiBean apiBean = new ApiBean(RpcTypeEnum.GRPC.getName(),
+                TestGrpcService.class.getName(),
+                TestGrpcService.class.getDeclaredConstructor().newInstance(),
+                "grpcTestService");
+
+        apiBean.addApiDefinition(TestGrpcService.class.getMethod("findById", 
String.class), "/findById");
+
+        String document = invokeGetDocument(grpcRegistrar, 
apiBean.getApiDefinitions().get(0));
+
+        // gRPC document should use requestParameters/responseParameters
+        Map<String, Object> docMap = 
GsonUtils.getInstance().toObjectMap(document);
+        assertThat("gRPC document should contain requestParameters", 
docMap.containsKey("requestParameters"), is(true));
+        assertThat("gRPC document should contain responseParameters", 
docMap.containsKey("responseParameters"), is(true));
+        assertThat("gRPC document should NOT contain parameters (old field)", 
docMap.containsKey("parameters"), is(false));
+        assertThat("gRPC document should NOT contain responseType (old 
field)", docMap.containsKey("responseType"), is(false));
+    }
+
+    @Test
+    void testGetDocumentWithCustomDocument() throws Exception {
+        ApiBean apiBean = new ApiBean(RpcTypeEnum.DUBBO.getName(),
+                TestDubboService.class.getName(),
+                TestDubboService.class.getDeclaredConstructor().newInstance(),
+                "dubboTestService");
+
+        apiBean.addApiDefinition(TestDubboService.class.getMethod("findById", 
String.class), "/findById");
+
+        // Set custom document via properties
+        String customDoc = 
"{\"tags\":[\"custom\"],\"operationId\":\"/custom\"}";
+        apiBean.getApiDefinitions().get(0).addProperties("document", 
customDoc);
+
+        String document = invokeGetDocument(dubboRegistrar, 
apiBean.getApiDefinitions().get(0));
+
+        // Should return the custom document as-is
+        Map<String, Object> docMap = 
GsonUtils.getInstance().toObjectMap(document);
+        assertThat("Should use custom document", 
docMap.containsKey("operationId"), is(true));
+        assertThat(docMap.get("operationId"), is("/custom"));
+    }
+
+    @SuppressWarnings("unchecked")
+    private String invokeGetDocument(final ApiDocRegistrarImpl registrar, 
final ApiBean.ApiDefinition api) throws Exception {
+        Method getDocumentMethod = 
ApiDocRegistrarImpl.class.getDeclaredMethod("getDocument", 
ApiBean.ApiDefinition.class);
+        getDocumentMethod.setAccessible(true);
+        return (String) getDocumentMethod.invoke(registrar, api);
+    }
+
+    // --- Inner types (must be after all methods per checkstyle 
InnerTypeLast) ---
+
+    public static class TestDubboService {
+        public Object findById(final String id) {
+            return null;
+        }
+    }
+
+    public static class TestHttpService {
+        public Object findById(final String id) {
+            return null;
+        }
+    }
+
+    public static class TestGrpcService {
+        public Object findById(final String id) {
+            return null;
+        }
+    }
+
+    static class DubboClientRegisterConfig implements ClientRegisterConfig {
+        @Override
+        public Integer getPort() {
+            return 20880;
+        }
+
+        @Override
+        public String getHost() {
+            return "127.0.0.1";
+        }
+
+        @Override
+        public String getAppName() {
+            return "test-dubbo";
+        }
+
+        @Override
+        public String getContextPath() {
+            return "/dubbo";
+        }
+
+        @Override
+        public String getIpAndPort() {
+            return "127.0.0.1:20880";
+        }
+
+        @Override
+        public Boolean getAddPrefixed() {
+            return false;
+        }
+
+        @Override
+        public RpcTypeEnum getRpcTypeEnum() {
+            return RpcTypeEnum.DUBBO;
+        }
+    }
+
+    static class HttpClientRegisterConfig implements ClientRegisterConfig {
+        @Override
+        public Integer getPort() {
+            return 8080;
+        }
+
+        @Override
+        public String getHost() {
+            return "127.0.0.1";
+        }
+
+        @Override
+        public String getAppName() {
+            return "test-http";
+        }
+
+        @Override
+        public String getContextPath() {
+            return "/http";
+        }
+
+        @Override
+        public String getIpAndPort() {
+            return "127.0.0.1:8080";
+        }
+
+        @Override
+        public Boolean getAddPrefixed() {
+            return false;
+        }
+
+        @Override
+        public RpcTypeEnum getRpcTypeEnum() {
+            return RpcTypeEnum.HTTP;
+        }
+    }
+
+    static class GrpcClientRegisterConfig implements ClientRegisterConfig {
+        @Override
+        public Integer getPort() {
+            return 9090;
+        }
+
+        @Override
+        public String getHost() {
+            return "127.0.0.1";
+        }
+
+        @Override
+        public String getAppName() {
+            return "test-grpc";
+        }
+
+        @Override
+        public String getContextPath() {
+            return "/grpc";
+        }
+
+        @Override
+        public String getIpAndPort() {
+            return "127.0.0.1:9090";
+        }
+
+        @Override
+        public Boolean getAddPrefixed() {
+            return false;
+        }
+
+        @Override
+        public RpcTypeEnum getRpcTypeEnum() {
+            return RpcTypeEnum.GRPC;
+        }
+    }
+}
diff --git 
a/shenyu-client/shenyu-client-core/src/test/java/org/apache/shenyu/client/core/register/registrar/NoHttpApiDocRegistrarTest.java
 
b/shenyu-client/shenyu-client-core/src/test/java/org/apache/shenyu/client/core/register/registrar/NoHttpApiDocRegistrarTest.java
index 835c57a635..e9760c6dc9 100644
--- 
a/shenyu-client/shenyu-client-core/src/test/java/org/apache/shenyu/client/core/register/registrar/NoHttpApiDocRegistrarTest.java
+++ 
b/shenyu-client/shenyu-client-core/src/test/java/org/apache/shenyu/client/core/register/registrar/NoHttpApiDocRegistrarTest.java
@@ -20,41 +20,295 @@ package org.apache.shenyu.client.core.register.registrar;
 import org.apache.shenyu.client.apidocs.annotations.ApiDoc;
 import org.apache.shenyu.client.apidocs.annotations.ApiModule;
 import org.apache.shenyu.client.core.constant.ShenyuClientConstants;
+import 
org.apache.shenyu.client.core.disruptor.ShenyuClientRegisterEventPublisher;
 import org.apache.shenyu.client.core.register.ApiBean;
+import org.apache.shenyu.client.core.register.ClientRegisterConfig;
 import org.apache.shenyu.common.enums.ApiHttpMethodEnum;
 import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.apache.shenyu.register.client.api.ShenyuClientRegisterRepository;
+import org.apache.shenyu.register.common.dto.ApiDocRegisterDTO;
+import org.apache.shenyu.register.common.type.DataTypeParent;
+import org.junit.jupiter.api.BeforeEach;
 import org.junit.jupiter.api.Test;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
 
 public class NoHttpApiDocRegistrarTest {
-    private final NoHttpApiDocRegistrar noHttpApiDocRegistrar =
-            new NoHttpApiDocRegistrar(null, new TestClientRegisterConfig());
-    
+
+    private TestShenyuClientRegisterEventPublisher testPublisher;
+
+    private NoHttpApiDocRegistrar noHttpApiDocRegistrar;
+
+    private NoHttpApiDocRegistrar grpcApiDocRegistrar;
+
+    @BeforeEach
+    public void init() {
+        testPublisher = new TestShenyuClientRegisterEventPublisher();
+        noHttpApiDocRegistrar = new NoHttpApiDocRegistrar(testPublisher, new 
DubboTestClientRegisterConfig());
+        grpcApiDocRegistrar = new NoHttpApiDocRegistrar(testPublisher, new 
GrpcTestClientRegisterConfig());
+    }
+
     @Test
     public void testDoParse() {
         final TestApiBeanAnnotatedClassAndMethod bean = new 
TestApiBeanAnnotatedClassAndMethod();
-        
-        ApiBean apiBean = new ApiBean(RpcTypeEnum.HTTP.getName(), "bean", 
bean);
-        
+
+        ApiBean apiBean = new ApiBean(RpcTypeEnum.DUBBO.getName(), "bean", 
bean);
+
         apiBean.addApiDefinition(null, null);
-        
+
         AbstractApiDocRegistrar.HttpApiSpecificInfo httpApiSpecificInfo =
                 
noHttpApiDocRegistrar.doParse(apiBean.getApiDefinitions().get(0));
-        
+
         assertThat(httpApiSpecificInfo.getApiHttpMethodEnums().get(0), 
is(ApiHttpMethodEnum.NOT_HTTP));
-        
+
         assertThat(httpApiSpecificInfo.getConsume(), 
is(ShenyuClientConstants.MEDIA_TYPE_ALL_VALUE));
-        
+
         assertThat(httpApiSpecificInfo.getProduce(), 
is(ShenyuClientConstants.MEDIA_TYPE_ALL_VALUE));
     }
-    
+
+    @Test
+    public void testRpcDocumentGenerationWithParameters() throws Exception {
+        ApiBean apiBean = new ApiBean(RpcTypeEnum.DUBBO.getName(),
+                DubboTestServiceImpl.class.getName(),
+                
DubboTestServiceImpl.class.getDeclaredConstructor().newInstance(),
+                "dubboTestService");
+
+        
apiBean.addApiDefinition(DubboTestServiceImpl.class.getMethod("findById", 
String.class), "/findById");
+
+        noHttpApiDocRegistrar.register(apiBean);
+
+        ApiDocRegisterDTO dto = testPublisher.metaData;
+        assertThat(dto, notNullValue());
+
+        // Verify document contains requestParameters (ResponseType format for 
RPC)
+        assertThat(dto.getDocument(), notNullValue());
+        assertThat("Document should contain requestParameters field", 
dto.getDocument().contains("\"requestParameters\""), is(true));
+        assertThat("Parameters should have string type", 
dto.getDocument().contains("\"type\":\"string\""), is(true));
+    }
+
+    @Test
+    public void testRpcDocumentGenerationWithComplexParameter() throws 
Exception {
+        ApiBean apiBean = new ApiBean(RpcTypeEnum.DUBBO.getName(),
+                DubboTestServiceImpl.class.getName(),
+                
DubboTestServiceImpl.class.getDeclaredConstructor().newInstance(),
+                "dubboTestService");
+
+        
apiBean.addApiDefinition(DubboTestServiceImpl.class.getMethod("insert", 
DubboTest.class), "/insert");
+
+        noHttpApiDocRegistrar.register(apiBean);
+
+        ApiDocRegisterDTO dto = testPublisher.metaData;
+        assertThat(dto, notNullValue());
+
+        // Verify document contains object-type parameter with nested refs
+        assertThat(dto.getDocument(), notNullValue());
+        assertThat("Document should contain requestParameters with object 
type",
+                dto.getDocument().contains("\"type\":\"object\""), is(true));
+        assertThat("Complex parameter should have nested refs",
+                dto.getDocument().contains("\"refs\""), is(true));
+    }
+
+    @Test
+    public void testRpcDocumentGenerationResponseType() throws Exception {
+        ApiBean apiBean = new ApiBean(RpcTypeEnum.DUBBO.getName(),
+                DubboTestServiceImpl.class.getName(),
+                
DubboTestServiceImpl.class.getDeclaredConstructor().newInstance(),
+                "dubboTestService");
+
+        
apiBean.addApiDefinition(DubboTestServiceImpl.class.getMethod("findById", 
String.class), "/findById");
+
+        noHttpApiDocRegistrar.register(apiBean);
+
+        ApiDocRegisterDTO dto = testPublisher.metaData;
+        assertThat(dto, notNullValue());
+
+        // Verify responseParameters contains object with id and name fields
+        assertThat(dto.getDocument(), notNullValue());
+        assertThat("responseParameters should contain object type",
+                dto.getDocument().contains("\"responseParameters\""), 
is(true));
+    }
+
+    @Test
+    public void testGrpcDocumentGenerationWithParameters() throws Exception {
+        ApiBean apiBean = new ApiBean(RpcTypeEnum.GRPC.getName(),
+                GrpcTestServiceImpl.class.getName(),
+                
GrpcTestServiceImpl.class.getDeclaredConstructor().newInstance(),
+                "grpcTestService");
+
+        
apiBean.addApiDefinition(GrpcTestServiceImpl.class.getMethod("unaryCall", 
String.class, io.grpc.stub.StreamObserver.class), "/unaryCall");
+
+        grpcApiDocRegistrar.register(apiBean);
+
+        ApiDocRegisterDTO dto = testPublisher.metaData;
+        assertThat(dto, notNullValue());
+        assertThat(dto.getDocument(), notNullValue());
+        assertThat("gRPC document should contain requestParameters field", 
dto.getDocument().contains("\"requestParameters\""), is(true));
+        assertThat("gRPC document should contain responseParameters field", 
dto.getDocument().contains("\"responseParameters\""), is(true));
+    }
+
+    @Test
+    public void testGrpcDocumentGenerationWithVoidReturn() throws Exception {
+        ApiBean apiBean = new ApiBean(RpcTypeEnum.GRPC.getName(),
+                GrpcTestServiceImpl.class.getName(),
+                
GrpcTestServiceImpl.class.getDeclaredConstructor().newInstance(),
+                "grpcTestService");
+
+        
apiBean.addApiDefinition(GrpcTestServiceImpl.class.getMethod("noStreamObserver",
 String.class), "/noStream");
+
+        grpcApiDocRegistrar.register(apiBean);
+
+        ApiDocRegisterDTO dto = testPublisher.metaData;
+        assertThat(dto, notNullValue());
+        assertThat(dto.getDocument(), notNullValue());
+        assertThat("gRPC document should contain requestParameters field", 
dto.getDocument().contains("\"requestParameters\""), is(true));
+        assertThat("gRPC document should contain responseParameters field", 
dto.getDocument().contains("\"responseParameters\""), is(true));
+    }
+
+    // --- Inner types (must be after all methods per checkstyle 
InnerTypeLast) ---
+
+    public static class DubboTest {
+
+        private String id;
+
+        private String name;
+
+        public DubboTest() {
+        }
+
+        public String getId() {
+            return id;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+
+    @ApiModule("dubboTestService")
+    public static class DubboTestServiceImpl {
+
+        @ApiDoc(desc = "findById")
+        public DubboTest findById(final String id) {
+            return null;
+        }
+
+        @ApiDoc(desc = "insert")
+        public DubboTest insert(final DubboTest dubboTest) {
+            return null;
+        }
+    }
+
+    @ApiModule("grpcTestService")
+    public static class GrpcTestServiceImpl {
+
+        @ApiDoc(desc = "unaryCall")
+        public void unaryCall(final String request, final 
io.grpc.stub.StreamObserver<DubboTest> responseObserver) {
+        }
+
+        @ApiDoc(desc = "noStreamObserver")
+        public String noStreamObserver(final String request) {
+            return null;
+        }
+    }
+
     @ApiModule("testClass")
     static class TestApiBeanAnnotatedClassAndMethod {
+
         @ApiDoc(desc = "testMethod")
         public String testMethod() {
             return "";
         }
     }
+
+    static class DubboTestClientRegisterConfig implements ClientRegisterConfig 
{
+
+        @Override
+        public Integer getPort() {
+            return 20880;
+        }
+
+        @Override
+        public String getHost() {
+            return "127.0.0.1";
+        }
+
+        @Override
+        public String getAppName() {
+            return "test-dubbo";
+        }
+
+        @Override
+        public String getContextPath() {
+            return "/dubbo";
+        }
+
+        @Override
+        public String getIpAndPort() {
+            return "127.0.0.1:20880";
+        }
+
+        @Override
+        public Boolean getAddPrefixed() {
+            return false;
+        }
+
+        @Override
+        public RpcTypeEnum getRpcTypeEnum() {
+            return RpcTypeEnum.DUBBO;
+        }
+    }
+
+    static class GrpcTestClientRegisterConfig implements ClientRegisterConfig {
+
+        @Override
+        public Integer getPort() {
+            return 9090;
+        }
+
+        @Override
+        public String getHost() {
+            return "127.0.0.1";
+        }
+
+        @Override
+        public String getAppName() {
+            return "test-grpc";
+        }
+
+        @Override
+        public String getContextPath() {
+            return "/grpc";
+        }
+
+        @Override
+        public String getIpAndPort() {
+            return "127.0.0.1:9090";
+        }
+
+        @Override
+        public Boolean getAddPrefixed() {
+            return false;
+        }
+
+        @Override
+        public RpcTypeEnum getRpcTypeEnum() {
+            return RpcTypeEnum.GRPC;
+        }
+    }
+
+    static class TestShenyuClientRegisterEventPublisher extends 
ShenyuClientRegisterEventPublisher {
+
+        private ApiDocRegisterDTO metaData;
+
+        @Override
+        public void start(final ShenyuClientRegisterRepository 
shenyuClientRegisterRepository) {
+        }
+
+        @Override
+        public void publishEvent(final DataTypeParent data) {
+            this.metaData = (ApiDocRegisterDTO) data;
+        }
+    }
 }
diff --git 
a/shenyu-client/shenyu-client-core/src/test/java/org/apache/shenyu/client/core/utils/OpenApiUtilsTest.java
 
b/shenyu-client/shenyu-client-core/src/test/java/org/apache/shenyu/client/core/utils/OpenApiUtilsTest.java
new file mode 100644
index 0000000000..350969927f
--- /dev/null
+++ 
b/shenyu-client/shenyu-client-core/src/test/java/org/apache/shenyu/client/core/utils/OpenApiUtilsTest.java
@@ -0,0 +1,519 @@
+/*
+ * 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.client.core.utils;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.google.protobuf.Empty;
+import org.apache.shenyu.client.core.test.Test.Address;
+import org.apache.shenyu.client.core.test.Test.TestRequest;
+import org.apache.shenyu.client.core.utils.OpenApiUtils.Parameter;
+import org.apache.shenyu.client.core.utils.OpenApiUtils.ResponseType;
+import org.apache.shenyu.common.enums.RpcTypeEnum;
+import org.junit.jupiter.api.Test;
+import org.springframework.web.bind.annotation.PathVariable;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.lang.reflect.Method;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+
+public class OpenApiUtilsTest {
+
+    @Test
+    void testGenerateRpcDocumentResponse() throws Exception {
+        Method method = DubboTestService.class.getMethod("findById", 
String.class);
+        Map<String, Object> response = 
OpenApiUtils.generateRpcDocumentResponse("/dubbo/findById", method);
+
+        assertThat(response.containsKey("200"), is(true));
+        assertThat(response.containsKey("404"), is(true));
+        assertThat(response.containsKey("409"), is(false));
+    }
+
+    @Test
+    void testGenerateRpcDocumentResponseVoidReturn() throws Exception {
+        Method method = RpcComplexParamService.class.getMethod("deleteById", 
String.class);
+        Map<String, Object> response = 
OpenApiUtils.generateRpcDocumentResponse("/rpc/deleteById", method);
+        assertThat(response.containsKey("200"), is(true));
+        assertThat(response.containsKey("404"), is(true));
+        assertThat(response.containsKey("409"), is(false));
+        Map<String, Object> successResponse = (Map<String, Object>) 
response.get("200");
+        Map<String, Object> content = (Map<String, Object>) 
successResponse.get("content");
+        Map<String, Object> schema = (Map<String, Object>) ((Map<String, 
Object>) content.get("*/*")).get("schema");
+        assertThat(schema.get("type"), is("void"));
+    }
+
+    @Test
+    void testParseReturnTypeObject() throws Exception {
+        Method method = DubboTestService.class.getMethod("findById", 
String.class);
+        ResponseType returnType = OpenApiUtils.parseReturnType(method);
+
+        assertThat(returnType.getType(), is("object"));
+        assertThat(returnType.getRefs(), notNullValue());
+        assertThat(returnType.getRefs(), hasSize(2));
+        assertThat(returnType.getRefs().get(0).getName(), is("id"));
+        assertThat(returnType.getRefs().get(0).getType(), is("string"));
+        assertThat(returnType.getRefs().get(1).getName(), is("name"));
+        assertThat(returnType.getRefs().get(1).getType(), is("string"));
+    }
+
+    @Test
+    void testParseReturnTypeList() throws Exception {
+        Method method = DubboTestService.class.getMethod("findAll");
+        ResponseType returnType = OpenApiUtils.parseReturnType(method);
+
+        assertThat(returnType.getType(), is("array"));
+        assertThat(returnType.getRefs(), notNullValue());
+        assertThat(returnType.getRefs(), hasSize(1));
+        assertThat(returnType.getRefs().get(0).getType(), is("object"));
+    }
+
+    @Test
+    void testIsProtobufMessageNegative() {
+        assertThat(OpenApiUtils.isProtobufMessage(DubboTest.class), is(false));
+        assertThat(OpenApiUtils.isProtobufMessage(String.class), is(false));
+        assertThat(OpenApiUtils.isProtobufMessage(null), is(false));
+        assertThat(OpenApiUtils.isProtobufMessage(int.class), is(false));
+    }
+
+    @Test
+    void testIsProtobufMessagePositive() {
+        assertThat(OpenApiUtils.isProtobufMessage(TestRequest.class), 
is(true));
+        assertThat(OpenApiUtils.isProtobufMessage(Address.class), is(true));
+        assertThat(OpenApiUtils.isProtobufMessage(Empty.class), is(true));
+    }
+
+    @Test
+    void testParseReturnTypeProtobufWithAllFieldTypes() throws Exception {
+        Method method = ProtobufTestService.class.getMethod("getTestRequest");
+        ResponseType returnType = OpenApiUtils.parseReturnType(method);
+        assertThat(returnType.getType(), is("object"));
+        assertThat(returnType.getRefs(), notNullValue());
+        assertThat(returnType.getRefs(), hasSize(7));
+
+        assertThat(returnType.getRefs().get(0).getName(), is("id"));
+        assertThat(returnType.getRefs().get(0).getType(), is("string"));
+
+        assertThat(returnType.getRefs().get(1).getName(), is("count"));
+        assertThat(returnType.getRefs().get(1).getType(), is("integer"));
+
+        assertThat(returnType.getRefs().get(2).getName(), is("enabled"));
+        assertThat(returnType.getRefs().get(2).getType(), is("boolean"));
+
+        assertThat(returnType.getRefs().get(3).getName(), is("status"));
+        assertThat(returnType.getRefs().get(3).getType(), is("string"));
+
+        assertThat(returnType.getRefs().get(4).getName(), is("address"));
+        assertThat(returnType.getRefs().get(4).getType(), is("object"));
+        assertThat(returnType.getRefs().get(4).getRefs(), notNullValue());
+        assertThat(returnType.getRefs().get(4).getRefs(), hasSize(2));
+        assertThat(returnType.getRefs().get(4).getRefs().get(0).getName(), 
is("street"));
+        assertThat(returnType.getRefs().get(4).getRefs().get(0).getType(), 
is("string"));
+        assertThat(returnType.getRefs().get(4).getRefs().get(1).getName(), 
is("city"));
+        assertThat(returnType.getRefs().get(4).getRefs().get(1).getType(), 
is("string"));
+
+        assertThat(returnType.getRefs().get(5).getName(), is("tags"));
+        assertThat(returnType.getRefs().get(5).getType(), is("array"));
+        assertThat(returnType.getRefs().get(5).getRefs(), notNullValue());
+        assertThat(returnType.getRefs().get(5).getRefs(), hasSize(1));
+        assertThat(returnType.getRefs().get(5).getRefs().get(0).getType(), 
is("string"));
+
+        assertThat(returnType.getRefs().get(6).getName(), is("addresses"));
+        assertThat(returnType.getRefs().get(6).getType(), is("array"));
+        assertThat(returnType.getRefs().get(6).getRefs(), notNullValue());
+        assertThat(returnType.getRefs().get(6).getRefs(), hasSize(1));
+        assertThat(returnType.getRefs().get(6).getRefs().get(0).getType(), 
is("object"));
+    }
+
+    @Test
+    void testParseReturnTypeProtobufEmpty() throws Exception {
+        Method method = ProtobufTestService.class.getMethod("getEmpty");
+        ResponseType returnType = OpenApiUtils.parseReturnType(method);
+        assertThat(returnType.getType(), is("object"));
+        assertThat(returnType.getRefs(), nullValue());
+    }
+
+    @Test
+    void testGenerateRpcRequestDocParametersProtobuf() throws Exception {
+        Method method = ProtobufTestService.class.getMethod("sendTestRequest", 
TestRequest.class);
+        List<Parameter> params = 
OpenApiUtils.generateRpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("request"));
+        assertThat(params.get(0).getType(), is("object"));
+        assertThat(params.get(0).getRefs(), notNullValue());
+        assertThat(params.get(0).getRefs(), hasSize(7));
+        assertThat(params.get(0).getRefs().get(0).getName(), is("id"));
+        assertThat(params.get(0).getRefs().get(0).getType(), is("string"));
+        assertThat(params.get(0).getRefs().get(3).getName(), is("status"));
+        assertThat(params.get(0).getRefs().get(3).getType(), is("string"));
+        assertThat(params.get(0).getRefs().get(5).getName(), is("tags"));
+        assertThat(params.get(0).getRefs().get(5).getType(), is("array"));
+    }
+
+    @Test
+    void testGenerateDocumentResponseExistingBehavior() {
+        Map<String, Object> response = 
OpenApiUtils.generateDocumentResponse("/test/path");
+        assertThat(response.containsKey("200"), is(true));
+        assertThat(response.containsKey("404"), is(true));
+        assertThat(response.containsKey("409"), is(true));
+    }
+
+    @Test
+    void testGenerateRequestDocParametersWithRequestParam() throws Exception {
+        Method method = SpringMvcController.class.getMethod("query", 
String.class);
+        List<Parameter> params = 
OpenApiUtils.generateRequestDocParameters("/test/query", method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("name"));
+        assertThat(params.get(0).getType(), is("string"));
+        assertThat(params.get(0).isRequired(), is(true));
+    }
+
+    @Test
+    void testGenerateRequestDocParametersWithPathVariable() throws Exception {
+        Method method = SpringMvcController.class.getMethod("getByPath", 
String.class);
+        List<Parameter> params = 
OpenApiUtils.generateRequestDocParameters("/test/{id}", method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("id"));
+        assertThat(params.get(0).getType(), is("string"));
+        assertThat(params.get(0).isRequired(), is(true));
+    }
+
+    @Test
+    void testGenerateRequestDocParametersWithQueryAndPath() throws Exception {
+        Method method = 
SpringMvcController.class.getMethod("getByPathWithQuery", String.class, 
String.class);
+        List<Parameter> params = 
OpenApiUtils.generateRequestDocParameters("/test/{id}/detail", method);
+        assertThat(params, hasSize(2));
+        assertThat(params.get(0).getName(), is("name"));
+        assertThat(params.get(0).getIn(), is("query"));
+        assertThat(params.get(1).getName(), is("id"));
+        assertThat(params.get(1).getIn(), is("path"));
+    }
+
+    @Test
+    void testGenerateRequestDocParametersNoAnnotations() throws Exception {
+        Method method = DubboTestService.class.getMethod("findById", 
String.class);
+        List<Parameter> params = 
OpenApiUtils.generateRequestDocParameters("/dubbo/findById", method);
+        assertThat(params, hasSize(0));
+    }
+
+    @Test
+    void testGenerateRpcRequestDocParametersSimpleType() throws Exception {
+        Method method = DubboTestService.class.getMethod("findById", 
String.class);
+        List<Parameter> params = 
OpenApiUtils.generateRpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("id"));
+        assertThat(params.get(0).getType(), is("string"));
+        assertThat(params.get(0).isRequired(), is(true));
+    }
+
+    @Test
+    void testGenerateRpcRequestDocParametersComplexType() throws Exception {
+        Method method = DubboTestService.class.getMethod("insert", 
DubboTest.class);
+        List<Parameter> params = 
OpenApiUtils.generateRpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getType(), is("object"));
+        assertThat(params.get(0).getRefs(), notNullValue());
+        assertThat(params.get(0).getRefs(), hasSize(2));
+        assertThat(params.get(0).isRequired(), is(true));
+    }
+
+    @Test
+    void testGenerateRpcRequestDocParametersWithListParameter() throws 
Exception {
+        Method method = RpcComplexParamService.class.getMethod("batchInsert", 
List.class);
+        List<Parameter> params = 
OpenApiUtils.generateRpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("ids"));
+        assertThat(params.get(0).getType(), is("array"));
+    }
+
+    @Test
+    void testGenerateRpcRequestDocParametersWithMapParameter() throws 
Exception {
+        Method method = RpcComplexParamService.class.getMethod("searchByMap", 
Map.class);
+        List<Parameter> params = 
OpenApiUtils.generateRpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("params"));
+        assertThat(params.get(0).getType(), is("object"));
+    }
+
+    @Test
+    void testUseSpringMvcParamParsing() {
+        assertThat(OpenApiUtils.useSpringMvcParamParsing(RpcTypeEnum.HTTP), 
is(true));
+        
assertThat(OpenApiUtils.useSpringMvcParamParsing(RpcTypeEnum.WEB_SOCKET), 
is(true));
+        
assertThat(OpenApiUtils.useSpringMvcParamParsing(RpcTypeEnum.SPRING_CLOUD), 
is(true));
+        assertThat(OpenApiUtils.useSpringMvcParamParsing(RpcTypeEnum.DUBBO), 
is(false));
+        assertThat(OpenApiUtils.useSpringMvcParamParsing(RpcTypeEnum.GRPC), 
is(false));
+        assertThat(OpenApiUtils.useSpringMvcParamParsing(RpcTypeEnum.SOFA), 
is(false));
+    }
+
+    // --- gRPC tests ---
+
+    @Test
+    void testGenerateGrpcRequestDocParametersSimpleType() throws Exception {
+        Method method = GrpcTestService.class.getMethod("unaryCall", 
String.class, io.grpc.stub.StreamObserver.class);
+        List<Parameter> params = 
OpenApiUtils.generateGrpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("request"));
+        assertThat(params.get(0).getType(), is("string"));
+        assertThat(params.get(0).isRequired(), is(true));
+    }
+
+    @Test
+    void testGenerateGrpcRequestDocParametersComplexType() throws Exception {
+        Method method = GrpcTestService.class.getMethod("unaryCallComplex", 
GrpcTestClass.class, io.grpc.stub.StreamObserver.class);
+        List<Parameter> params = 
OpenApiUtils.generateGrpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("request"));
+        assertThat(params.get(0).getType(), is("object"));
+        assertThat(params.get(0).getRefs(), notNullValue());
+        assertThat(params.get(0).isRequired(), is(true));
+    }
+
+    @Test
+    void testGenerateGrpcRequestDocParametersClientStreaming() throws 
Exception {
+        Method method = 
GrpcClientStreamingService.class.getMethod("clientStreaming", 
io.grpc.stub.StreamObserver.class);
+        List<Parameter> params = 
OpenApiUtils.generateGrpcRequestDocParameters(method);
+        assertThat(params, hasSize(1));
+        assertThat(params.get(0).getName(), is("request"));
+        assertThat(params.get(0).getType(), is("string"));
+        assertThat(params.get(0).isRequired(), is(true));
+    }
+
+    @Test
+    void testParseGrpcReturnTypeWithStreamObserver() throws Exception {
+        Method method = GrpcTestService.class.getMethod("unaryCall", 
String.class, io.grpc.stub.StreamObserver.class);
+        ResponseType returnType = OpenApiUtils.parseGrpcReturnType(method);
+        assertThat(returnType.getName(), is("ROOT"));
+        assertThat(returnType.getType(), is("object"));
+        assertThat(returnType.getRefs(), notNullValue());
+        assertThat(returnType.getRefs(), hasSize(2));
+    }
+
+    @Test
+    void testParseGrpcReturnTypeVoid() throws Exception {
+        Method method = GrpcTestService.class.getMethod("noStreamObserver", 
String.class);
+        ResponseType returnType = OpenApiUtils.parseGrpcReturnType(method);
+        assertThat(returnType.getName(), is("ROOT"));
+        assertThat(returnType.getType(), is("void"));
+    }
+
+    @Test
+    void testParseGrpcReturnTypeClientStreaming() throws Exception {
+        Method method = 
GrpcClientStreamingService.class.getMethod("clientStreaming", 
io.grpc.stub.StreamObserver.class);
+        ResponseType returnType = OpenApiUtils.parseGrpcReturnType(method);
+        assertThat(returnType.getName(), is("ROOT"));
+        assertThat(returnType.getType(), is("object"));
+        assertThat(returnType.getRefs(), notNullValue());
+        assertThat(returnType.getRefs(), hasSize(2));
+    }
+
+    @Test
+    void testGenerateGrpcDocumentResponseWithStreamObserver() throws Exception 
{
+        Method method = GrpcTestService.class.getMethod("unaryCall", 
String.class, io.grpc.stub.StreamObserver.class);
+        Map<String, Object> response = 
OpenApiUtils.generateGrpcDocumentResponse("/grpc/unaryCall", method);
+        assertThat(response.containsKey("200"), is(true));
+        assertThat(response.containsKey("404"), is(true));
+        assertThat(response.containsKey("409"), is(false));
+    }
+
+    @Test
+    void testGenerateGrpcDocumentResponseVoid() throws Exception {
+        Method method = GrpcTestService.class.getMethod("noStreamObserver", 
String.class);
+        Map<String, Object> response = 
OpenApiUtils.generateGrpcDocumentResponse("/grpc/noStream", method);
+        assertThat(response.containsKey("200"), is(true));
+        assertThat(response.containsKey("404"), is(true));
+    }
+
+    // --- buildDocumentJson dispatch tests ---
+
+    @Test
+    void testBuildDocumentJsonHttp() throws Exception {
+        Method method = SpringMvcController.class.getMethod("query", 
String.class);
+        String json = OpenApiUtils.buildDocumentJson(Arrays.asList("tag1"), 
"/test/query", method, RpcTypeEnum.HTTP);
+        JsonObject doc = JsonParser.parseString(json).getAsJsonObject();
+        assertThat(doc.has("requestParameters"), is(true));
+        assertThat(doc.has("responseParameters"), is(true));
+        assertThat(doc.has("parameters"), is(false));
+        assertThat(doc.has("responseType"), is(false));
+        JsonObject responses = doc.getAsJsonObject("responses");
+        assertThat(responses.has("200"), is(true));
+        assertThat(responses.has("404"), is(true));
+        assertThat(responses.has("409"), is(true));
+    }
+
+    @Test
+    void testBuildDocumentJsonGrpc() throws Exception {
+        Method method = GrpcTestService.class.getMethod("unaryCall", 
String.class, io.grpc.stub.StreamObserver.class);
+        String json = OpenApiUtils.buildDocumentJson(Arrays.asList("tag1"), 
"/grpc/unaryCall", method, RpcTypeEnum.GRPC);
+        JsonObject doc = JsonParser.parseString(json).getAsJsonObject();
+        assertThat(doc.has("requestParameters"), is(true));
+        assertThat(doc.has("responseParameters"), is(true));
+        assertThat(doc.has("parameters"), is(false));
+        assertThat(doc.has("responseType"), is(false));
+        JsonObject responses = doc.getAsJsonObject("responses");
+        assertThat(responses.has("200"), is(true));
+        assertThat(responses.has("404"), is(true));
+        assertThat(responses.has("409"), is(false));
+    }
+
+    @Test
+    void testBuildDocumentJsonRpc() throws Exception {
+        Method method = DubboTestService.class.getMethod("findById", 
String.class);
+        String json = OpenApiUtils.buildDocumentJson(Arrays.asList("tag1"), 
"/dubbo/findById", method, RpcTypeEnum.DUBBO);
+        JsonObject doc = JsonParser.parseString(json).getAsJsonObject();
+        assertThat(doc.has("requestParameters"), is(true));
+        assertThat(doc.has("responseParameters"), is(true));
+        assertThat(doc.has("parameters"), is(false));
+        assertThat(doc.has("responseType"), is(false));
+        JsonObject responses = doc.getAsJsonObject("responses");
+        assertThat(responses.has("200"), is(true));
+        assertThat(responses.has("404"), is(true));
+        assertThat(responses.has("409"), is(false));
+    }
+
+    // --- Inner types (must be after all methods per checkstyle 
InnerTypeLast) ---
+
+    public static class DubboTest {
+
+        private String id;
+
+        private String name;
+
+        public DubboTest() {
+        }
+
+        public String getId() {
+            return id;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+
+    public static class DubboTestService {
+
+        public DubboTest findById(final String id) {
+            return null;
+        }
+
+        public DubboTest insert(final DubboTest dubboTest) {
+            return null;
+        }
+
+        public List<DubboTest> findAll() {
+            return null;
+        }
+    }
+
+    @RestController
+    @RequestMapping("/test")
+    public static class SpringMvcController {
+
+        @RequestMapping("/query")
+        public String query(@RequestParam("name") final String name) {
+            return "";
+        }
+
+        @RequestMapping("/{id}")
+        public String getByPath(@PathVariable("id") final String id) {
+            return "";
+        }
+
+        @RequestMapping("/{id}/detail")
+        public String getByPathWithQuery(@PathVariable("id") final String id, 
@RequestParam("name") final String name) {
+            return "";
+        }
+    }
+
+    public static class GrpcTestClass {
+
+        private String id;
+
+        private String name;
+
+        public GrpcTestClass() {
+        }
+
+        public String getId() {
+            return id;
+        }
+
+        public String getName() {
+            return name;
+        }
+    }
+
+    public static class GrpcTestService {
+
+        public void unaryCall(final String request, final 
io.grpc.stub.StreamObserver<GrpcTestClass> responseObserver) {
+        }
+
+        public void unaryCallComplex(final GrpcTestClass request, final 
io.grpc.stub.StreamObserver<GrpcTestClass> responseObserver) {
+        }
+
+        public String noStreamObserver(final String request) {
+            return null;
+        }
+    }
+
+    public static class GrpcClientStreamingService {
+
+        public io.grpc.stub.StreamObserver<String> clientStreaming(
+                final io.grpc.stub.StreamObserver<GrpcTestClass> 
responseObserver) {
+            return null;
+        }
+    }
+
+    public static class ProtobufTestService {
+
+        public TestRequest getTestRequest() {
+            return null;
+        }
+
+        public TestRequest sendTestRequest(final TestRequest request) {
+            return null;
+        }
+
+        public Empty getEmpty() {
+            return null;
+        }
+    }
+
+    public static class RpcComplexParamService {
+
+        public String batchInsert(final List<String> ids) {
+            return null;
+        }
+
+        public String searchByMap(final Map<String, String> params) {
+            return null;
+        }
+
+        public void deleteById(final String id) {
+        }
+    }
+}
diff --git a/shenyu-client/shenyu-client-core/src/test/proto/test.proto 
b/shenyu-client/shenyu-client-core/src/test/proto/test.proto
new file mode 100644
index 0000000000..fc3e0d305d
--- /dev/null
+++ b/shenyu-client/shenyu-client-core/src/test/proto/test.proto
@@ -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.
+
+syntax = "proto3";
+
+package org.apache.shenyu.client.core.test;
+
+option java_package = "org.apache.shenyu.client.core.test";
+
+enum Status {
+  UNKNOWN = 0;
+  ACTIVE = 1;
+  INACTIVE = 2;
+}
+
+message Address {
+  string street = 1;
+  string city = 2;
+}
+
+message TestRequest {
+  string id = 1;
+  int32 count = 2;
+  bool enabled = 3;
+  Status status = 4;
+  Address address = 5;
+  repeated string tags = 6;
+  repeated Address addresses = 7;
+}
diff --git 
a/shenyu-client/shenyu-client-sofa/src/main/java/org/apache/shenyu/client/sofa/SofaServiceEventListener.java
 
b/shenyu-client/shenyu-client-sofa/src/main/java/org/apache/shenyu/client/sofa/SofaServiceEventListener.java
index 167c580da6..bc3d40ac1c 100644
--- 
a/shenyu-client/shenyu-client-sofa/src/main/java/org/apache/shenyu/client/sofa/SofaServiceEventListener.java
+++ 
b/shenyu-client/shenyu-client-sofa/src/main/java/org/apache/shenyu/client/sofa/SofaServiceEventListener.java
@@ -37,9 +37,9 @@ import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.aop.support.AopUtils;
 import org.springframework.context.ApplicationContext;
-import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.core.annotation.AnnotatedElementUtils;
 import org.springframework.lang.NonNull;
+import org.springframework.lang.Nullable;
 import org.springframework.util.ReflectionUtils;
 
 import java.lang.annotation.Annotation;
@@ -102,25 +102,41 @@ public class SofaServiceEventListener extends 
AbstractContextRefreshedEventListe
         return ShenyuSofaClient.class;
     }
 
+    @Override
+    protected void handleClass(final Class<?> clazz,
+                               final ServiceFactoryBean bean,
+                               @NonNull final ShenyuSofaClient 
beanShenyuClient,
+                               final String superPath) {
+        List<String> namespaceIds = super.getNamespace();
+        Method[] methods = ReflectionUtils.getDeclaredMethods(clazz);
+        for (String namespaceId : namespaceIds) {
+            for (Method method : methods) {
+                final MetaDataRegisterDTO metaData = buildMetaDataDTO(bean, 
beanShenyuClient,
+                        buildApiPath(method, superPath, null), clazz, method, 
namespaceId);
+                getPublisher().publishEvent(metaData);
+                getMetaDataMap().put(method, metaData);
+            }
+        }
+    }
+
     @Override
     protected String buildApiPath(final Method method,
                                   final String superPath,
-                                  @NonNull final ShenyuSofaClient 
shenyuSofaClient) {
+                                  @Nullable final ShenyuSofaClient 
shenyuSofaClient) {
         final String contextPath = this.getContextPath();
         return superPath.contains("*") ? pathJoin(contextPath, 
superPath.replace("*", ""), method.getName())
-                : pathJoin(contextPath, superPath, shenyuSofaClient.path());
+                : pathJoin(contextPath, superPath, 
Objects.requireNonNull(shenyuSofaClient).path());
     }
 
     @Override
     protected MetaDataRegisterDTO buildMetaDataDTO(final ServiceFactoryBean 
serviceBean,
                                                    @NonNull final 
ShenyuSofaClient shenyuSofaClient,
-                                                   final String superPath,
+                                                   final String path,
                                                    final Class<?> clazz,
                                                    final Method method,
                                                    final String namespaceId) {
         String appName = this.getAppName();
         String contextPath = this.getContextPath();
-        String path = pathJoin(contextPath, superPath, 
shenyuSofaClient.path());
         String serviceName = serviceBean.getInterfaceClass().getName();
         String desc = shenyuSofaClient.desc();
         String configRuleName = shenyuSofaClient.ruleName();
@@ -157,11 +173,18 @@ public class SofaServiceEventListener extends 
AbstractContextRefreshedEventListe
     }
 
     @Override
-    public void onApplicationEvent(final ContextRefreshedEvent 
contextRefreshedEvent) {
-        Map<String, ServiceFactoryBean> serviceBean = 
contextRefreshedEvent.getApplicationContext().getBeansOfType(ServiceFactoryBean.class);
-        for (Map.Entry<String, ServiceFactoryBean> entry : 
serviceBean.entrySet()) {
-            handler(entry.getValue());
+    protected Class<?> getCorrectedClass(final ServiceFactoryBean bean) {
+        Object targetProxy;
+        try {
+            targetProxy = ((Service) 
Objects.requireNonNull(bean.getObject())).getTarget();
+        } catch (Exception e) {
+            LOG.error("failed to get sofa target class", e);
+            return bean.getClass();
         }
+        if (AopUtils.isAopProxy(targetProxy)) {
+            return AopUtils.getTargetClass(targetProxy);
+        }
+        return targetProxy.getClass();
     }
 
     @Override
@@ -179,42 +202,6 @@ public class SofaServiceEventListener extends 
AbstractContextRefreshedEventListe
         return Sextet.with(values, consume, produce, apiHttpMethodEnums, 
RpcTypeEnum.SOFA, version);
     }
 
-    private void handler(final ServiceFactoryBean serviceBean) {
-        Class<?> clazz;
-        Object targetProxy;
-        try {
-            targetProxy = ((Service) 
Objects.requireNonNull(serviceBean.getObject())).getTarget();
-            clazz = targetProxy.getClass();
-        } catch (Exception e) {
-            LOG.error("failed to get sofa target class", e);
-            return;
-        }
-        if (AopUtils.isAopProxy(targetProxy)) {
-            clazz = AopUtils.getTargetClass(targetProxy);
-        }
-        final ShenyuSofaClient beanSofaClient = 
AnnotatedElementUtils.findMergedAnnotation(clazz, ShenyuSofaClient.class);
-        final String superPath = buildApiSuperPath(clazz, beanSofaClient);
-        List<String> namespaceIds = super.getNamespace();
-        if (superPath.contains("*") && Objects.nonNull(beanSofaClient)) {
-            Method[] declaredMethods = 
ReflectionUtils.getDeclaredMethods(clazz);
-            for (String namespaceId : namespaceIds) {
-                for (Method declaredMethod : declaredMethods) {
-                    getPublisher().publishEvent(buildMetaDataDTO(serviceBean, 
beanSofaClient, superPath, clazz, declaredMethod, namespaceId));
-                }
-            }
-            return;
-        }
-        Method[] methods = ReflectionUtils.getUniqueDeclaredMethods(clazz);
-        for (String namespaceId : namespaceIds) {
-            for (Method method : methods) {
-                ShenyuSofaClient methodSofaClient = 
AnnotatedElementUtils.findMergedAnnotation(method, ShenyuSofaClient.class);
-                if (Objects.nonNull(methodSofaClient)) {
-                    getPublisher().publishEvent(buildMetaDataDTO(serviceBean, 
methodSofaClient, superPath, clazz, method, namespaceId));
-                }
-            }
-        }
-    }
-
     private String buildRpcExt(final ShenyuSofaClient shenyuSofaClient) {
         SofaRpcExt build = SofaRpcExt.builder()
             .loadbalance(shenyuSofaClient.loadBalance())
diff --git 
a/shenyu-client/shenyu-client-sofa/src/test/java/org/apache/shenyu/client/sofa/SofaServiceEventListenerTest.java
 
b/shenyu-client/shenyu-client-sofa/src/test/java/org/apache/shenyu/client/sofa/SofaServiceEventListenerTest.java
index a38615c987..5e0bc07b6e 100644
--- 
a/shenyu-client/shenyu-client-sofa/src/test/java/org/apache/shenyu/client/sofa/SofaServiceEventListenerTest.java
+++ 
b/shenyu-client/shenyu-client-sofa/src/test/java/org/apache/shenyu/client/sofa/SofaServiceEventListenerTest.java
@@ -204,11 +204,12 @@ public class SofaServiceEventListenerTest {
         String expectedPath = "/sofa/findByIdsAndName/path";
         String expectedRpcExt = 
"{\"loadbalance\":\"loadBalance\",\"retries\":0,\"timeout\":0}";
 
+        String apiPath = sofaServiceEventListener.buildApiPath(method, 
SUPER_PATH_NOT_CONTAINS_STAR, shenyuSofaClient);
         MetaDataRegisterDTO realMetaDataRegisterDTO = sofaServiceEventListener
                 .buildMetaDataDTO(
                         serviceFactoryBean,
                         shenyuSofaClient,
-                        SUPER_PATH_NOT_CONTAINS_STAR,
+                        apiPath,
                         SofaServiceEventListener.class,
                         method, Constants.SYS_DEFAULT_NAMESPACE_ID);
         MetaDataRegisterDTO expectedMetaDataRegisterDTO = MetaDataRegisterDTO
diff --git 
a/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-logging-rocketmq/compose/script/e2e-logging-rocketmq-compose.sh
 
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-logging-rocketmq/compose/script/e2e-logging-rocketmq-compose.sh
index 4049b338fd..1967c16f0d 100644
--- 
a/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-logging-rocketmq/compose/script/e2e-logging-rocketmq-compose.sh
+++ 
b/shenyu-e2e/shenyu-e2e-case/shenyu-e2e-case-logging-rocketmq/compose/script/e2e-logging-rocketmq-compose.sh
@@ -31,16 +31,18 @@ SYNC_ARRAY=("websocket" "http" "zookeeper")
 docker network create -d bridge shenyu
 
 for sync in "${SYNC_ARRAY[@]}"; do
+  
sync_compose_file="$SHENYU_TESTCASE_DIR"/compose/sync/shenyu-sync-"${sync}".yml
   echo -e "------------------\n"
   echo "[Start ${sync} synchronous] create shenyu-admin-${sync}.yml 
shenyu-bootstrap-${sync}.yml "
-  docker compose -f 
"$SHENYU_TESTCASE_DIR"/compose/sync/shenyu-sync-"${sync}".yml up -d --quiet-pull
+  docker compose -f "${sync_compose_file}" up -d --quiet-pull || true
   sleep 30s
-  sh "$SHENYU_TESTCASE_DIR"/k8s/script/healthcheck.sh 
http://localhost:31095/actuator/health
-  sh "$SHENYU_TESTCASE_DIR"/k8s/script/healthcheck.sh 
http://localhost:31195/actuator/health
+  sh "$SHENYU_TESTCASE_DIR"/k8s/script/healthcheck.sh 
http://localhost:31095/actuator/health || exit 1
+  docker compose -f "${sync_compose_file}" up -d shenyu-bootstrap
+  sh "$SHENYU_TESTCASE_DIR"/k8s/script/healthcheck.sh 
http://localhost:31195/actuator/health || exit 1
   docker compose -f "${PRGDIR}"/shenyu-rocketmq-compose.yml up -d --quiet-pull
   docker compose -f "${PRGDIR}"/shenyu-examples-http-compose.yml up -d 
--quiet-pull
   sleep 30s
-  sh "$SHENYU_TESTCASE_DIR"/k8s/script/healthcheck.sh 
http://localhost:31189/actuator/health
+  sh "$SHENYU_TESTCASE_DIR"/k8s/script/healthcheck.sh 
http://localhost:31189/actuator/health || exit 1
   sleep 10s
   docker ps -a
   ## run e2e-test
@@ -60,7 +62,7 @@ for sync in "${SYNC_ARRAY[@]}"; do
     docker compose -f "${PRGDIR}"/shenyu-rocketmq-compose.yml logs
     exit 1
   fi
-  docker compose -f 
"$SHENYU_TESTCASE_DIR"/compose/sync/shenyu-sync-"${sync}".yml down
+  docker compose -f "${sync_compose_file}" down
   docker compose -f "${PRGDIR}"/shenyu-rocketmq-compose.yml down
   docker compose -f "${PRGDIR}"/shenyu-examples-http-compose.yml down
   echo "[Remove ${sync} synchronous] delete shenyu-admin-${sync}.yml 
shenyu-bootstrap-${sync}.yml "
diff --git 
a/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service-annotation/src/main/java/org/apache/shenyu/examples/apache/dubbo/service/annotation/impl/DubboProtobufServiceImpl.java
 
b/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service-annotation/src/main/java/org/apache/shenyu/examples/apache/dubbo/service/annotation/impl/DubboProtobufServiceImpl.java
index e589bd0c53..8d8a93f345 100644
--- 
a/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service-annotation/src/main/java/org/apache/shenyu/examples/apache/dubbo/service/annotation/impl/DubboProtobufServiceImpl.java
+++ 
b/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service-annotation/src/main/java/org/apache/shenyu/examples/apache/dubbo/service/annotation/impl/DubboProtobufServiceImpl.java
@@ -19,27 +19,33 @@ package 
org.apache.shenyu.examples.apache.dubbo.service.annotation.impl;
 
 import com.google.protobuf.Empty;
 import org.apache.dubbo.config.annotation.DubboService;
+import org.apache.shenyu.client.apidocs.annotations.ApiDoc;
+import org.apache.shenyu.client.apidocs.annotations.ApiModule;
 import org.apache.shenyu.client.dubbo.common.annotation.ShenyuDubboClient;
 import org.apache.shenyu.examples.dubbo.api.service.DubboProtobufService;
 import org.apache.shenyu.examples.dubbo.api.service.DubboTestProtobuf;
 
 @DubboService(serialization = "protobuf")
 @ShenyuDubboClient(value = "/protobufSerialization")
+@ApiModule(value = "dubboProtobufService")
 public class DubboProtobufServiceImpl implements DubboProtobufService {
 
     @ShenyuDubboClient("/insert")
+    @ApiDoc(desc = "insert")
     @Override
     public DubboTestProtobuf insert(final DubboTestProtobuf request) {
         return request;
     }
 
     @ShenyuDubboClient("/update")
+    @ApiDoc(desc = "update")
     @Override
     public Empty update(final DubboTestProtobuf request) {
         return Empty.getDefaultInstance();
     }
 
     @ShenyuDubboClient("/findOne")
+    @ApiDoc(desc = "findOne")
     @Override
     public DubboTestProtobuf findOne(final Empty request) {
         return 
DubboTestProtobuf.newBuilder().setId("1").setName("test1").build();
diff --git 
a/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service-xml/src/main/java/org/apache/shenyu/examples/apache/dubbo/service/xml/impl/DubboProtobufServiceImpl.java
 
b/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service-xml/src/main/java/org/apache/shenyu/examples/apache/dubbo/service/xml/impl/DubboProtobufServiceImpl.java
index 6084bc2470..c8f5135e14 100644
--- 
a/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service-xml/src/main/java/org/apache/shenyu/examples/apache/dubbo/service/xml/impl/DubboProtobufServiceImpl.java
+++ 
b/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service-xml/src/main/java/org/apache/shenyu/examples/apache/dubbo/service/xml/impl/DubboProtobufServiceImpl.java
@@ -18,6 +18,8 @@
 package org.apache.shenyu.examples.apache.dubbo.service.xml.impl;
 
 import com.google.protobuf.Empty;
+import org.apache.shenyu.client.apidocs.annotations.ApiDoc;
+import org.apache.shenyu.client.apidocs.annotations.ApiModule;
 import org.apache.shenyu.client.dubbo.common.annotation.ShenyuDubboClient;
 import org.apache.shenyu.examples.dubbo.api.service.DubboProtobufService;
 import org.apache.shenyu.examples.dubbo.api.service.DubboTestProtobuf;
@@ -25,21 +27,25 @@ import org.springframework.stereotype.Service;
 
 @Service("dubboProtobufService")
 @ShenyuDubboClient(value = "/protobufSerialization")
+@ApiModule(value = "dubboProtobufService")
 public class DubboProtobufServiceImpl implements DubboProtobufService {
 
     @ShenyuDubboClient("/insert")
+    @ApiDoc(desc = "insert")
     @Override
     public DubboTestProtobuf insert(final DubboTestProtobuf request) {
         return request;
     }
 
     @ShenyuDubboClient("/update")
+    @ApiDoc(desc = "update")
     @Override
     public Empty update(final DubboTestProtobuf request) {
         return Empty.getDefaultInstance();
     }
 
     @ShenyuDubboClient("/findOne")
+    @ApiDoc(desc = "findOne")
     @Override
     public DubboTestProtobuf findOne(final Empty request) {
         return 
DubboTestProtobuf.newBuilder().setId("1").setName("test1").build();
diff --git 
a/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service/src/main/java/org/apache/shenyu/examples/apache/dubbo/service/impl/DubboProtobufServiceImpl.java
 
b/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service/src/main/java/org/apache/shenyu/examples/apache/dubbo/service/impl/DubboProtobufServiceImpl.java
index da96e87072..d2efd24320 100644
--- 
a/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service/src/main/java/org/apache/shenyu/examples/apache/dubbo/service/impl/DubboProtobufServiceImpl.java
+++ 
b/shenyu-examples/shenyu-examples-dubbo/shenyu-examples-apache-dubbo-service/src/main/java/org/apache/shenyu/examples/apache/dubbo/service/impl/DubboProtobufServiceImpl.java
@@ -18,6 +18,8 @@
 package org.apache.shenyu.examples.apache.dubbo.service.impl;
 
 import com.google.protobuf.Empty;
+import org.apache.shenyu.client.apidocs.annotations.ApiDoc;
+import org.apache.shenyu.client.apidocs.annotations.ApiModule;
 import org.apache.shenyu.client.dubbo.common.annotation.ShenyuDubboClient;
 import org.apache.shenyu.examples.dubbo.api.service.DubboProtobufService;
 import org.apache.shenyu.examples.dubbo.api.service.DubboTestProtobuf;
@@ -25,21 +27,25 @@ import org.springframework.stereotype.Service;
 
 @ShenyuDubboClient(value = "/protobufSerialization")
 @Service("dubboProtobufService")
+@ApiModule(value = "dubboProtobufService")
 public class DubboProtobufServiceImpl implements DubboProtobufService {
 
     @ShenyuDubboClient("/insert")
+    @ApiDoc(desc = "insert")
     @Override
     public DubboTestProtobuf insert(final DubboTestProtobuf request) {
         return request;
     }
 
     @ShenyuDubboClient("/update")
+    @ApiDoc(desc = "update")
     @Override
     public Empty update(final DubboTestProtobuf request) {
         return Empty.getDefaultInstance();
     }
 
     @ShenyuDubboClient("/findOne")
+    @ApiDoc(desc = "findOne")
     @Override
     public DubboTestProtobuf findOne(final Empty request) {
         return 
DubboTestProtobuf.newBuilder().setId("1").setName("test1").build();

Reply via email to