Repository: zeromicro/go-zero
Pull request #2436 (2022-09-22 06:55:57)

Readme Tweak


Patch:
diff --git a/core/mapping/yamlunmarshaler.go b/core/mapping/yamlunmarshaler.go
index 4c41ddfb..9ae89214 100644
--- a/core/mapping/yamlunmarshaler.go
+++ b/core/mapping/yamlunmarshaler.go
@@ -63,7 +63,7 @@ func cleanupMapValue(v interface{}) interface{} {
 	}
 }
 
-func unmarshal(unmarshaler *Unmarshaler, o interface{}, v interface{}) error {
+func unmarshal(unmarshaler *Unmarshaler, o, v interface{}) error {
 	if m, ok := o.(map[string]interface{}); ok {
 		return unmarshaler.Unmarshal(m, v)
 	}
diff --git a/core/stores/mon/collection.go b/core/stores/mon/collection.go
index 556ad31d..229d83cc 100644
--- a/core/stores/mon/collection.go
+++ b/core/stores/mon/collection.go
@@ -83,12 +83,12 @@ type (
 		// FindOneAndReplace returns at most one document that matches the filter. If the filter
 		// matches multiple documents, FindOneAndReplace returns the first document in the
 		// collection that matches the filter.
-		FindOneAndReplace(ctx context.Context, filter interface{}, replacement interface{},
+		FindOneAndReplace(ctx context.Context, filter, replacement interface{},
 			opts ...*mopt.FindOneAndReplaceOptions) (*mongo.SingleResult, error)
 		// FindOneAndUpdate returns at most one document that matches the filter. If the filter
 		// matches multiple documents, FindOneAndUpdate returns the first document in the
 		// collection that matches the filter.
-		FindOneAndUpdate(ctx context.Context, filter interface{}, update interface{},
+		FindOneAndUpdate(ctx context.Context, filter, update interface{},
 			opts ...*mopt.FindOneAndUpdateOptions) (*mongo.SingleResult, error)
 		// Indexes returns the index view for this collection.
 		Indexes() mongo.IndexView
@@ -99,16 +99,16 @@ type (
 		InsertOne(ctx context.Context, document interface{}, opts ...*mopt.InsertOneOptions) (
 			*mongo.InsertOneResult, error)
 		// ReplaceOne replaces at most one document that matches the filter.
-		ReplaceOne(ctx context.Context, filter interface{}, replacement interface{},
+		ReplaceOne(ctx context.Context, filter, replacement interface{},
 			opts ...*mopt.ReplaceOptions) (*mongo.UpdateResult, error)
 		// UpdateByID updates a single document matching the provided filter.
-		UpdateByID(ctx context.Context, id interface{}, update interface{},
+		UpdateByID(ctx context.Context, id, update interface{},
 			opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error)
 		// UpdateMany updates the provided documents.
-		UpdateMany(ctx context.Context, filter interface{}, update interface{},
+		UpdateMany(ctx context.Context, filter, update interface{},
 			opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error)
 		// UpdateOne updates a single document matching the provided filter.
-		UpdateOne(ctx context.Context, filter interface{}, update interface{},
+		UpdateOne(ctx context.Context, filter, update interface{},
 			opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error)
 		// Watch returns a change stream cursor used to receive notifications of changes to the collection.
 		Watch(ctx context.Context, pipeline interface{}, opts ...*mopt.ChangeStreamOptions) (
@@ -359,7 +359,7 @@ func (c *decoratedCollection) FindOneAndReplace(ctx context.Context, filter inte
 	return
 }
 
-func (c *decoratedCollection) FindOneAndUpdate(ctx context.Context, filter interface{}, update interface{},
+func (c *decoratedCollection) FindOneAndUpdate(ctx context.Context, filter, update interface{},
 	opts ...*mopt.FindOneAndUpdateOptions) (res *mongo.SingleResult, err error) {
 	ctx, span := startSpan(ctx, findOneAndUpdate)
 	defer func() {
@@ -420,7 +420,7 @@ func (c *decoratedCollection) InsertOne(ctx context.Context, document interface{
 	return
 }
 
-func (c *decoratedCollection) ReplaceOne(ctx context.Context, filter interface{}, replacement interface{},
+func (c *decoratedCollection) ReplaceOne(ctx context.Context, filter, replacement interface{},
 	opts ...*mopt.ReplaceOptions) (res *mongo.UpdateResult, err error) {
 	ctx, span := startSpan(ctx, replaceOne)
 	defer func() {
@@ -440,7 +440,7 @@ func (c *decoratedCollection) ReplaceOne(ctx context.Context, filter interface{}
 	return
 }
 
-func (c *decoratedCollection) UpdateByID(ctx context.Context, id interface{}, update interface{},
+func (c *decoratedCollection) UpdateByID(ctx context.Context, id, update interface{},
 	opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
 	ctx, span := startSpan(ctx, updateByID)
 	defer func() {
@@ -460,7 +460,7 @@ func (c *decoratedCollection) UpdateByID(ctx context.Context, id interface{}, up
 	return
 }
 
-func (c *decoratedCollection) UpdateMany(ctx context.Context, filter interface{}, update interface{},
+func (c *decoratedCollection) UpdateMany(ctx context.Context, filter, update interface{},
 	opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
 	ctx, span := startSpan(ctx, updateMany)
 	defer func() {
@@ -480,7 +480,7 @@ func (c *decoratedCollection) UpdateMany(ctx context.Context, filter interface{}
 	return
 }
 
-func (c *decoratedCollection) UpdateOne(ctx context.Context, filter interface{}, update interface{},
+func (c *decoratedCollection) UpdateOne(ctx context.Context, filter, update interface{},
 	opts ...*mopt.UpdateOptions) (res *mongo.UpdateResult, err error) {
 	ctx, span := startSpan(ctx, updateOne)
 	defer func() {
diff --git a/core/stores/mon/model.go b/core/stores/mon/model.go
index c963f988..1f27ff72 100644
--- a/core/stores/mon/model.go
+++ b/core/stores/mon/model.go
@@ -159,7 +159,7 @@ func (m *Model) FindOneAndDelete(ctx context.Context, v, filter interface{},
 }
 
 // FindOneAndReplace finds a single document and replaces it.
-func (m *Model) FindOneAndReplace(ctx context.Context, v, filter interface{}, replacement interface{},
+func (m *Model) FindOneAndReplace(ctx context.Context, v, filter, replacement interface{},
 	opts ...*mopt.FindOneAndReplaceOptions) error {
 	res, err := m.Collection.FindOneAndReplace(ctx, filter, replacement, opts...)
 	if err != nil {
@@ -170,7 +170,7 @@ func (m *Model) FindOneAndReplace(ctx context.Context, v, filter interface{}, re
 }
 
 // FindOneAndUpdate finds a single document and updates it.
-func (m *Model) FindOneAndUpdate(ctx context.Context, v, filter interface{}, update interface{},
+func (m *Model) FindOneAndUpdate(ctx context.Context, v, filter, update interface{},
 	opts ...*mopt.FindOneAndUpdateOptions) error {
 	res, err := m.Collection.FindOneAndUpdate(ctx, filter, update, opts...)
 	if err != nil {
diff --git a/core/stores/monc/cachedmodel.go b/core/stores/monc/cachedmodel.go
index 93d27694..7c1eeb84 100644
--- a/core/stores/monc/cachedmodel.go
+++ b/core/stores/monc/cachedmodel.go
@@ -192,7 +192,7 @@ func (mm *Model) InsertOneNoCache(ctx context.Context, document interface{},
 }
 
 // ReplaceOne replaces a single document in the collection, and remove the cache.
-func (mm *Model) ReplaceOne(ctx context.Context, key string, filter interface{}, replacement interface{},
+func (mm *Model) ReplaceOne(ctx context.Context, key string, filter, replacement interface{},
 	opts ...*mopt.ReplaceOptions) (*mongo.UpdateResult, error) {
 	res, err := mm.Model.ReplaceOne(ctx, filter, replacement, opts...)
 	if err != nil {
@@ -207,7 +207,7 @@ func (mm *Model) ReplaceOne(ctx context.Context, key string, filter interface{},
 }
 
 // ReplaceOneNoCache replaces a single document in the collection.
-func (mm *Model) ReplaceOneNoCache(ctx context.Context, filter interface{}, replacement interface{},
+func (mm *Model) ReplaceOneNoCache(ctx context.Context, filter, replacement interface{},
 	opts ...*mopt.ReplaceOptions) (*mongo.UpdateResult, error) {
 	return mm.Model.ReplaceOne(ctx, filter, replacement, opts...)
 }
@@ -218,7 +218,7 @@ func (mm *Model) SetCache(key string, v interface{}) error {
 }
 
 // UpdateByID updates the document with given id with update, and remove the cache.
-func (mm *Model) UpdateByID(ctx context.Context, key string, id interface{}, update interface{},
+func (mm *Model) UpdateByID(ctx context.Context, key string, id, update interface{},
 	opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error) {
 	res, err := mm.Model.UpdateByID(ctx, id, update, opts...)
 	if err != nil {
@@ -233,13 +233,13 @@ func (mm *Model) UpdateByID(ctx context.Context, key string, id interface{}, upd
 }
 
 // UpdateByIDNoCache updates the document with given id with update.
-func (mm *Model) UpdateByIDNoCache(ctx context.Context, id interface{}, update interface{},
+func (mm *Model) UpdateByIDNoCache(ctx context.Context, id, update interface{},
 	opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error) {
 	return mm.Model.UpdateByID(ctx, id, update, opts...)
 }
 
 // UpdateMany updates the documents that match filter with update, and remove the cache.
-func (mm *Model) UpdateMany(ctx context.Context, keys []string, filter interface{}, update interface{},
+func (mm *Model) UpdateMany(ctx context.Context, keys []string, filter, update interface{},
 	opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error) {
 	res, err := mm.Model.UpdateMany(ctx, filter, update, opts...)
 	if err != nil {
@@ -254,13 +254,13 @@ func (mm *Model) UpdateMany(ctx context.Context, keys []string, filter interface
 }
 
 // UpdateManyNoCache updates the documents that match filter with update.
-func (mm *Model) UpdateManyNoCache(ctx context.Context, filter interface{}, update interface{},
+func (mm *Model) UpdateManyNoCache(ctx context.Context, filter, update interface{},
 	opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error) {
 	return mm.Model.UpdateMany(ctx, filter, update, opts...)
 }
 
 // UpdateOne updates the first document that matches filter with update, and remove the cache.
-func (mm *Model) UpdateOne(ctx context.Context, key string, filter interface{}, update interface{},
+func (mm *Model) UpdateOne(ctx context.Context, key string, filter, update interface{},
 	opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error) {
 	res, err := mm.Model.UpdateOne(ctx, filter, update, opts...)
 	if err != nil {
@@ -275,7 +275,7 @@ func (mm *Model) UpdateOne(ctx context.Context, key string, filter interface{},
 }
 
 // UpdateOneNoCache updates the first document that matches filter with update.
-func (mm *Model) UpdateOneNoCache(ctx context.Context, filter interface{}, update interface{},
+func (mm *Model) UpdateOneNoCache(ctx context.Context, filter, update interface{},
 	opts ...*mopt.UpdateOptions) (*mongo.UpdateResult, error) {
 	return mm.Model.UpdateOne(ctx, filter, update, opts...)
 }
diff --git a/readme.md b/readme.md
index 4673efcd..ab3d6d94 100644
--- a/readme.md
+++ b/readme.md
@@ -1,8 +1,11 @@
-<img align="right" width="150px" src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/go-zero.png">
-
 # go-zero
+<p align="center">
+<img align="center" width="150px" src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/go-zero.png">
+</p>
 
-English | [简体中文](readme-cn.md)
+go-zero is a web and rpc framework with lots of builtin engineering practices. It’s born to ensure the stability of the busy services with resilience design and has been serving sites with tens of millions of users for years.
+
+<div align=center>
 
 [![Go](https://github.com/zeromicro/go-zero/workflows/Go/badge.svg?branch=master)](https://github.com/zeromicro/go-zero/actions)
 [![codecov](https://codecov.io/gh/zeromicro/go-zero/branch/master/graph/badge.svg)](https://codecov.io/gh/zeromicro/go-zero)
@@ -13,23 +16,20 @@ English | [简体中文](readme-cn.md)
 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
 [![Discord](https://img.shields.io/discord/794530774463414292?label=chat&logo=discord)](https://discord.gg/4JQvC5A4Fe)
 
-<a href="https://www.producthunt.com/posts/go-zero?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go&#0045;zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go&#0045;zero - A&#0032;web&#0032;&#0038;&#0032;rpc&#0032;framework&#0032;written&#0032;in&#0032;Go&#0046; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
+</div>
+
+
+## 🤷‍ What is go-zero?
+English | [简体中文](readme-cn.md)
 
-> ***Important!***
-> 
-> To upgrade from versions eariler than v1.3.0, run the following commands.
-> 
-> `go install github.com/zeromicro/go-zero/tools/goctl@latest`
-> 
-> `goctl migrate —verbose —version v1.4.0`
+<a href="https://www.producthunt.com/posts/go-zero?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-go&#0045;zero" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=334030&theme=light" alt="go&#0045;zero - A&#0032;web&#0032;&#0038;&#0032;rpc&#0032;framework&#0032;written&#0032;in&#0032;Go&#0046; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
 
-## 0. what is go-zero
 
 go-zero (listed in CNCF Landscape: [https://landscape.cncf.io/?selected=go-zero](https://landscape.cncf.io/?selected=go-zero)) is a web and rpc framework with lots of builtin engineering practices. It’s born to ensure the stability of the busy services with resilience design and has been serving sites with tens of millions of users for years.
 
 go-zero contains simple API description syntax and code generation tool called `goctl`. You can generate Go, iOS, Android, Kotlin, Dart, TypeScript, JavaScript from .api files with `goctl`.
 
-Advantages of go-zero:
+#### Advantages of go-zero:
 
 * improve the stability of the services with tens of millions of daily active users
 * builtin chained timeout control, concurrency control, rate limit, adaptive circuit breaker, adaptive load shedding, even no configuration needed
@@ -40,7 +40,7 @@ Advantages of go-zero:
 
 <img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/architecture-en.png" alt="Architecture" width="1500" />
 
-## 1. Backgrounds of go-zero
+## Backgrounds of go-zero
 
 At the beginning of 2018, we decided to re-design our system, from monolithic architecture with Java+MongoDB to microservice architecture. After research and comparison, we chose to:
 
@@ -55,7 +55,7 @@ At the beginning of 2018, we decided to re-design our system, from monolithic ar
   * easy to locate the problems
   * easy to extend the features
 
-## 2. Design considerations on go-zero
+## Design considerations on go-zero
 
 By designing the microservice architecture, we expected to ensure stability, as well as productivity. And from just the beginning, we have the following design principles:
 
@@ -69,7 +69,7 @@ By designing the microservice architecture, we expected to ensure stability, as
 
 After almost half a year, we finished the transfer from a monolithic system to microservice system and deployed on August 2018. The new system guaranteed business growth and system stability.
 
-## 3. The implementation and features of go-zero
+## The implementation and features of go-zero
 
 go-zero is a web and rpc framework that integrates lots of engineering practices. The features are mainly listed below:
 
@@ -91,27 +91,38 @@ As below, go-zero protects the system with a couple of layers and mechanisms:
 
 ![Resilience](https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/resilience-en.png)
 
-## 4. The simplified architecture that we use with go-zero
+##  The simplified architecture that we use with go-zero
 
 <img width="1067" alt="image" src="https://user-images.githubusercontent.com/1918356/171880372-5010d846-e8b1-4942-8fe2-e2bbb584f762.png">
 
-## 5. Installation
+##  Installation
 
 Run the following command under your project:
 
 ```shell
 go get -u github.com/zeromicro/go-zero
 ```
+## Upgrade
+
+To upgrade from versions eariler than v1.3.0, run the following commands.
+
+```shell
+go install github.com/zeromicro/go-zero/tools/goctl@latest
+```
+
+```shell
+goctl migrate —verbose —version v1.4.0
+```
 
-## 6. Quick Start
+##  Quick Start
 
-0. full examples can be checked out from below:
+1. full examples can be checked out from below:
 
      [Rapid development of microservice systems](https://github.com/zeromicro/zero-doc/blob/main/doc/shorturl-en.md)
 
      [Rapid development of microservice systems - multiple RPCs](https://github.com/zeromicro/zero-doc/blob/main/docs/zero/bookstore-en.md)
 
-1. install goctl
+2. install goctl
 
    `goctl`can be read as `go control`. `goctl` means not to be controlled by code, instead, we control it. The inside `go` is not `golang`. At the very beginning, I was expecting it to help us improve productivity, and make our lives easier.
 
@@ -138,7 +149,7 @@ go get -u github.com/zeromicro/go-zero
 
    make sure goctl is executable.
 
-2. create the API file, like greet.api, you can install the plugin of goctl in vs code, api syntax is supported.
+3. create the API file, like greet.api, you can install the plugin of goctl in vs code, api syntax is supported.
 
    ```go
    type (
@@ -163,7 +174,7 @@ go get -u github.com/zeromicro/go-zero
    goctl api -o greet.api
    ```
    
-3. generate the go server-side code
+4. generate the go server-side code
 
    ```shell
    goctl api go -api greet.api -dir greet
@@ -216,12 +227,12 @@ go get -u github.com/zeromicro/go-zero
    Content-Length: 0
    ```
 
-4. Write the business logic code
+5. Write the business logic code
 
     * the dependencies can be passed into the logic within servicecontext.go, like mysql, reds, etc.
     * add the logic code in a logic package according to .api file
 
-5. Generate code like Java, TypeScript, Dart, JavaScript, etc. just from the api file
+6. Generate code like Java, TypeScript, Dart, JavaScript, etc. just from the api file
 
    ```shell
    goctl api java -api greet.api -dir greet
@@ -229,24 +240,24 @@ go get -u github.com/zeromicro/go-zero
    ...
    ```
 
-## 7. Benchmark
+## Benchmark
 
 ![benchmark](https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/benchmark.png)
 
 [Checkout the test code](https://github.com/smallnest/go-web-framework-benchmark)
 
-## 8. Documents
+## Documents
 
 * [Documents](https://go-zero.dev/)
 * [Rapid development of microservice systems](https://github.com/zeromicro/zero-doc/blob/main/doc/shorturl-en.md)
 * [Rapid development of microservice systems - multiple RPCs](https://github.com/zeromicro/zero-doc/blob/main/docs/zero/bookstore-en.md)
 * [Examples](https://github.com/zeromicro/zero-examples)
 
-## 9. Chat group
+##  Chat group
 
 Join the chat via https://discord.gg/4JQvC5A4Fe
 
-## 10. Cloud Native Landscape
+##  Cloud Native Landscape
 
 <p float="left">
 <img src="https://landscape.cncf.io/images/left-logo.svg" width="150"/>&nbsp;&nbsp;&nbsp;
diff --git a/rest/handler/loghandler.go b/rest/handler/loghandler.go
index b236ecf5..d3d5cddc 100644
--- a/rest/handler/loghandler.go
+++ b/rest/handler/loghandler.go
@@ -170,9 +170,9 @@ func logBrief(r *http.Request, code int, timer *utils.ElapsedTimer, logs *intern
 	buf.WriteString(fmt.Sprintf("[HTTP] %s - %s %s - %s - %s",
 		wrapStatusCode(code), wrapMethod(r.Method), r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent()))
 	if duration > slowThreshold.Load() {
-		logger.Slowf("[HTTP] %s - %s - %s %s - %s - slowcall(%s)",
+		logger.Slowf("[HTTP] %s - %s %s - %s - %s - slowcall(%s)",
 			wrapStatusCode(code), wrapMethod(r.Method), r.RequestURI, httpx.GetRemoteAddr(r), r.UserAgent(),
-			fmt.Sprintf("slowcall(%s)", timex.ReprOfDuration(duration)))
+			timex.ReprOfDuration(duration))
 	}
 
 	ok := isOkResponse(code)
diff --git a/tools/goctl/api/docgen/doc.go b/tools/goctl/api/docgen/doc.go
index c4b0cbed..37e21e7e 100644
--- a/tools/goctl/api/docgen/doc.go
+++ b/tools/goctl/api/docgen/doc.go
@@ -107,7 +107,7 @@ func associatedTypes(tp spec.DefineStruct, tps *[]spec.Type) {
 }
 
 // buildTypes gen types to string
-func buildTypes(types []spec.Type, all []spec.Type) (string, error) {
+func buildTypes(types, all []spec.Type) (string, error) {
 	var builder strings.Builder
 	first := true
 	for _, tp := range types {
diff --git a/tools/goctl/api/spec/fn.go b/tools/goctl/api/spec/fn.go
index 6ca97d99..8821b3a7 100644
--- a/tools/goctl/api/spec/fn.go
+++ b/tools/goctl/api/spec/fn.go
@@ -13,10 +13,11 @@ const (
 	bodyTagKey        = "json"
 	formTagKey        = "form"
 	pathTagKey        = "path"
+	headerTagKey      = "header"
 	defaultSummaryKey = "summary"
 )
 
-var definedKeys = []string{bodyTagKey, formTagKey, pathTagKey}
+var definedKeys = []string{bodyTagKey, formTagKey, pathTagKey, headerTagKey}
 
 func (s Service) JoinPrefix() Service {
 	var groups []Group
@@ -138,6 +139,21 @@ func (m Member) IsFormMember() bool {
 	return false
 }
 
+// IsTagMember returns true if contains given tag
+func (m Member) IsTagMember(tagKey string) bool {
+	if m.IsInline {
+		return true
+	}
+
+	tags := m.Tags()
+	for _, tag := range tags {
+		if tag.Key == tagKey {
+			return true
+		}
+	}
+	return false
+}
+
 // GetBodyMembers returns all json fields
 func (t DefineStruct) GetBodyMembers() []Member {
 	var result []Member
@@ -171,6 +187,17 @@ func (t DefineStruct) GetNonBodyMembers() []Member {
 	return result
 }
 
+// GetTagMembers returns all given key fields
+func (t DefineStruct) GetTagMembers(tagKey string) []Member {
+	var result []Member
+	for _, member := range t.Members {
+		if member.IsTagMember(tagKey) {
+			result = append(result, member)
+		}
+	}
+	return result
+}
+
 // JoinedDoc joins comments and summary value in AtDoc
 func (r Route) JoinedDoc() string {
 	doc := r.AtDoc.Text
diff --git a/tools/goctl/api/tsgen/genpacket.go b/tools/goctl/api/tsgen/genpacket.go
index 99296ad4..73ec4d79 100644
--- a/tools/goctl/api/tsgen/genpacket.go
+++ b/tools/goctl/api/tsgen/genpacket.go
@@ -105,20 +105,43 @@ func paramsForRoute(route spec.Route) string {
 	}
 	hasParams := pathHasParams(route)
 	hasBody := hasRequestBody(route)
+	hasHeader := hasRequestHeader(route)
+	hasPath := hasRequestPath(route)
 	rt, err := goTypeToTs(route.RequestType, true)
 	if err != nil {
 		fmt.Println(err.Error())
 		return ""
 	}
+	var params []string
+	if hasParams {
+		params = append(params, fmt.Sprintf("params: %s", rt+"Params"))
+	}
+	if hasBody {
+		params = append(params, fmt.Sprintf("req: %s", rt))
+	}
+	if hasHeader {
+		params = append(params, fmt.Sprintf("headers: %s", rt+"Headers"))
+	}
+	if hasPath {
+		ds, ok := route.RequestType.(spec.DefineStruct)
+		if !ok {
+			fmt.Printf("invalid route.RequestType: {%v}\n", route.RequestType)
+		}
+		members := ds.GetTagMembers(pathTagKey)
+		for _, member := range members {
+			tags := member.Tags()
 
-	if hasParams && hasBody {
-		return fmt.Sprintf("params: %s, req: %s", rt+"Params", rt)
-	} else if hasParams {
-		return fmt.Sprintf("params: %s", rt+"Params")
-	} else if hasBody {
-		return fmt.Sprintf("req: %s", rt)
+			if len(tags) > 0 && tags[0].Key == pathTagKey {
+				valueType, err := goTypeToTs(member.Type, false)
+				if err != nil {
+					fmt.Println(err.Error())
+					return ""
+				}
+				params = append(params, fmt.Sprintf("%s: %s", tags[0].Name, valueType))
+			}
+		}
 	}
-	return ""
+	return strings.Join(params, ", ")
 }
 
 func commentForRoute(route spec.Route) string {
@@ -128,14 +151,16 @@ func commentForRoute(route spec.Route) string {
 	builder.WriteString("\n * @description " + comment)
 	hasParams := pathHasParams(route)
 	hasBody := hasRequestBody(route)
-	if hasParams && hasBody {
+	hasHeader := hasRequestHeader(route)
+	if hasParams {
 		builder.WriteString("\n * @param params")
-		builder.WriteString("\n * @param req")
-	} else if hasParams {
-		builder.WriteString("\n * @param params")
-	} else if hasBody {
+	}
+	if hasBody {
 		builder.WriteString("\n * @param req")
 	}
+	if hasHeader {
+		builder.WriteString("\n * @param headers")
+	}
 	builder.WriteString("\n */")
 	return builder.String()
 }
@@ -143,26 +168,42 @@ func commentForRoute(route spec.Route) string {
 func callParamsForRoute(route spec.Route, group spec.Group) string {
 	hasParams := pathHasParams(route)
 	hasBody := hasRequestBody(route)
-	if hasParams && hasBody {
-		return fmt.Sprintf("%s, %s, %s", pathForRoute(route, group), "params", "req")
-	} else if hasParams {
-		return fmt.Sprintf("%s, %s", pathForRoute(route, group), "params")
-	} else if hasBody {
-		return fmt.Sprintf("%s, %s", pathForRoute(route, group), "req")
+	hasHeader := hasRequestHeader(route)
+
+	var params = []string{pathForRoute(route, group)}
+	if hasParams {
+		params = append(params, "params")
+	}
+	if hasBody {
+		params = append(params, "req")
+	}
+	if hasHeader {
+		params = append(params, "headers")
 	}
 
-	return pathForRoute(route, group)
+	return strings.Join(params, ", ")
 }
 
 func pathForRoute(route spec.Route, group spec.Group) string {
 	prefix := group.GetAnnotation(pathPrefix)
+
+	routePath := route.Path
+	if strings.Contains(routePath, ":") {
+		pathSlice := strings.Split(routePath, "/")
+		for i, part := range pathSlice {
+			if strings.Contains(part, ":") {
+				pathSlice[i] = fmt.Sprintf("${%s}", part[1:])
+			}
+		}
+		routePath = strings.Join(pathSlice, "/")
+	}
 	if len(prefix) == 0 {
-		return "\"" + route.Path + "\""
+		return "`" + routePath + "`"
 	}
 
 	prefix = strings.TrimPrefix(prefix, `"`)
 	prefix = strings.TrimSuffix(prefix, `"`)
-	return fmt.Sprintf(`"%s/%s"`, prefix, strings.TrimPrefix(route.Path, "/"))
+	return fmt.Sprintf("`%s/%s`", prefix, strings.TrimPrefix(routePath, "/"))
 }
 
 func pathHasParams(route spec.Route) bool {
@@ -182,3 +223,21 @@ func hasRequestBody(route spec.Route) bool {
 
 	return len(route.RequestTypeName()) > 0 && len(ds.GetBodyMembers()) > 0
 }
+
+func hasRequestPath(route spec.Route) bool {
+	ds, ok := route.RequestType.(spec.DefineStruct)
+	if !ok {
+		return false
+	}
+
+	return len(route.RequestTypeName()) > 0 && len(ds.GetTagMembers(pathTagKey)) > 0
+}
+
+func hasRequestHeader(route spec.Route) bool {
+	ds, ok := route.RequestType.(spec.DefineStruct)
+	if !ok {
+		return false
+	}
+
+	return len(route.RequestTypeName()) > 0 && len(ds.GetTagMembers(headerTagKey)) > 0
+}
diff --git a/tools/goctl/api/tsgen/util.go b/tools/goctl/api/tsgen/util.go
index 5c608b51..27c230d9 100644
--- a/tools/goctl/api/tsgen/util.go
+++ b/tools/goctl/api/tsgen/util.go
@@ -11,6 +11,12 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util"
 )
 
+const (
+	formTagKey   = "form"
+	pathTagKey   = "path"
+	headerTagKey = "header"
+)
+
 func writeProperty(writer io.Writer, member spec.Member, indent int) error {
 	writeIndent(writer, indent)
 	ty, err := goTypeToTs(member.Type, false)
@@ -129,13 +135,21 @@ func genParamsTypesIfNeed(writer io.Writer, tp spec.Type) error {
 	if len(members) == 0 {
 		return nil
 	}
-	fmt.Fprintf(writer, "\n")
+
 	fmt.Fprintf(writer, "export interface %sParams {\n", util.Title(tp.Name()))
-	if err := writeMembers(writer, tp, true); err != nil {
+	if err := writeTagMembers(writer, tp, formTagKey); err != nil {
 		return err
 	}
-
 	fmt.Fprintf(writer, "}\n")
+
+	if len(definedType.GetTagMembers(headerTagKey)) > 0 {
+		fmt.Fprintf(writer, "export interface %sHeaders {\n", util.Title(tp.Name()))
+		if err := writeTagMembers(writer, tp, headerTagKey); err != nil {
+			return err
+		}
+		fmt.Fprintf(writer, "}\n")
+	}
+
 	return nil
 }
 
@@ -168,3 +182,30 @@ func writeMembers(writer io.Writer, tp spec.Type, isParam bool) error {
 	}
 	return nil
 }
+
+func writeTagMembers(writer io.Writer, tp spec.Type, tagKey string) error {
+	definedType, ok := tp.(spec.DefineStruct)
+	if !ok {
+		pointType, ok := tp.(spec.PointerType)
+		if ok {
+			return writeTagMembers(writer, pointType.Type, tagKey)
+		}
+
+		return fmt.Errorf("type %s not supported", tp.Name())
+	}
+
+	members := definedType.GetTagMembers(tagKey)
+	for _, member := range members {
+		if member.IsInline {
+			if err := writeTagMembers(writer, member.Type, tagKey); err != nil {
+				return err
+			}
+			continue
+		}
+
+		if err := writeProperty(writer, member, 1); err != nil {
+			return apiutil.WrapErr(err, " type "+tp.Name())
+		}
+	}
+	return nil
+}


Test patch:
diff --git a/core/discov/internal/registry_test.go b/core/discov/internal/registry_test.go
index 0839cb1e..0ebc00ca 100644
--- a/core/discov/internal/registry_test.go
+++ b/core/discov/internal/registry_test.go
@@ -2,7 +2,6 @@ package internal
 
 import (
 	"context"
-	"go.etcd.io/etcd/api/v3/etcdserverpb"
 	"sync"
 	"testing"
 
@@ -12,6 +11,7 @@ import (
 	"github.com/zeromicro/go-zero/core/lang"
 	"github.com/zeromicro/go-zero/core/logx"
 	"github.com/zeromicro/go-zero/core/stringx"
+	"go.etcd.io/etcd/api/v3/etcdserverpb"
 	"go.etcd.io/etcd/api/v3/mvccpb"
 	clientv3 "go.etcd.io/etcd/client/v3"
 )
diff --git a/core/mr/mapreduce_fuzz_test.go b/core/mr/mapreduce_fuzz_test.go
index fa930a50..fc336a95 100644
--- a/core/mr/mapreduce_fuzz_test.go
+++ b/core/mr/mapreduce_fuzz_test.go
@@ -19,7 +19,7 @@ func FuzzMapReduce(f *testing.F) {
 	rand.Seed(time.Now().UnixNano())
 
 	f.Add(uint(10), uint(runtime.NumCPU()))
-	f.Fuzz(func(t *testing.T, num uint, workers uint) {
+	f.Fuzz(func(t *testing.T, num, workers uint) {
 		n := int64(num)%5000 + 5000
 		genPanic := rand.Intn(100) == 0
 		mapperPanic := rand.Intn(100) == 0
diff --git a/core/stores/mon/collection_test.go b/core/stores/mon/collection_test.go
index 225f008f..b8a0f880 100644
--- a/core/stores/mon/collection_test.go
+++ b/core/stores/mon/collection_test.go
@@ -106,14 +106,14 @@ func TestCollection_BulkWrite(t *testing.T) {
 		}
 		mt.AddMockResponses(mtest.CreateSuccessResponse(bson.D{{Key: "ok", Value: 1}}...))
 		res, err := c.BulkWrite(context.Background(), []mongo.WriteModel{
-			mongo.NewInsertOneModel().SetDocument(bson.D{{Key: "foo", Value: 1}})},
-		)
+			mongo.NewInsertOneModel().SetDocument(bson.D{{Key: "foo", Value: 1}}),
+		})
 		assert.Nil(t, err)
 		assert.NotNil(t, res)
 		c.brk = new(dropBreaker)
 		_, err = c.BulkWrite(context.Background(), []mongo.WriteModel{
-			mongo.NewInsertOneModel().SetDocument(bson.D{{Key: "foo", Value: 1}})},
-		)
+			mongo.NewInsertOneModel().SetDocument(bson.D{{Key: "foo", Value: 1}}),
+		})
 		assert.Equal(t, errDummy, err)
 	})
 }