Repository: gogf/gf

Issue:
net/goai: g.Meta is passed as parameters of a request
### Description

Here is the API definition:
```go
type DrainReq struct {
    g.Meta    `path:"/cluster/{cluster_id}/node/{name}/drain" method:"post" tags:"节点管理" summary:"驱逐节点" dc:"该接口会先设置节点不可调度，再进行驱逐"`
    ClusterId string `json:"cluster_id" v:"required" dc:"集群ID"`
    Name      string `json:"name"       v:"required" dc:"节点名称"`
}
type DrainRes struct{}
```

Here is the generated API document:

![Image](https://github.com/user-attachments/assets/d7f8c1d2-8e9f-4cbc-a70f-59d569583795)

### Additional

_No response_

Hints from issue comments:
@github-actions: Hello @gqcn. We like your proposal/feedback and would appreciate a contribution via a Pull Request by you or another community member. We thank you in advance for your contribution and are looking forward to reviewing it!
你好 @gqcn。我们喜欢您的提案/反馈，并希望您或其他社区成员通过拉取请求做出贡献。我们提前感谢您的贡献，并期待对其进行审查。

Solution patch:
diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml
index a062493ea..d4cabb4b4 100644
--- a/.github/workflows/golangci-lint.yml
+++ b/.github/workflows/golangci-lint.yml
@@ -36,6 +36,8 @@ jobs:
     steps:
       - name: Checkout
         uses: actions/checkout@v5
+        with:
+          fetch-depth: 0
       - name: Setup Golang ${{ matrix.go-version }}
         uses: actions/setup-go@v5
         with:
diff --git a/.golangci.yml b/.golangci.yml
index 704df9685..6a5fae5d6 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -196,6 +196,19 @@ formatters:
       no-lex-order: false
     gofmt:
       simplify: true
+      rewrite-rules:
+        - pattern: 'interface{}'
+          replacement: 'any'
+        - pattern: 'reflect.Ptr'
+          replacement: 'reflect.Pointer'
+        - pattern: 'ioutil.ReadAll'
+          replacement: 'io.ReadAll'
+        - pattern: 'ioutil.WriteFile'
+          replacement: 'os.WriteFile'
+        - pattern: 'ioutil.ReadFile'
+          replacement: 'os.ReadFile'
+        - pattern: 'ioutil.NopCloser'
+          replacement: 'io.NopCloser'
     goimports:
       local-prefixes:
         - github.com/gogf/gf/v2
diff --git a/net/goai/goai_path.go b/net/goai/goai_path.go
index 516def487..0914ffeba 100644
--- a/net/goai/goai_path.go
+++ b/net/goai/goai_path.go
@@ -275,7 +275,7 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
 	}
 
 	// Remove operation body duplicated properties.
-	oai.removeOperationDuplicatedProperties(operation)
+	oai.removeOperationDuplicatedProperties(&operation)
 
 	// Assign to certain operation attribute.
 	switch gstr.ToUpper(in.Method) {
@@ -317,7 +317,7 @@ func (oai *OpenApiV3) addPath(in addPathInput) error {
 	return nil
 }
 
-func (oai *OpenApiV3) removeOperationDuplicatedProperties(operation Operation) {
+func (oai *OpenApiV3) removeOperationDuplicatedProperties(operation *Operation) {
 	if len(operation.Parameters) == 0 {
 		// Nothing to do.
 		return
@@ -352,6 +352,10 @@ func (oai *OpenApiV3) removeOperationDuplicatedProperties(operation Operation) {
 				requestBodyContent.Schema.Value = newSchema
 				newSchema.Required = oai.removeItemsFromArray(newSchema.Required, duplicatedParameterNames)
 				newSchema.Properties.Removes(duplicatedParameterNames)
+				// remove request body if there are no properties left
+				if newSchema.Properties.refs.IsEmpty() {
+					operation.RequestBody = nil
+				}
 				continue
 			}
 		}
diff --git a/util/gpage/gpage.go b/util/gpage/gpage.go
index 9b5540436..63fedf6d1 100644
--- a/util/gpage/gpage.go
+++ b/util/gpage/gpage.go
@@ -7,6 +7,7 @@
 // Package gpage provides useful paging functionality for web pages.
 //
 // Deprecated: wrap this pagination html content in business layer.
+// Will be removed in version 3.0.
 package gpage
 
 import (



Test patch:
diff --git a/net/goai/goai_z_unit_issue_test.go b/net/goai/goai_z_unit_issue_test.go
index 4259045b0..40793db13 100644
--- a/net/goai/goai_z_unit_issue_test.go
+++ b/net/goai/goai_z_unit_issue_test.go
@@ -349,3 +349,59 @@ func Test_Issue3235(t *testing.T) {
 			"test user desc")
 	})
 }
+
+type Issue4247NoBodyReq struct {
+	g.Meta    `path:"/cluster/{cluster_id}/node/{name}/drain" method:"post" tags:"节点管理" summary:"驱逐节点" dc:"该接口会先设置节点不可调度，再进行驱逐"`
+	ClusterId string `json:"cluster_id" v:"required" dc:"集群ID" `
+	Name      string `json:"name"       v:"required" dc:"节点名称"`
+}
+type Issue4247NoBodyRes struct{}
+
+type Issue4247HasBodyReq struct {
+	g.Meta    `path:"/cluster/{cluster_id}/node/{name}/join" method:"post" tags:"节点管理" summary:"增加节点" dc:"增加节点"`
+	ClusterId string `json:"cluster_id" v:"required" dc:"集群ID" `
+	Name      string `json:"name"       v:"required" dc:"节点名称"`
+	Type      string `json:"type"       v:"required" dc:"节点类型"`
+}
+type Issue4247HasBodyRes struct{}
+
+type Issue4247 struct{}
+
+func (Issue4247) Issue4247NoBody(ctx context.Context, req *Issue4247NoBodyReq) (res *Issue4247NoBodyRes, err error) {
+	res = &Issue4247NoBodyRes{}
+	return
+}
+
+func (Issue4247) Issue4247HasBody(ctx context.Context, req *Issue4247HasBodyReq) (res *Issue4247HasBodyRes, err error) {
+	res = &Issue4247HasBodyRes{}
+	return
+}
+
+// https://github.com/gogf/gf/issues/4247
+func Test_Issue4247(t *testing.T) {
+	gtest.C(t, func(t *gtest.T) {
+		s := g.Server(guid.S())
+		s.Use(ghttp.MiddlewareHandlerResponse)
+		s.Group("/", func(group *ghttp.RouterGroup) {
+			group.Bind(
+				new(Issue4247),
+			)
+		})
+		s.SetLogger(nil)
+		s.SetOpenApiPath("/api.json")
+		s.SetDumpRouterMap(false)
+		s.Start()
+
+		defer s.Shutdown()
+		time.Sleep(100 * time.Millisecond)
+
+		c := g.Client()
+		c.SetPrefix(fmt.Sprintf("http://127.0.0.1:%d", s.GetListenedPort()))
+		apiContent := c.GetBytes(ctx, "/api.json")
+		j, err := gjson.LoadJson(apiContent)
+		t.AssertNil(err)
+		requestBody := `{"content":{"application/json":{"schema":{"description":"增加节点","properties":{"type":{"description":"节点类型","format":"string","type":"string"}},"required":["type"],"type":"object"}}}}`
+		t.Assert(j.Get(`paths./cluster/{cluster_id}/node/{name}/drain.post.requestBody`).IsEmpty(), true)
+		t.Assert(j.Get(`paths./cluster/{cluster_id}/node/{name}/join.post.requestBody`).String(), requestBody)
+	})
+}