Repository: zeromicro/go-zero
Pull request #1552 (2022-02-16 22:16:10)

feat: support oracle :N dynamic parameters


Patch:
diff --git a/core/limit/periodlimit.go b/core/limit/periodlimit.go
index edb737e8..73fb9d00 100644
--- a/core/limit/periodlimit.go
+++ b/core/limit/periodlimit.go
@@ -14,8 +14,8 @@ local window = tonumber(ARGV[2])
 local current = redis.call("INCRBY", KEYS[1], 1)
 if current == 1 then
     redis.call("expire", KEYS[1], window)
-    return 1
-elseif current < limit then
+end
+if current < limit then
     return 1
 elseif current == limit then
     return 2
diff --git a/core/logx/durationlogger.go b/core/logx/durationlogger.go
index ab26a0a6..7108a939 100644
--- a/core/logx/durationlogger.go
+++ b/core/logx/durationlogger.go
@@ -3,6 +3,7 @@ package logx
 import (
 	"fmt"
 	"io"
+	"sync/atomic"
 	"time"
 
 	"github.com/zeromicro/go-zero/core/timex"
@@ -79,7 +80,7 @@ func (l *durationLogger) WithDuration(duration time.Duration) Logger {
 }
 
 func (l *durationLogger) write(writer io.Writer, level string, val interface{}) {
-	switch encoding {
+	switch atomic.LoadUint32(&encoding) {
 	case plainEncodingType:
 		writePlainAny(writer, level, val, l.Duration)
 	default:
diff --git a/core/logx/logs.go b/core/logx/logs.go
index 34bb1112..c0a3315a 100644
--- a/core/logx/logs.go
+++ b/core/logx/logs.go
@@ -75,7 +75,7 @@ var (
 	timeFormat   = "2006-01-02T15:04:05.000Z07:00"
 	writeConsole bool
 	logLevel     uint32
-	encoding     = jsonEncodingType
+	encoding     uint32 = jsonEncodingType
 	// use uint32 for atomic operations
 	disableStat uint32
 	infoLog     io.WriteCloser
@@ -137,9 +137,9 @@ func SetUp(c LogConf) error {
 	}
 	switch c.Encoding {
 	case plainEncoding:
-		encoding = plainEncodingType
+		atomic.StoreUint32(&encoding, plainEncodingType)
 	default:
-		encoding = jsonEncodingType
+		atomic.StoreUint32(&encoding, jsonEncodingType)
 	}
 
 	switch c.Mode {
@@ -424,7 +424,7 @@ func infoTextSync(msg string) {
 }
 
 func outputAny(writer io.Writer, level string, val interface{}) {
-	switch encoding {
+	switch atomic.LoadUint32(&encoding) {
 	case plainEncodingType:
 		writePlainAny(writer, level, val)
 	default:
@@ -438,7 +438,7 @@ func outputAny(writer io.Writer, level string, val interface{}) {
 }
 
 func outputText(writer io.Writer, level, msg string) {
-	switch encoding {
+	switch atomic.LoadUint32(&encoding) {
 	case plainEncodingType:
 		writePlainText(writer, level, msg)
 	default:
diff --git a/core/logx/tracelogger.go b/core/logx/tracelogger.go
index e2702d51..647f3dff 100644
--- a/core/logx/tracelogger.go
+++ b/core/logx/tracelogger.go
@@ -4,6 +4,7 @@ import (
 	"context"
 	"fmt"
 	"io"
+	"sync/atomic"
 	"time"
 
 	"github.com/zeromicro/go-zero/core/timex"
@@ -80,7 +81,7 @@ func (l *traceLogger) write(writer io.Writer, level string, val interface{}) {
 	traceID := traceIdFromContext(l.ctx)
 	spanID := spanIdFromContext(l.ctx)
 
-	switch encoding {
+	switch atomic.LoadUint32(&encoding) {
 	case plainEncodingType:
 		writePlainAny(writer, level, val, l.Duration, traceID, spanID)
 	default:
diff --git a/core/stores/builder/builder.go b/core/stores/builder/builder.go
index d2fe5cc7..cddd1298 100644
--- a/core/stores/builder/builder.go
+++ b/core/stores/builder/builder.go
@@ -30,17 +30,21 @@ func RawFieldNames(in interface{}, postgresSql ...bool) []string {
 	for i := 0; i < v.NumField(); i++ {
 		// gets us a StructField
 		fi := typ.Field(i)
-		if tagv := fi.Tag.Get(dbTag); tagv != "" {
+		tagv := fi.Tag.Get(dbTag)
+		switch tagv {
+		case "-":
+			continue
+		case "":
 			if pg {
-				out = append(out, tagv)
+				out = append(out, fi.Name)
 			} else {
-				out = append(out, fmt.Sprintf("`%s`", tagv))
+				out = append(out, fmt.Sprintf("`%s`", fi.Name))
 			}
-		} else {
+		default:
 			if pg {
-				out = append(out, fi.Name)
+				out = append(out, tagv)
 			} else {
-				out = append(out, fmt.Sprintf("`%s`", fi.Name))
+				out = append(out, fmt.Sprintf("`%s`", tagv))
 			}
 		}
 	}
diff --git a/core/stores/mongoc/cachedcollection.go b/core/stores/mongoc/cachedcollection.go
index 49fdd203..3b4742fb 100644
--- a/core/stores/mongoc/cachedcollection.go
+++ b/core/stores/mongoc/cachedcollection.go
@@ -12,8 +12,8 @@ var (
 	ErrNotFound = mgo.ErrNotFound
 
 	// can't use one SingleFlight per conn, because multiple conns may share the same cache key.
-	sharedCalls = syncx.NewSingleFlight()
-	stats       = cache.NewStat("mongoc")
+	singleFlight = syncx.NewSingleFlight()
+	stats        = cache.NewStat("mongoc")
 )
 
 type (
diff --git a/core/stores/mongoc/cachedmodel.go b/core/stores/mongoc/cachedmodel.go
index c366cc3c..919afcea 100644
--- a/core/stores/mongoc/cachedmodel.go
+++ b/core/stores/mongoc/cachedmodel.go
@@ -38,7 +38,7 @@ func MustNewModel(url, collection string, c cache.CacheConf, opts ...cache.Optio
 
 // NewModel returns a Model with a cache cluster.
 func NewModel(url, collection string, conf cache.CacheConf, opts ...cache.Option) (*Model, error) {
-	c := cache.New(conf, sharedCalls, stats, mgo.ErrNotFound, opts...)
+	c := cache.New(conf, singleFlight, stats, mgo.ErrNotFound, opts...)
 	return NewModelWithCache(url, collection, c)
 }
 
@@ -51,7 +51,7 @@ func NewModelWithCache(url, collection string, c cache.Cache) (*Model, error) {
 
 // NewNodeModel returns a Model with a cache node.
 func NewNodeModel(url, collection string, rds *redis.Redis, opts ...cache.Option) (*Model, error) {
-	c := cache.NewNode(rds, sharedCalls, stats, mgo.ErrNotFound, opts...)
+	c := cache.NewNode(rds, singleFlight, stats, mgo.ErrNotFound, opts...)
 	return NewModelWithCache(url, collection, c)
 }
 
diff --git a/core/stores/sqlx/bulkinserter.go b/core/stores/sqlx/bulkinserter.go
index 507170f4..a4a2d99a 100644
--- a/core/stores/sqlx/bulkinserter.go
+++ b/core/stores/sqlx/bulkinserter.go
@@ -25,6 +25,7 @@ type (
 
 	// A BulkInserter is used to batch insert records.
 	// Postgresql is not supported yet, because of the sql is formated with symbol `$`.
+	// Oracle is not supported yet, because of the sql is formated with symbol `:`.
 	BulkInserter struct {
 		executor *executors.PeriodicalExecutor
 		inserter *dbInserter
diff --git a/core/stores/sqlx/utils.go b/core/stores/sqlx/utils.go
index 9888e400..6aa966e7 100644
--- a/core/stores/sqlx/utils.go
+++ b/core/stores/sqlx/utils.go
@@ -67,7 +67,7 @@ func format(query string, args ...interface{}) (string, error) {
 
 			writeValue(&b, args[argIndex])
 			argIndex++
-		case '$':
+		case ':', '$':
 			var j int
 			for j = i + 1; j < bytes; j++ {
 				char := query[j]
@@ -81,10 +81,11 @@ func format(query string, args ...interface{}) (string, error) {
 					return "", err
 				}
 
-				// index starts from 1 for pg
+				// index starts from 1 for pg or oracle
 				if index > argIndex {
 					argIndex = index
 				}
+				
 				index--
 				if index < 0 || numArgs <= index {
 					return "", fmt.Errorf("error: wrong index %d in sql", index)
diff --git a/go.mod b/go.mod
index 98c14a20..dff028d2 100644
--- a/go.mod
+++ b/go.mod
@@ -17,8 +17,8 @@ require (
 	github.com/prometheus/client_golang v1.11.0
 	github.com/spaolacci/murmur3 v1.1.0
 	github.com/stretchr/testify v1.7.0
-	go.etcd.io/etcd/api/v3 v3.5.1
-	go.etcd.io/etcd/client/v3 v3.5.1
+	go.etcd.io/etcd/api/v3 v3.5.2
+	go.etcd.io/etcd/client/v3 v3.5.2
 	go.opentelemetry.io/otel v1.3.0
 	go.opentelemetry.io/otel/exporters/jaeger v1.3.0
 	go.opentelemetry.io/otel/exporters/zipkin v1.3.0
@@ -44,8 +44,11 @@ require (
 	github.com/go-redis/redis/v8 v8.11.4
 	github.com/mattn/go-runewidth v0.0.13 // indirect
 	github.com/openzipkin/zipkin-go v0.4.0 // indirect
-	golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd // indirect
-	golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
-	google.golang.org/genproto v0.0.0-20220211171837-173942840c17 // indirect
+	go.uber.org/atomic v1.9.0 // indirect
+	go.uber.org/multierr v1.8.0 // indirect
+	go.uber.org/zap v1.21.0 // indirect
+	golang.org/x/net v0.0.0-20220225172249-27dd8689420f // indirect
+	golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 // indirect
+	google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7 // indirect
 	k8s.io/klog/v2 v2.40.1 // indirect
 )
diff --git a/go.sum b/go.sum
index 9391f6e7..4059a4a2 100644
--- a/go.sum
+++ b/go.sum
@@ -52,6 +52,7 @@ github.com/alicebob/miniredis/v2 v2.17.0 h1:EwLdrIS50uczw71Jc7iVSxZluTKj5nfSP8n7
 github.com/alicebob/miniredis/v2 v2.17.0/go.mod h1:gquAfGbzn92jvtrSC69+6zZnwSODVXVpYDRaGhWaL6I=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
@@ -378,10 +379,16 @@ github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5u
 github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
 go.etcd.io/etcd/api/v3 v3.5.1 h1:v28cktvBq+7vGyJXF8G+rWJmj+1XUmMtqcLnH8hDocM=
 go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
+go.etcd.io/etcd/api/v3 v3.5.2 h1:tXok5yLlKyuQ/SXSjtqHc4uzNaMqZi2XsoSPr/LlJXI=
+go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
 go.etcd.io/etcd/client/pkg/v3 v3.5.1 h1:XIQcHCFSG53bJETYeRJtIxdLv2EWRGxcfzR8lSnTH4E=
 go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/pkg/v3 v3.5.2 h1:4hzqQ6hIb3blLyQ8usCU4h3NghkqcsohEQ3o3VetYxE=
+go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
 go.etcd.io/etcd/client/v3 v3.5.1 h1:oImGuV5LGKjCqXdjkMHCyWa5OO1gYKCnC/1sgdfj1Uk=
 go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q=
+go.etcd.io/etcd/client/v3 v3.5.2 h1:WdnejrUtQC4nCxK0/dLTMqKOB+U5TP/2Ya0BJL+1otA=
+go.etcd.io/etcd/client/v3 v3.5.2/go.mod h1:kOOaWFFgHygyT0WlSmL8TJiXmMysO/nNUlEsSsN6W4o=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -399,14 +406,21 @@ go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKu
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
 go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
 go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.8.0 h1:dg6GjLku4EH+249NNmoIciG9N/jURbDG+pFlTkhzIC8=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
 go.uber.org/zap v1.17.0 h1:MTjgFu6ZLKvY6Pvaqk97GlxNBuMpV4Hy/3P6tRGlI2U=
 go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go.uber.org/zap v1.21.0 h1:WefMeulhovoZ2sYXz7st6K0sLj7bBhpiFaud4r4zST8=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -485,6 +499,8 @@ golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT
 golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd h1:O7DYs+zxREGLKzKoMQrtrEacpb0ZVXA5rIwylE2Xchk=
 golang.org/x/net v0.0.0-20220127200216-cd36cc0744dd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -548,6 +564,8 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
 golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -648,6 +666,8 @@ google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEY
 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
 google.golang.org/genproto v0.0.0-20220211171837-173942840c17 h1:2X+CNIheCutWRyKRte8szGxrE5ggtV4U+NKAbh/oLhg=
 google.golang.org/genproto v0.0.0-20220211171837-173942840c17/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7 h1:ntPPoHzFW6Xp09ueznmahONZufyoSakK/piXnr2BU3I=
+google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
diff --git a/readme-cn.md b/readme-cn.md
index 716059d3..c8bb9048 100644
--- a/readme-cn.md
+++ b/readme-cn.md
@@ -2,6 +2,8 @@
 
 # go-zero
 
+***缩短从需求到上线的距离***
+
 [English](readme.md) | 简体中文
 
 [![Go](https://github.com/zeromicro/go-zero/workflows/Go/badge.svg?branch=master)](https://github.com/zeromicro/go-zero/actions)
@@ -11,9 +13,13 @@
 [![Release](https://img.shields.io/github/v/release/zeromicro/go-zero.svg?style=flat-square)](https://github.com/zeromicro/go-zero)
 [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
 
-> ***缩短从需求到上线的距离***
-
-**注意：为了满足开源基金会要求，go-zero 从好未来（tal-tech）组织下迁移至中立的 GitHub 组织（zeromicro）。**
+> ***注意：***
+>
+> 从 v1.3.0 之前版本升级请执行以下命令：
+> 
+> `GOPROXY=https://goproxy.cn/,direct go install github.com/zeromicro/go-zero/tools/goctl@latest`
+> 
+> `goctl migrate —verbose —version v1.3.1`
 
 ## 0. go-zero 介绍
 
@@ -244,6 +250,8 @@ go-zero 已被许多公司用于生产部署，接入场景如在线教育、电
 >56. 时代脉搏网络科技（云浮市）有限公司
 >57. 店有帮
 >58. 七牛云
+>59. 费芮网络
+>60. 51CTO
 
 如果贵公司也已使用 go-zero，欢迎在 [登记地址](https://github.com/zeromicro/go-zero/issues/602) 登记，仅仅为了推广，不做其它用途。
 
@@ -273,3 +281,9 @@ go-zero 收录在 [CNCF Cloud Native 云原生技术全景图](https://landscape
 加群之前有劳点一下 ***star***，一个小小的 ***star*** 是作者们回答海量问题的动力！🤝
 
 <img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/wechat.jpg" alt="wechat" width="300" />
+
+## 12. 赞助一下👍
+
+如果觉得项目有帮助，可以请作者喝杯咖啡 🍹
+
+<img src="https://raw.githubusercontent.com/zeromicro/zero-doc/main/doc/images/sponsor.png" alt="wechat" width="300" />
diff --git a/readme.md b/readme.md
index 90037a6d..1848d9a6 100644
--- a/readme.md
+++ b/readme.md
@@ -9,8 +9,16 @@ English | [简体中文](readme-cn.md)
 [![Go Report Card](https://goreportcard.com/badge/github.com/zeromicro/go-zero)](https://goreportcard.com/report/github.com/zeromicro/go-zero)
 [![Release](https://img.shields.io/github/v/release/zeromicro/go-zero.svg?style=flat-square)](https://github.com/zeromicro/go-zero)
 [![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>
 
-**Note: To meet the requirements of Open Source Foundation, we moved go-zero from tal-tech to zeromicro (a neutral GitHub organization).**
+> ***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.3.1`
 
 ## 0. what is go-zero
 
@@ -221,7 +229,7 @@ go get -u github.com/zeromicro/go-zero
 
 ## 9. Chat group
 
-Join the chat via https://join.slack.com/t/go-zero/shared_invite/zt-10ruju779-BE4y6lQNB_R21samtyKTgA
+Join the chat via https://discord.gg/4JQvC5A4Fe
 
 ## 10. Cloud Native Landscape
 
diff --git a/rest/engine.go b/rest/engine.go
index 8ba13378..86e8dead 100644
--- a/rest/engine.go
+++ b/rest/engine.go
@@ -14,6 +14,7 @@ import (
 	"github.com/zeromicro/go-zero/rest/handler"
 	"github.com/zeromicro/go-zero/rest/httpx"
 	"github.com/zeromicro/go-zero/rest/internal"
+	"github.com/zeromicro/go-zero/rest/internal/response"
 )
 
 // use 1000m to represent 100%
@@ -34,16 +35,16 @@ type engine struct {
 }
 
 func newEngine(c RestConf) *engine {
-	srv := &engine{
+	svr := &engine{
 		conf: c,
 	}
 	if c.CpuThreshold > 0 {
-		srv.shedder = load.NewAdaptiveShedder(load.WithCpuThreshold(c.CpuThreshold))
-		srv.priorityShedder = load.NewAdaptiveShedder(load.WithCpuThreshold(
+		svr.shedder = load.NewAdaptiveShedder(load.WithCpuThreshold(c.CpuThreshold))
+		svr.priorityShedder = load.NewAdaptiveShedder(load.WithCpuThreshold(
 			(c.CpuThreshold + topCpuUsage) >> 1))
 	}
 
-	return srv
+	return svr
 }
 
 func (ng *engine) addRoutes(r featuredRoutes) {
@@ -154,6 +155,27 @@ func (ng *engine) getShedder(priority bool) load.Shedder {
 	return ng.shedder
 }
 
+// notFoundHandler returns a middleware that handles 404 not found requests.
+func (ng *engine) notFoundHandler(next http.Handler) http.Handler {
+	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		chain := alice.New(
+			handler.TracingHandler(ng.conf.Name, ""),
+			ng.getLogHandler(),
+		)
+
+		var h http.Handler
+		if next != nil {
+			h = chain.Then(next)
+		} else {
+			h = chain.Then(http.NotFoundHandler())
+		}
+
+		cw := response.NewHeaderOnceResponseWriter(w)
+		h.ServeHTTP(cw, r)
+		cw.WriteHeader(http.StatusNotFound)
+	})
+}
+
 func (ng *engine) setTlsConfig(cfg *tls.Config) {
 	ng.tlsConfig = cfg
 }
@@ -216,9 +238,9 @@ func (ng *engine) start(router httpx.Router) error {
 	}
 
 	return internal.StartHttps(ng.conf.Host, ng.conf.Port, ng.conf.CertFile,
-		ng.conf.KeyFile, router, func(srv *http.Server) {
+		ng.conf.KeyFile, router, func(svr *http.Server) {
 			if ng.tlsConfig != nil {
-				srv.TLSConfig = ng.tlsConfig
+				svr.TLSConfig = ng.tlsConfig
 			}
 		})
 }
diff --git a/rest/handler/authhandler.go b/rest/handler/authhandler.go
index 26aacca0..9099e62b 100644
--- a/rest/handler/authhandler.go
+++ b/rest/handler/authhandler.go
@@ -1,15 +1,14 @@
 package handler
 
 import (
-	"bufio"
 	"context"
 	"errors"
-	"net"
 	"net/http"
 	"net/http/httputil"
 
 	"github.com/golang-jwt/jwt/v4"
 	"github.com/zeromicro/go-zero/core/logx"
+	"github.com/zeromicro/go-zero/rest/internal/response"
 	"github.com/zeromicro/go-zero/rest/token"
 )
 
@@ -105,7 +104,7 @@ func detailAuthLog(r *http.Request, reason string) {
 }
 
 func unauthorized(w http.ResponseWriter, r *http.Request, err error, callback UnauthorizedCallback) {
-	writer := newGuardedResponseWriter(w)
+	writer := response.NewHeaderOnceResponseWriter(w)
 
 	if err != nil {
 		detailAuthLog(r, err.Error())
@@ -121,47 +120,3 @@ func unauthorized(w http.ResponseWriter, r *http.Request, err error, callback Un
 	// if user not setting HTTP header, we set header with 401
 	writer.WriteHeader(http.StatusUnauthorized)
 }
-
-type guardedResponseWriter struct {
-	writer      http.ResponseWriter
-	wroteHeader bool
-}
-
-func newGuardedResponseWriter(w http.ResponseWriter) *guardedResponseWriter {
-	return &guardedResponseWriter{
-		writer: w,
-	}
-}
-
-func (grw *guardedResponseWriter) Flush() {
-	if flusher, ok := grw.writer.(http.Flusher); ok {
-		flusher.Flush()
-	}
-}
-
-func (grw *guardedResponseWriter) Header() http.Header {
-	return grw.writer.Header()
-}
-
-// Hijack implements the http.Hijacker interface.
-// This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
-func (grw *guardedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
-	if hijacked, ok := grw.writer.(http.Hijacker); ok {
-		return hijacked.Hijack()
-	}
-
-	return nil, nil, errors.New("server doesn't support hijacking")
-}
-
-func (grw *guardedResponseWriter) Write(body []byte) (int, error) {
-	return grw.writer.Write(body)
-}
-
-func (grw *guardedResponseWriter) WriteHeader(statusCode int) {
-	if grw.wroteHeader {
-		return
-	}
-
-	grw.wroteHeader = true
-	grw.writer.WriteHeader(statusCode)
-}
diff --git a/rest/handler/breakerhandler.go b/rest/handler/breakerhandler.go
index 516aba76..e9d7243a 100644
--- a/rest/handler/breakerhandler.go
+++ b/rest/handler/breakerhandler.go
@@ -9,7 +9,7 @@ import (
 	"github.com/zeromicro/go-zero/core/logx"
 	"github.com/zeromicro/go-zero/core/stat"
 	"github.com/zeromicro/go-zero/rest/httpx"
-	"github.com/zeromicro/go-zero/rest/internal/security"
+	"github.com/zeromicro/go-zero/rest/internal/response"
 )
 
 const breakerSeparator = "://"
@@ -28,7 +28,7 @@ func BreakerHandler(method, path string, metrics *stat.Metrics) func(http.Handle
 				return
 			}
 
-			cw := &security.WithCodeResponseWriter{Writer: w}
+			cw := &response.WithCodeResponseWriter{Writer: w}
 			defer func() {
 				if cw.Code < http.StatusInternalServerError {
 					promise.Accept()
diff --git a/rest/handler/prometheushandler.go b/rest/handler/prometheushandler.go
index a1a286a0..639d59c0 100644
--- a/rest/handler/prometheushandler.go
+++ b/rest/handler/prometheushandler.go
@@ -8,7 +8,7 @@ import (
 	"github.com/zeromicro/go-zero/core/metric"
 	"github.com/zeromicro/go-zero/core/prometheus"
 	"github.com/zeromicro/go-zero/core/timex"
-	"github.com/zeromicro/go-zero/rest/internal/security"
+	"github.com/zeromicro/go-zero/rest/internal/response"
 )
 
 const serverNamespace = "http_server"
@@ -41,7 +41,7 @@ func PrometheusHandler(path string) func(http.Handler) http.Handler {
 
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			startTime := timex.Now()
-			cw := &security.WithCodeResponseWriter{Writer: w}
+			cw := &response.WithCodeResponseWriter{Writer: w}
 			defer func() {
 				metricServerReqDur.Observe(int64(timex.Since(startTime)/time.Millisecond), path)
 				metricServerReqCodeTotal.Inc(path, strconv.Itoa(cw.Code))
diff --git a/rest/handler/sheddinghandler.go b/rest/handler/sheddinghandler.go
index ffbee34f..977824dc 100644
--- a/rest/handler/sheddinghandler.go
+++ b/rest/handler/sheddinghandler.go
@@ -8,7 +8,7 @@ import (
 	"github.com/zeromicro/go-zero/core/logx"
 	"github.com/zeromicro/go-zero/core/stat"
 	"github.com/zeromicro/go-zero/rest/httpx"
-	"github.com/zeromicro/go-zero/rest/internal/security"
+	"github.com/zeromicro/go-zero/rest/internal/response"
 )
 
 const serviceType = "api"
@@ -41,7 +41,7 @@ func SheddingHandler(shedder load.Shedder, metrics *stat.Metrics) func(http.Hand
 				return
 			}
 
-			cw := &security.WithCodeResponseWriter{Writer: w}
+			cw := &response.WithCodeResponseWriter{Writer: w}
 			defer func() {
 				if cw.Code == http.StatusServiceUnavailable {
 					promise.Fail()
diff --git a/rest/handler/tracinghandler.go b/rest/handler/tracinghandler.go
index 0a8cba9e..0b1cdf7e 100644
--- a/rest/handler/tracinghandler.go
+++ b/rest/handler/tracinghandler.go
@@ -18,12 +18,16 @@ func TracingHandler(serviceName, path string) func(http.Handler) http.Handler {
 
 		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 			ctx := propagator.Extract(r.Context(), propagation.HeaderCarrier(r.Header))
+			spanName := path
+			if len(spanName) == 0 {
+				spanName = r.URL.Path
+			}
 			spanCtx, span := tracer.Start(
 				ctx,
-				path,
+				spanName,
 				oteltrace.WithSpanKind(oteltrace.SpanKindServer),
 				oteltrace.WithAttributes(semconv.HTTPServerAttributesFromHTTPRequest(
-					serviceName, path, r)...),
+					serviceName, spanName, r)...),
 			)
 			defer span.End()
 
diff --git a/rest/internal/cors/handlers.go b/rest/internal/cors/handlers.go
index c613b27d..7bb3f077 100644
--- a/rest/internal/cors/handlers.go
+++ b/rest/internal/cors/handlers.go
@@ -1,10 +1,9 @@
 package cors
 
 import (
-	"bufio"
-	"errors"
-	"net"
 	"net/http"
+
+	"github.com/zeromicro/go-zero/rest/internal/response"
 )
 
 const (
@@ -30,7 +29,7 @@ const (
 // At most one origin can be specified, other origins are ignored if given, default to be *.
 func NotAllowedHandler(fn func(w http.ResponseWriter), origins ...string) http.Handler {
 	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-		gw := &guardedResponseWriter{w: w}
+		gw := response.NewHeaderOnceResponseWriter(w)
 		checkAndSetHeaders(gw, r, origins)
 		if fn != nil {
 			fn(gw)
@@ -62,44 +61,6 @@ func Middleware(fn func(w http.Header), origins ...string) func(http.HandlerFunc
 	}
 }
 
-type guardedResponseWriter struct {
-	w           http.ResponseWriter
-	wroteHeader bool
-}
-
-func (w *guardedResponseWriter) Flush() {
-	if flusher, ok := w.w.(http.Flusher); ok {
-		flusher.Flush()
-	}
-}
-
-func (w *guardedResponseWriter) Header() http.Header {
-	return w.w.Header()
-}
-
-// Hijack implements the http.Hijacker interface.
-// This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
-func (w *guardedResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
-	if hijacked, ok := w.w.(http.Hijacker); ok {
-		return hijacked.Hijack()
-	}
-
-	return nil, nil, errors.New("server doesn't support hijacking")
-}
-
-func (w *guardedResponseWriter) Write(bytes []byte) (int, error) {
-	return w.w.Write(bytes)
-}
-
-func (w *guardedResponseWriter) WriteHeader(code int) {
-	if w.wroteHeader {
-		return
-	}
-
-	w.w.WriteHeader(code)
-	w.wroteHeader = true
-}
-
 func checkAndSetHeaders(w http.ResponseWriter, r *http.Request, origins []string) {
 	setVaryHeaders(w, r)
 
diff --git a/rest/internal/response/headeronceresponsewriter.go b/rest/internal/response/headeronceresponsewriter.go
new file mode 100644
index 00000000..62ba4442
--- /dev/null
+++ b/rest/internal/response/headeronceresponsewriter.go
@@ -0,0 +1,57 @@
+package response
+
+import (
+	"bufio"
+	"errors"
+	"net"
+	"net/http"
+)
+
+// HeaderOnceResponseWriter is a http.ResponseWriter implementation
+// that only the first WriterHeader takes effect.
+type HeaderOnceResponseWriter struct {
+	w           http.ResponseWriter
+	wroteHeader bool
+}
+
+// NewHeaderOnceResponseWriter returns a HeaderOnceResponseWriter.
+func NewHeaderOnceResponseWriter(w http.ResponseWriter) http.ResponseWriter {
+	return &HeaderOnceResponseWriter{w: w}
+}
+
+// Flush flushes the response writer.
+func (w *HeaderOnceResponseWriter) Flush() {
+	if flusher, ok := w.w.(http.Flusher); ok {
+		flusher.Flush()
+	}
+}
+
+// Header returns the http header.
+func (w *HeaderOnceResponseWriter) Header() http.Header {
+	return w.w.Header()
+}
+
+// Hijack implements the http.Hijacker interface.
+// This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
+func (w *HeaderOnceResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+	if hijacked, ok := w.w.(http.Hijacker); ok {
+		return hijacked.Hijack()
+	}
+
+	return nil, nil, errors.New("server doesn't support hijacking")
+}
+
+// Write writes bytes into w.
+func (w *HeaderOnceResponseWriter) Write(bytes []byte) (int, error) {
+	return w.w.Write(bytes)
+}
+
+// WriteHeader writes code into w, and not sealing the writer.
+func (w *HeaderOnceResponseWriter) WriteHeader(code int) {
+	if w.wroteHeader {
+		return
+	}
+
+	w.w.WriteHeader(code)
+	w.wroteHeader = true
+}
diff --git a/rest/internal/security/withcoderesponsewriter.go b/rest/internal/response/withcoderesponsewriter.go
similarity index 85%
rename from rest/internal/security/withcoderesponsewriter.go
rename to rest/internal/response/withcoderesponsewriter.go
index 2496e301..4fa9631e 100644
--- a/rest/internal/security/withcoderesponsewriter.go
+++ b/rest/internal/response/withcoderesponsewriter.go
@@ -1,7 +1,8 @@
-package security
+package response
 
 import (
 	"bufio"
+	"errors"
 	"net"
 	"net/http"
 )
@@ -27,7 +28,11 @@ func (w *WithCodeResponseWriter) Header() http.Header {
 // Hijack implements the http.Hijacker interface.
 // This expands the Response to fulfill http.Hijacker if the underlying http.ResponseWriter supports it.
 func (w *WithCodeResponseWriter) Hijack() (net.Conn, *bufio.ReadWriter, error) {
-	return w.Writer.(http.Hijacker).Hijack()
+	if hijacked, ok := w.Writer.(http.Hijacker); ok {
+		return hijacked.Hijack()
+	}
+
+	return nil, nil, errors.New("server doesn't support hijacking")
 }
 
 // Write writes bytes into w.
diff --git a/rest/internal/starter.go b/rest/internal/starter.go
index 4f87bf4f..0f45f989 100644
--- a/rest/internal/starter.go
+++ b/rest/internal/starter.go
@@ -10,25 +10,25 @@ import (
 )
 
 // StartOption defines the method to customize http.Server.
-type StartOption func(srv *http.Server)
+type StartOption func(svr *http.Server)
 
 // StartHttp starts a http server.
 func StartHttp(host string, port int, handler http.Handler, opts ...StartOption) error {
-	return start(host, port, handler, func(srv *http.Server) error {
-		return srv.ListenAndServe()
+	return start(host, port, handler, func(svr *http.Server) error {
+		return svr.ListenAndServe()
 	}, opts...)
 }
 
 // StartHttps starts a https server.
 func StartHttps(host string, port int, certFile, keyFile string, handler http.Handler,
 	opts ...StartOption) error {
-	return start(host, port, handler, func(srv *http.Server) error {
+	return start(host, port, handler, func(svr *http.Server) error {
 		// certFile and keyFile are set in buildHttpsServer
-		return srv.ListenAndServeTLS(certFile, keyFile)
+		return svr.ListenAndServeTLS(certFile, keyFile)
 	}, opts...)
 }
 
-func start(host string, port int, handler http.Handler, run func(srv *http.Server) error,
+func start(host string, port int, handler http.Handler, run func(svr *http.Server) error,
 	opts ...StartOption) (err error) {
 	server := &http.Server{
 		Addr:    fmt.Sprintf("%s:%d", host, port),
diff --git a/rest/server.go b/rest/server.go
index 391ee75c..793faffa 100644
--- a/rest/server.go
+++ b/rest/server.go
@@ -49,6 +49,7 @@ func NewServer(c RestConf, opts ...RunOption) (*Server, error) {
 		router: router.NewRouter(),
 	}
 
+	opts = append([]RunOption{WithNotFoundHandler(nil)}, opts...)
 	for _, opt := range opts {
 		opt(server)
 	}
@@ -163,7 +164,8 @@ func WithMiddleware(middleware Middleware, rs ...Route) []Route {
 // WithNotFoundHandler returns a RunOption with not found handler set to given handler.
 func WithNotFoundHandler(handler http.Handler) RunOption {
 	return func(server *Server) {
-		server.router.SetNotFoundHandler(handler)
+		notFoundHandler := server.ngin.notFoundHandler(handler)
+		server.router.SetNotFoundHandler(notFoundHandler)
 	}
 }
 
@@ -223,22 +225,22 @@ func WithTimeout(timeout time.Duration) RouteOption {
 
 // WithTLSConfig returns a RunOption that with given tls config.
 func WithTLSConfig(cfg *tls.Config) RunOption {
-	return func(srv *Server) {
-		srv.ngin.setTlsConfig(cfg)
+	return func(svr *Server) {
+		svr.ngin.setTlsConfig(cfg)
 	}
 }
 
 // WithUnauthorizedCallback returns a RunOption that with given unauthorized callback set.
 func WithUnauthorizedCallback(callback handler.UnauthorizedCallback) RunOption {
-	return func(srv *Server) {
-		srv.ngin.setUnauthorizedCallback(callback)
+	return func(svr *Server) {
+		svr.ngin.setUnauthorizedCallback(callback)
 	}
 }
 
 // WithUnsignedCallback returns a RunOption that with given unsigned callback set.
 func WithUnsignedCallback(callback handler.UnsignedCallback) RunOption {
-	return func(srv *Server) {
-		srv.ngin.setUnsignedCallback(callback)
+	return func(svr *Server) {
+		svr.ngin.setUnsignedCallback(callback)
 	}
 }
 
diff --git a/tools/goctl/.gitignore b/tools/goctl/.gitignore
new file mode 100644
index 00000000..722d5e71
--- /dev/null
+++ b/tools/goctl/.gitignore
@@ -0,0 +1,1 @@
+.vscode
diff --git a/tools/goctl/api/apigen/gen.go b/tools/goctl/api/apigen/gen.go
index 50cb3d78..afdba15e 100644
--- a/tools/goctl/api/apigen/gen.go
+++ b/tools/goctl/api/apigen/gen.go
@@ -55,8 +55,9 @@ func ApiCommand(c *cli.Context) error {
 
 	home := c.String("home")
 	remote := c.String("remote")
+	branch := c.String("branch")
 	if len(remote) > 0 {
-		repo, _ := util.CloneIntoGitHome(remote)
+		repo, _ := util.CloneIntoGitHome(remote, branch)
 		if len(repo) > 0 {
 			home = repo
 		}
diff --git a/tools/goctl/api/dartgen/format.go b/tools/goctl/api/dartgen/format.go
new file mode 100644
index 00000000..029e3bec
--- /dev/null
+++ b/tools/goctl/api/dartgen/format.go
@@ -0,0 +1,40 @@
+package dartgen
+
+import (
+	"fmt"
+	"os"
+	"os/exec"
+)
+
+const dartExec = "dart"
+
+func formatDir(dir string) error {
+	ok, err := dirctoryExists(dir)
+	if err != nil {
+		return err
+	}
+	if !ok {
+		return fmt.Errorf("format failed, directory %q does not exist", dir)
+	}
+
+	_, err = exec.LookPath(dartExec)
+	if err != nil {
+		return err
+	}
+	cmd := exec.Command(dartExec, "format", dir)
+	cmd.Env = os.Environ()
+	cmd.Stderr = os.Stderr
+
+	return cmd.Run()
+}
+
+func dirctoryExists(dir string) (bool, error) {
+	_, err := os.Stat(dir)
+	if err == nil {
+		return true, nil
+	}
+	if os.IsNotExist(err) {
+		return false, nil
+	}
+	return false, err
+}
diff --git a/tools/goctl/api/dartgen/gen.go b/tools/goctl/api/dartgen/gen.go
index 3bb08a99..c7078bf2 100644
--- a/tools/goctl/api/dartgen/gen.go
+++ b/tools/goctl/api/dartgen/gen.go
@@ -2,6 +2,7 @@ package dartgen
 
 import (
 	"errors"
+	"fmt"
 	"strings"
 
 	"github.com/urfave/cli"
@@ -13,12 +14,18 @@ import (
 func DartCommand(c *cli.Context) error {
 	apiFile := c.String("api")
 	dir := c.String("dir")
+	isLegacy := c.Bool("legacy")
+	hostname := c.String("hostname")
 	if len(apiFile) == 0 {
 		return errors.New("missing -api")
 	}
 	if len(dir) == 0 {
 		return errors.New("missing -dir")
 	}
+	if len(hostname) == 0 {
+		fmt.Println("you could use '-hostname' flag to specify your server hostname")
+		hostname = "go-zero.dev"
+	}
 
 	api, err := parser.Parse(apiFile)
 	if err != nil {
@@ -30,8 +37,11 @@ func DartCommand(c *cli.Context) error {
 		dir = dir + "/"
 	}
 	api.Info.Title = strings.Replace(apiFile, ".api", "", -1)
-	logx.Must(genData(dir+"data/", api))
-	logx.Must(genApi(dir+"api/", api))
-	logx.Must(genVars(dir + "vars/"))
+	logx.Must(genData(dir+"data/", api, isLegacy))
+	logx.Must(genApi(dir+"api/", api, isLegacy))
+	logx.Must(genVars(dir+"vars/", isLegacy, hostname))
+	if err := formatDir(dir); err != nil {
+		logx.Errorf("failed to format, %v", err)
+	}
 	return nil
 }
diff --git a/tools/goctl/api/dartgen/genapi.go b/tools/goctl/api/dartgen/genapi.go
index 380b858a..7f735a81 100644
--- a/tools/goctl/api/dartgen/genapi.go
+++ b/tools/goctl/api/dartgen/genapi.go
@@ -2,13 +2,14 @@ package dartgen
 
 import (
 	"os"
+	"strings"
 	"text/template"
 
 	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
 )
 
 const apiTemplate = `import 'api.dart';
-import '../data/{{with .Info}}{{.Title}}{{end}}.dart';
+import '../data/{{with .Info}}{{getBaseName .Title}}{{end}}.dart';
 {{with .Service}}
 /// {{.Name}}
 {{range .Routes}}
@@ -22,24 +23,45 @@ Future {{pathToFuncName .Path}}( {{if ne .Method "get"}}{{with .RequestType}}{{.
     Function eventually}) async {
   await api{{if eq .Method "get"}}Get{{else}}Post{{end}}('{{.Path}}',{{if ne .Method "get"}}request,{{end}}
   	 ok: (data) {
-    if (ok != null) ok({{with .ResponseType}}{{.Name}}{{end}}.fromJson(data));
+    if (ok != null) ok({{with .ResponseType}}{{.Name}}.fromJson(data){{end}});
   }, fail: fail, eventually: eventually);
 }
 {{end}}
 {{end}}`
 
-func genApi(dir string, api *spec.ApiSpec) error {
+const apiTemplateV2 = `import 'api.dart';
+import '../data/{{with .Info}}{{getBaseName .Title}}{{end}}.dart';
+{{with .Service}}
+/// {{.Name}}
+{{range .Routes}}
+/// --{{.Path}}--
+///
+/// request: {{with .RequestType}}{{.Name}}{{end}}
+/// response: {{with .ResponseType}}{{.Name}}{{end}}
+Future {{pathToFuncName .Path}}( {{if ne .Method "get"}}{{with .RequestType}}{{.Name}} request,{{end}}{{end}}
+    {Function({{with .ResponseType}}{{.Name}}{{end}})? ok,
+    Function(String)? fail,
+    Function? eventually}) async {
+  await api{{if eq .Method "get"}}Get{{else}}Post{{end}}('{{.Path}}',{{if ne .Method "get"}}request,{{end}}
+  	 ok: (data) {
+    if (ok != null) ok({{with .ResponseType}}{{.Name}}.fromJson(data){{end}});
+  }, fail: fail, eventually: eventually);
+}
+{{end}}
+{{end}}`
+
+func genApi(dir string, api *spec.ApiSpec, isLegacy bool) error {
 	err := os.MkdirAll(dir, 0o755)
 	if err != nil {
 		return err
 	}
 
-	err = genApiFile(dir)
+	err = genApiFile(dir, isLegacy)
 	if err != nil {
 		return err
 	}
 
-	file, err := os.OpenFile(dir+api.Service.Name+".dart", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
+	file, err := os.OpenFile(dir+strings.ToLower(api.Service.Name+".dart"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
 	if err != nil {
 		return err
 	}
@@ -47,7 +69,11 @@ func genApi(dir string, api *spec.ApiSpec) error {
 	defer file.Close()
 	t := template.New("apiTemplate")
 	t = t.Funcs(funcMap)
-	t, err = t.Parse(apiTemplate)
+	tpl := apiTemplateV2
+	if isLegacy {
+		tpl = apiTemplate
+	}
+	t, err = t.Parse(tpl)
 	if err != nil {
 		return err
 	}
@@ -55,7 +81,7 @@ func genApi(dir string, api *spec.ApiSpec) error {
 	return t.Execute(file, api)
 }
 
-func genApiFile(dir string) error {
+func genApiFile(dir string, isLegacy bool) error {
 	path := dir + "api.dart"
 	if fileExists(path) {
 		return nil
@@ -66,6 +92,10 @@ func genApiFile(dir string) error {
 	}
 
 	defer apiFile.Close()
-	_, err = apiFile.WriteString(apiFileContent)
+	tpl := apiFileContentV2
+	if isLegacy {
+		tpl = apiFileContent
+	}
+	_, err = apiFile.WriteString(tpl)
 	return err
 }
diff --git a/tools/goctl/api/dartgen/gendata.go b/tools/goctl/api/dartgen/gendata.go
index 6b33e610..90d97ab2 100644
--- a/tools/goctl/api/dartgen/gendata.go
+++ b/tools/goctl/api/dartgen/gendata.go
@@ -2,6 +2,7 @@ package dartgen
 
 import (
 	"os"
+	"strings"
 	"text/template"
 
 	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
@@ -19,30 +20,54 @@ class {{.Name}}{
 	});
 	factory {{.Name}}.fromJson(Map<String,dynamic> m) {
 		return {{.Name}}({{range .Members}}
-			{{lowCamelCase .Name}}: {{if isDirectType .Type.Name}}m['{{tagGet .Tag "json"}}']{{else if isClassListType .Type.Name}}(m['{{tagGet .Tag "json"}}'] as List<dynamic>).map((i) => {{getCoreType .Type.Name}}.fromJson(i)){{else}}{{.Type.Name}}.fromJson(m['{{tagGet .Tag "json"}}']){{end}},{{end}}
+			{{lowCamelCase .Name}}: {{if isDirectType .Type.Name}}m['{{getPropertyFromMember .}}']{{else if isClassListType .Type.Name}}(m['{{getPropertyFromMember .}}'] as List<dynamic>).map((i) => {{getCoreType .Type.Name}}.fromJson(i)){{else}}{{.Type.Name}}.fromJson(m['{{getPropertyFromMember .}}']){{end}},{{end}}
 		);
 	}
 	Map<String,dynamic> toJson() {
 		return { {{range .Members}}
-			'{{tagGet .Tag "json"}}': {{if isDirectType .Type.Name}}{{lowCamelCase .Name}}{{else if isClassListType .Type.Name}}{{lowCamelCase .Name}}.map((i) => i.toJson()){{else}}{{lowCamelCase .Name}}.toJson(){{end}},{{end}}
+			'{{getPropertyFromMember .}}': {{if isDirectType .Type.Name}}{{lowCamelCase .Name}}{{else if isClassListType .Type.Name}}{{lowCamelCase .Name}}.map((i) => i.toJson()){{else}}{{lowCamelCase .Name}}.toJson(){{end}},{{end}}
 		};
 	}
 }
 {{end}}
 `
 
-func genData(dir string, api *spec.ApiSpec) error {
+const dataTemplateV2 = `// --{{with .Info}}{{.Title}}{{end}}--
+{{ range .Types}}
+class {{.Name}} {
+	{{range .Members}}
+	{{if .Comment}}{{.Comment}}{{end}}
+	final {{.Type.Name}} {{lowCamelCase .Name}};
+  {{end}}{{.Name}}({{if .Members}}{
+	{{range .Members}}  required this.{{lowCamelCase .Name}},
+	{{end}}}{{end}});
+	factory {{.Name}}.fromJson(Map<String,dynamic> m) {
+		return {{.Name}}({{range .Members}}
+			{{lowCamelCase .Name}}: {{if isDirectType .Type.Name}}m['{{getPropertyFromMember .}}']
+			{{else if isClassListType .Type.Name}}(m['{{getPropertyFromMember .}}'] as List<dynamic>).map((i) => {{getCoreType .Type.Name}}.fromJson(i)).toList()
+			{{else}}{{.Type.Name}}.fromJson(m['{{getPropertyFromMember .}}']){{end}},{{end}}
+		);
+	}
+	Map<String,dynamic> toJson() {
+		return { {{range .Members}}
+			'{{getPropertyFromMember .}}': {{if isDirectType .Type.Name}}{{lowCamelCase .Name}}{{else if isClassListType .Type.Name}}{{lowCamelCase .Name}}.map((i) => i.toJson()){{else}}{{lowCamelCase .Name}}.toJson(){{end}},{{end}}
+		};
+	}
+}
+{{end}}`
+
+func genData(dir string, api *spec.ApiSpec, isLegacy bool) error {
 	err := os.MkdirAll(dir, 0o755)
 	if err != nil {
 		return err
 	}
 
-	err = genTokens(dir)
+	err = genTokens(dir, isLegacy)
 	if err != nil {
 		return err
 	}
 
-	file, err := os.OpenFile(dir+api.Service.Name+".dart", os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
+	file, err := os.OpenFile(dir+strings.ToLower(api.Service.Name+".dart"), os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0o644)
 	if err != nil {
 		return err
 	}
@@ -50,7 +75,11 @@ func genData(dir string, api *spec.ApiSpec) error {
 
 	t := template.New("dataTemplate")
 	t = t.Funcs(funcMap)
-	t, err = t.Parse(dataTemplate)
+	tpl := dataTemplateV2
+	if isLegacy {
+		tpl = dataTemplate
+	}
+	t, err = t.Parse(tpl)
 	if err != nil {
 		return err
 	}
@@ -63,7 +92,7 @@ func genData(dir string, api *spec.ApiSpec) error {
 	return t.Execute(file, api)
 }
 
-func genTokens(dir string) error {
+func genTokens(dir string, isLeagcy bool) error {
 	path := dir + "tokens.dart"
 	if fileExists(path) {
 		return nil
@@ -75,7 +104,11 @@ func genTokens(dir string) error {
 	}
 
 	defer tokensFile.Close()
-	_, err = tokensFile.WriteString(tokensFileContent)
+	tpl := tokensFileContentV2
+	if isLeagcy {
+		tpl = tokensFileContent
+	}
+	_, err = tokensFile.WriteString(tpl)
 	return err
 }
 
diff --git a/tools/goctl/api/dartgen/genvars.go b/tools/goctl/api/dartgen/genvars.go
index c6b90a56..51a9fa13 100644
--- a/tools/goctl/api/dartgen/genvars.go
+++ b/tools/goctl/api/dartgen/genvars.go
@@ -1,11 +1,13 @@
 package dartgen
 
 import (
+	"fmt"
 	"io/ioutil"
 	"os"
 )
 
-const varTemplate = `import 'dart:convert';
+const (
+	varTemplate = `import 'dart:convert';
 import 'package:shared_preferences/shared_preferences.dart';
 import '../data/tokens.dart';
 
@@ -40,21 +42,59 @@ Future<Tokens> getTokens() async {
 }
 `
 
-func genVars(dir string) error {
+	varTemplateV2 = `import 'dart:convert';
+import 'package:shared_preferences/shared_preferences.dart';
+import '../data/tokens.dart';
+
+const String _tokenKey = 'tokens';
+
+/// Saves tokens
+Future<bool> setTokens(Tokens tokens) async {
+  var sp = await SharedPreferences.getInstance();
+  return await sp.setString(_tokenKey, jsonEncode(tokens.toJson()));
+}
+
+/// remove tokens
+Future<bool> removeTokens() async {
+  var sp = await SharedPreferences.getInstance();
+  return sp.remove(_tokenKey);
+}
+
+/// Reads tokens
+Future<Tokens?> getTokens() async {
+  try {
+    var sp = await SharedPreferences.getInstance();
+    var str = sp.getString('tokens');
+    if (str.isEmpty) {
+      return null;
+    }
+    return Tokens.fromJson(jsonDecode(str));
+  } catch (e) {
+    print(e);
+    return null;
+  }
+}`
+)
+
+func genVars(dir string, isLegacy bool, hostname string) error {
 	err := os.MkdirAll(dir, 0o755)
 	if err != nil {
 		return err
 	}
 
 	if !fileExists(dir + "vars.dart") {
-		err = ioutil.WriteFile(dir+"vars.dart", []byte(`const serverHost='demo-crm.xiaoheiban.cn';`), 0o644)
+		err = ioutil.WriteFile(dir+"vars.dart", []byte(fmt.Sprintf(`const serverHost='%s';`, hostname)), 0o644)
 		if err != nil {
 			return err
 		}
 	}
 
 	if !fileExists(dir + "kv.dart") {
-		err = ioutil.WriteFile(dir+"kv.dart", []byte(varTemplate), 0o644)
+		tpl := varTemplateV2
+		if isLegacy {
+			tpl = varTemplate
+		}
+		err = ioutil.WriteFile(dir+"kv.dart", []byte(tpl), 0o644)
 		if err != nil {
 			return err
 		}
diff --git a/tools/goctl/api/dartgen/util.go b/tools/goctl/api/dartgen/util.go
index 5e5fe7e3..b6a52f15 100644
--- a/tools/goctl/api/dartgen/util.go
+++ b/tools/goctl/api/dartgen/util.go
@@ -4,6 +4,7 @@ import (
 	"errors"
 	"fmt"
 	"os"
+	"path"
 	"strings"
 
 	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
@@ -34,18 +35,16 @@ func pathToFuncName(path string) string {
 	return util.ToLower(camel[:1]) + camel[1:]
 }
 
-func tagGet(tag, k string) string {
-	tags, err := spec.Parse(tag)
-	if err != nil {
-		panic(k + " not exist")
-	}
+func getBaseName(str string) string {
+	return path.Base(str)
+}
 
-	v, err := tags.Get(k)
+func getPropertyFromMember(member spec.Member) string {
+	name, err := member.GetPropertyName()
 	if err != nil {
-		panic(k + " value not exist")
+		panic(fmt.Sprintf("cannot get property name of %q", member.Name))
 	}
-
-	return v.Name
+	return name
 }
 
 func isDirectType(s string) bool {
@@ -129,11 +128,10 @@ func specTypeToDart(tp spec.Type) (string, error) {
 		}
 
 		s := getBaseType(valueType)
-		if len(s) == 0 {
-			return s, errors.New("unsupported primitive type " + tp.Name())
+		if len(s) != 0 {
+			return s, nil
 		}
-
-		return s, nil
+		return fmt.Sprintf("List<%s>", valueType), nil
 	case spec.InterfaceType:
 		return "Object", nil
 	case spec.PointerType:
diff --git a/tools/goctl/api/dartgen/vars.go b/tools/goctl/api/dartgen/vars.go
index 78f95e1f..621a7c3a 100644
--- a/tools/goctl/api/dartgen/vars.go
+++ b/tools/goctl/api/dartgen/vars.go
@@ -3,12 +3,13 @@ package dartgen
 import "text/template"
 
 var funcMap = template.FuncMap{
-	"tagGet":          tagGet,
-	"isDirectType":    isDirectType,
-	"isClassListType": isClassListType,
-	"getCoreType":     getCoreType,
-	"pathToFuncName":  pathToFuncName,
-	"lowCamelCase":    lowCamelCase,
+	"getBaseName":           getBaseName,
+	"getPropertyFromMember": getPropertyFromMember,
+	"isDirectType":          isDirectType,
+	"isClassListType":       isClassListType,
+	"getCoreType":           getCoreType,
+	"pathToFuncName":        pathToFuncName,
+	"lowCamelCase":          lowCamelCase,
 }
 
 const (
@@ -99,6 +100,96 @@ Future _apiRequest(String method, String path, dynamic data,
 }
 `
 
+	apiFileContentV2 = `import 'dart:io';
+	import 'dart:convert';
+	import '../vars/kv.dart';
+	import '../vars/vars.dart';
+
+	/// send request with post method
+	///
+	/// data: any request class that will be converted to json automatically
+	/// ok: is called when request succeeds
+	/// fail: is called when request fails
+	/// eventually: is always called until the nearby functions returns
+	Future apiPost(String path, dynamic data,
+			{Map<String, String>? header,
+			Function(Map<String, dynamic>)? ok,
+			Function(String)? fail,
+			Function? eventually}) async {
+		await _apiRequest('POST', path, data,
+				header: header, ok: ok, fail: fail, eventually: eventually);
+	}
+
+	/// send request with get method
+	///
+	/// ok: is called when request succeeds
+	/// fail: is called when request fails
+	/// eventually: is always called until the nearby functions returns
+	Future apiGet(String path,
+			{Map<String, String>? header,
+			Function(Map<String, dynamic>)? ok,
+			Function(String)? fail,
+			Function? eventually}) async {
+		await _apiRequest('GET', path, null,
+				header: header, ok: ok, fail: fail, eventually: eventually);
+	}
+
+	Future _apiRequest(String method, String path, dynamic data,
+			{Map<String, String>? header,
+			Function(Map<String, dynamic>)? ok,
+			Function(String)? fail,
+			Function? eventually}) async {
+		var tokens = await getTokens();
+		try {
+			var client = HttpClient();
+			HttpClientRequest r;
+			if (method == 'POST') {
+				r = await client.postUrl(Uri.parse('https://' + serverHost + path));
+			} else {
+				r = await client.getUrl(Uri.parse('https://' + serverHost + path));
+			}
+
+			r.headers.set('Content-Type', 'application/json');
+			if (tokens != null) {
+				r.headers.set('Authorization', tokens.accessToken);
+			}
+			if (header != null) {
+				header.forEach((k, v) {
+					r.headers.set(k, v);
+				});
+			}
+			var strData = '';
+			if (data != null) {
+				strData = jsonEncode(data);
+			}
+			r.write(strData);
+			var rp = await r.close();
+			var body = await rp.transform(utf8.decoder).join();
+			print('${rp.statusCode} - $path');
+			print('-- request --');
+			print(strData);
+			print('-- response --');
+			print('$body \n');
+			if (rp.statusCode == 404) {
+				if (fail != null) fail('404 not found');
+			} else {
+				Map<String, dynamic> base = jsonDecode(body);
+				if (rp.statusCode == 200) {
+					if (base['code'] != 0) {
+						if (fail != null) fail(base['desc']);
+					} else {
+						if (ok != null) ok(base['data']);
+					}
+				} else if (base['code'] != 0) {
+					if (fail != null) fail(base['desc']);
+				}
+			}
+		} catch (e) {
+			if (fail != null) fail(e.toString());
+		}
+		if (eventually != null) eventually();
+	}`
+
 	tokensFileContent = `class Tokens {
   /// 用于访问的token, 每次请求都必须带在Header里面
   final String accessToken;
@@ -132,5 +223,41 @@ Future _apiRequest(String method, String path, dynamic data,
     };
   }
 }
+`
+
+	tokensFileContentV2 = `class Tokens {
+  /// 用于访问的token, 每次请求都必须带在Header里面
+  final String accessToken;
+  final int accessExpire;
+
+  /// 用于刷新token
+  final String refreshToken;
+  final int refreshExpire;
+  final int refreshAfter;
+  Tokens({
+		required this.accessToken,
+		required this.accessExpire,
+		required this.refreshToken,
+		required this.refreshExpire,
+		required this.refreshAfter
+	});
+  factory Tokens.fromJson(Map<String, dynamic> m) {
+    return Tokens(
+        accessToken: m['access_token'],
+        accessExpire: m['access_expire'],
+        refreshToken: m['refresh_token'],
+        refreshExpire: m['refresh_expire'],
+        refreshAfter: m['refresh_after']);
+  }
+  Map<String, dynamic> toJson() {
+    return {
+      'access_token': accessToken,
+      'access_expire': accessExpire,
+      'refresh_token': refreshToken,
+      'refresh_expire': refreshExpire,
+      'refresh_after': refreshAfter,
+    };
+  }
+}
 `
 )
diff --git a/tools/goctl/api/format/format.go b/tools/goctl/api/format/format.go
index 79672952..02cbd222 100644
--- a/tools/goctl/api/format/format.go
+++ b/tools/goctl/api/format/format.go
@@ -28,10 +28,11 @@ const (
 // GoFormatApi format api file
 func GoFormatApi(c *cli.Context) error {
 	useStdin := c.Bool("stdin")
+	skipCheckDeclare := c.Bool("declare")
 
 	var be errorx.BatchError
 	if useStdin {
-		if err := apiFormatByStdin(); err != nil {
+		if err := apiFormatByStdin(skipCheckDeclare); err != nil {
 			be.Add(err)
 		}
 	} else {
@@ -47,7 +48,7 @@ func GoFormatApi(c *cli.Context) error {
 
 		err = filepath.Walk(dir, func(path string, fi os.FileInfo, errBack error) (err error) {
 			if strings.HasSuffix(path, ".api") {
-				if err := ApiFormatByPath(path); err != nil {
+				if err := ApiFormatByPath(path, skipCheckDeclare); err != nil {
 					be.Add(util.WrapErr(err, fi.Name()))
 				}
 			}
@@ -64,13 +65,13 @@ func GoFormatApi(c *cli.Context) error {
 	return be.Err()
 }
 
-func apiFormatByStdin() error {
+func apiFormatByStdin(skipCheckDeclare bool) error {
 	data, err := ioutil.ReadAll(os.Stdin)
 	if err != nil {
 		return err
 	}
 
-	result, err := apiFormat(string(data))
+	result, err := apiFormat(string(data), skipCheckDeclare)
 	if err != nil {
 		return err
 	}
@@ -80,7 +81,7 @@ func apiFormatByStdin() error {
 }
 
 // ApiFormatByPath format api from file path
-func ApiFormatByPath(apiFilePath string) error {
+func ApiFormatByPath(apiFilePath string, skipCheckDeclare bool) error {
 	data, err := ioutil.ReadFile(apiFilePath)
 	if err != nil {
 		return err
@@ -91,12 +92,12 @@ func ApiFormatByPath(apiFilePath string) error {
 		return err
 	}
 
-	result, err := apiFormat(string(data), abs)
+	result, err := apiFormat(string(data), skipCheckDeclare, abs)
 	if err != nil {
 		return err
 	}
 
-	_, err = parser.ParseContent(result, abs)
+	_, err = parser.ParseContentWithParserSkipCheckTypeDeclaration(result, abs)
 	if err != nil {
 		return err
 	}
@@ -104,8 +105,13 @@ func ApiFormatByPath(apiFilePath string) error {
 	return ioutil.WriteFile(apiFilePath, []byte(result), os.ModePerm)
 }
 
-func apiFormat(data string, filename ...string) (string, error) {
-	_, err := parser.ParseContent(data, filename...)
+func apiFormat(data string, skipCheckDeclare bool, filename ...string) (string, error) {
+	var err error
+	if skipCheckDeclare {
+		_, err = parser.ParseContentWithParserSkipCheckTypeDeclaration(data, filename...)
+	} else {
+		_, err = parser.ParseContent(data, filename...)
+	}
 	if err != nil {
 		return "", err
 	}
diff --git a/tools/goctl/api/gogen/gen.go b/tools/goctl/api/gogen/gen.go
index 2d3a0f6a..14b81fcd 100644
--- a/tools/goctl/api/gogen/gen.go
+++ b/tools/goctl/api/gogen/gen.go
@@ -33,8 +33,9 @@ func GoCommand(c *cli.Context) error {
 	namingStyle := c.String("style")
 	home := c.String("home")
 	remote := c.String("remote")
+	branch := c.String("branch")
 	if len(remote) > 0 {
-		repo, _ := util.CloneIntoGitHome(remote)
+		repo, _ := util.CloneIntoGitHome(remote, branch)
 		if len(repo) > 0 {
 			home = repo
 		}
@@ -85,7 +86,7 @@ func DoGenProject(apiFile, dir, style string) error {
 		return err
 	}
 
-	if err := apiformat.ApiFormatByPath(apiFile); err != nil {
+	if err := apiformat.ApiFormatByPath(apiFile, false); err != nil {
 		return err
 	}
 
diff --git a/tools/goctl/api/ktgen/gen.go b/tools/goctl/api/ktgen/gen.go
index 40703749..73969cf4 100644
--- a/tools/goctl/api/ktgen/gen.go
+++ b/tools/goctl/api/ktgen/gen.go
@@ -80,7 +80,7 @@ import com.google.gson.Gson
 object {{with .Info}}{{.Title}}{{end}}{
 	{{range .Types}}
 	data class {{.Name}}({{$length := (len .Members)}}{{range $i,$item := .Members}}
-		val {{with $item}}{{lowCamelCase .Name}}: {{parseType .Type}}{{end}}{{if ne $i (add $length -1)}},{{end}}{{end}}
+		val {{with $item}}{{lowCamelCase .Name}}: {{parseType .Type.Name}}{{end}}{{if ne $i (add $length -1)}},{{end}}{{end}}
 	){{end}}
 	{{with .Service}}
 	{{range .Routes}}suspend fun {{routeToFuncName .Method .Path}}({{with .RequestType}}{{if ne .Name ""}}
diff --git a/tools/goctl/api/new/newservice.go b/tools/goctl/api/new/newservice.go
index f978aeec..83b6fd20 100644
--- a/tools/goctl/api/new/newservice.go
+++ b/tools/goctl/api/new/newservice.go
@@ -67,8 +67,9 @@ func CreateServiceCommand(c *cli.Context) error {
 
 	home := c.String("home")
 	remote := c.String("remote")
+	branch := c.String("branch")
 	if len(remote) > 0 {
-		repo, _ := util.CloneIntoGitHome(remote)
+		repo, _ := util.CloneIntoGitHome(remote, branch)
 		if len(repo) > 0 {
 			home = repo
 		}
diff --git a/tools/goctl/api/parser/g4/ast/api.go b/tools/goctl/api/parser/g4/ast/api.go
index d657b9f4..cc747eaf 100644
--- a/tools/goctl/api/parser/g4/ast/api.go
+++ b/tools/goctl/api/parser/g4/ast/api.go
@@ -8,8 +8,10 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/api/parser/g4/gen/api"
 )
 
-const prefixKey = "prefix"
-const groupKey = "group"
+const (
+	prefixKey = "prefix"
+	groupKey  = "group"
+)
 
 // Api describes syntax for api
 type Api struct {
diff --git a/tools/goctl/api/parser/g4/ast/apiparser.go b/tools/goctl/api/parser/g4/ast/apiparser.go
index 29d69da3..b19ba666 100644
--- a/tools/goctl/api/parser/g4/ast/apiparser.go
+++ b/tools/goctl/api/parser/g4/ast/apiparser.go
@@ -19,7 +19,8 @@ type (
 		debug      bool
 		log        console.Console
 		antlr.DefaultErrorListener
-		src string
+		src                      string
+		skipCheckTypeDeclaration bool
 	}
 
 	// ParserOption defines an function with argument Parser
@@ -114,13 +115,16 @@ func (p *Parser) parse(filename, content string) (*Api, error) {
 	apiAstList = append(apiAstList, root)
 	for _, imp := range root.Import {
 		dir := filepath.Dir(p.src)
-		imp := filepath.Join(dir, imp.Value.Text())
-		data, err := p.readContent(imp)
+		impPath := strings.ReplaceAll(imp.Value.Text(), "\"", "")
+		if !filepath.IsAbs(impPath) {
+			impPath = filepath.Join(dir, impPath)
+		}
+		data, err := p.readContent(impPath)
 		if err != nil {
 			return nil, err
 		}
 
-		nestedApi, err := p.invoke(imp, data)
+		nestedApi, err := p.invoke(impPath, data)
 		if err != nil {
 			return nil, err
 		}
@@ -133,9 +137,11 @@ func (p *Parser) parse(filename, content string) (*Api, error) {
 		apiAstList = append(apiAstList, nestedApi)
 	}
 
-	err = p.checkTypeDeclaration(apiAstList)
-	if err != nil {
-		return nil, err
+	if !p.skipCheckTypeDeclaration {
+		err = p.checkTypeDeclaration(apiAstList)
+		if err != nil {
+			return nil, err
+		}
 	}
 
 	allApi := p.memberFill(apiAstList)
@@ -480,3 +486,9 @@ func WithParserPrefix(prefix string) ParserOption {
 		p.linePrefix = prefix
 	}
 }
+
+func WithParserSkipCheckTypeDeclaration() ParserOption {
+	return func(p *Parser) {
+		p.skipCheckTypeDeclaration = true
+	}
+}
diff --git a/tools/goctl/api/parser/g4/gen/api/baseparser.go b/tools/goctl/api/parser/g4/gen/api/baseparser.go
index 3d6772e5..32b53093 100644
--- a/tools/goctl/api/parser/g4/gen/api/baseparser.go
+++ b/tools/goctl/api/parser/g4/gen/api/baseparser.go
@@ -12,7 +12,7 @@ import (
 
 const (
 	versionRegex     = `(?m)"v[1-9][0-9]*"`
-	importValueRegex = `(?m)"(/?[a-zA-Z0-9_#-])+\.api"`
+	importValueRegex = `(?m)"\/?(([a-zA-Z0-9.]+)+(\/?){1})+([a-zA-Z0-9]+)+\.api"`
 	tagRegex         = `(?m)\x60[a-z]+:".+"\x60`
 )
 
diff --git a/tools/goctl/api/parser/parser.go b/tools/goctl/api/parser/parser.go
index 8469605b..dadfe952 100644
--- a/tools/goctl/api/parser/parser.go
+++ b/tools/goctl/api/parser/parser.go
@@ -33,9 +33,13 @@ func Parse(filename string) (*spec.ApiSpec, error) {
 	return spec, nil
 }
 
-// ParseContent parses the api content
-func ParseContent(content string, filename ...string) (*spec.ApiSpec, error) {
-	astParser := ast.NewParser()
+func parseContent(content string, skipCheckTypeDeclaration bool, filename ...string) (*spec.ApiSpec, error) {
+	var astParser *ast.Parser
+	if skipCheckTypeDeclaration {
+		astParser = ast.NewParser(ast.WithParserSkipCheckTypeDeclaration())
+	} else {
+		astParser = ast.NewParser()
+	}
 	ast, err := astParser.ParseContent(content, filename...)
 	if err != nil {
 		return nil, err
@@ -51,6 +55,16 @@ func ParseContent(content string, filename ...string) (*spec.ApiSpec, error) {
 	return spec, nil
 }
 
+// ParseContent parses the api content
+func ParseContent(content string, filename ...string) (*spec.ApiSpec, error) {
+	return parseContent(content, false, filename...)
+}
+
+// ParseContentWithParserSkipCheckTypeDeclaration parses the api content with skip check type declaration
+func ParseContentWithParserSkipCheckTypeDeclaration(content string, filename ...string) (*spec.ApiSpec, error) {
+	return parseContent(content, true, filename...)
+}
+
 func (p parser) convert2Spec() error {
 	p.fillInfo()
 	p.fillSyntax()
diff --git a/tools/goctl/completion/const.go b/tools/goctl/completion/const.go
index 2dd95714..9a66c746 100644
--- a/tools/goctl/completion/const.go
+++ b/tools/goctl/completion/const.go
@@ -1,7 +1,10 @@
 package completion
 
-const BashCompletionFlag = `generate-goctl-completion`
-const defaultCompletionFilename = "goctl_autocomplete"
+const (
+	BashCompletionFlag        = `generate-goctl-completion`
+	defaultCompletionFilename = "goctl_autocomplete"
+)
+
 const (
 	magic = 1 << iota
 	flagZsh
diff --git a/tools/goctl/docker/docker.go b/tools/goctl/docker/docker.go
index e17165a7..d8292509 100644
--- a/tools/goctl/docker/docker.go
+++ b/tools/goctl/docker/docker.go
@@ -24,14 +24,17 @@ const (
 
 // Docker describes a dockerfile
 type Docker struct {
-	Chinese   bool
-	GoRelPath string
-	GoFile    string
-	ExeFile   string
-	HasPort   bool
-	Port      int
-	Argument  string
-	Version   string
+	Chinese     bool
+	GoRelPath   string
+	GoFile      string
+	ExeFile     string
+	Scratch     bool
+	HasPort     bool
+	Port        int
+	Argument    string
+	Version     string
+	HasTimezone bool
+	Timezone    string
 }
 
 // DockerCommand provides the entry for goctl docker
@@ -46,8 +49,10 @@ func DockerCommand(c *cli.Context) (err error) {
 	home := c.String("home")
 	version := c.String("version")
 	remote := c.String("remote")
+	branch := c.String("branch")
+	timezone := c.String("tz")
 	if len(remote) > 0 {
-		repo, _ := util.CloneIntoGitHome(remote)
+		repo, _ := util.CloneIntoGitHome(remote, branch)
 		if len(repo) > 0 {
 			home = repo
 		}
@@ -69,9 +74,10 @@ func DockerCommand(c *cli.Context) (err error) {
 		return fmt.Errorf("file %q not found", goFile)
 	}
 
+	scratch := c.Bool("scratch")
 	port := c.Int("port")
 	if _, err := os.Stat(etcDir); os.IsNotExist(err) {
-		return generateDockerfile(goFile, port, version)
+		return generateDockerfile(goFile, scratch, port, version, timezone)
 	}
 
 	cfg, err := findConfig(goFile, etcDir)
@@ -79,7 +85,7 @@ func DockerCommand(c *cli.Context) (err error) {
 		return err
 	}
 
-	if err := generateDockerfile(goFile, port, version, "-f", "etc/"+cfg); err != nil {
+	if err := generateDockerfile(goFile, scratch, port, version, timezone, "-f", "etc/"+cfg); err != nil {
 		return err
 	}
 
@@ -120,7 +126,7 @@ func findConfig(file, dir string) (string, error) {
 	return files[0], nil
 }
 
-func generateDockerfile(goFile string, port int, version string, args ...string) error {
+func generateDockerfile(goFile string, scratch bool, port int, version, timezone string, args ...string) error {
 	projPath, err := getFilePath(filepath.Dir(goFile))
 	if err != nil {
 		return err
@@ -149,14 +155,17 @@ func generateDockerfile(goFile string, port int, version string, args ...string)
 	_, offset := time.Now().Zone()
 	t := template.Must(template.New("dockerfile").Parse(text))
 	return t.Execute(out, Docker{
-		Chinese:   offset == cstOffset,
-		GoRelPath: projPath,
-		GoFile:    goFile,
-		ExeFile:   pathx.FileNameWithoutExt(filepath.Base(goFile)),
-		HasPort:   port > 0,
-		Port:      port,
-		Argument:  builder.String(),
-		Version:   version,
+		Chinese:     offset == cstOffset,
+		GoRelPath:   projPath,
+		GoFile:      goFile,
+		ExeFile:     pathx.FileNameWithoutExt(filepath.Base(goFile)),
+		Scratch:     scratch,
+		HasPort:     port > 0,
+		Port:        port,
+		Argument:    builder.String(),
+		Version:     version,
+		HasTimezone: len(timezone) > 0,
+		Timezone:    timezone,
 	})
 }
 
diff --git a/tools/goctl/docker/template.go b/tools/goctl/docker/template.go
index 929b8026..6d21d3c6 100644
--- a/tools/goctl/docker/template.go
+++ b/tools/goctl/docker/template.go
@@ -15,8 +15,10 @@ LABEL stage=gobuilder
 ENV CGO_ENABLED 0
 ENV GOOS linux
 {{if .Chinese}}ENV GOPROXY https://goproxy.cn,direct
+{{end}}{{if .HasTimezone}}
+RUN apk update --no-cache && apk add --no-cache tzdata
 {{end}}
-WORKDIR /build/zero
+WORKDIR /build
 
 ADD go.mod .
 ADD go.sum .
@@ -26,11 +28,12 @@ COPY . .
 {{end}}RUN go build -ldflags="-s -w" -o /app/{{.ExeFile}} {{.GoRelPath}}/{{.GoFile}}
 
 
-FROM alpine
-
-RUN apk update --no-cache && apk add --no-cache ca-certificates tzdata
-ENV TZ Asia/Shanghai
+FROM {{if .Scratch}}scratch{{else}}alpine{{end}}
 
+{{if .Scratch}}COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/ca-certificates.crt{{else}}RUN apk update --no-cache && apk add --no-cache ca-certificates{{end}}
+{{if .HasTimezone}}COPY --from=builder /usr/share/zoneinfo/{{.Timezone}} /usr/share/zoneinfo/{{.Timezone}}
+ENV TZ {{.Timezone}}
+{{end}}
 WORKDIR /app
 COPY --from=builder /app/{{.ExeFile}} /app/{{.ExeFile}}{{if .Argument}}
 COPY --from=builder /app/etc /app/etc{{end}}
diff --git a/tools/goctl/env/check.go b/tools/goctl/env/check.go
new file mode 100644
index 00000000..044e39e8
--- /dev/null
+++ b/tools/goctl/env/check.go
@@ -0,0 +1,112 @@
+package env
+
+import (
+	"fmt"
+	"strings"
+	"time"
+
+	"github.com/urfave/cli"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/env"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/protoc"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/protocgengo"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/protocgengogrpc"
+	"github.com/zeromicro/go-zero/tools/goctl/util/console"
+)
+
+type bin struct {
+	name   string
+	exists bool
+	get    func(cacheDir string) (string, error)
+}
+
+var bins = []bin{
+	{
+		name:   "protoc",
+		exists: protoc.Exists(),
+		get:    protoc.Install,
+	},
+	{
+		name:   "protoc-gen-go",
+		exists: protocgengo.Exists(),
+		get:    protocgengo.Install,
+	},
+	{
+		name:   "protoc-gen-go-grpc",
+		exists: protocgengogrpc.Exists(),
+		get:    protocgengogrpc.Install,
+	},
+}
+
+func Check(ctx *cli.Context) error {
+	install := ctx.Bool("install")
+	force := ctx.Bool("force")
+	return Prepare(install, force)
+}
+
+func Prepare(install, force bool) error {
+	pending := true
+	console.Info("[goctl-env]: preparing to check env")
+	defer func() {
+		if p := recover(); p != nil {
+			console.Error("%+v", p)
+			return
+		}
+		if pending {
+			console.Success("\n[goctl-env]: congratulations! your goctl environment is ready!")
+		} else {
+			console.Error(`
+[goctl-env]: check env finish, some dependencies is not found in PATH, you can execute
+command 'goctl env check --install' to install it, for details, please execute command 
+'goctl env check --help'`)
+		}
+	}()
+	for _, e := range bins {
+		time.Sleep(200 * time.Millisecond)
+		console.Info("")
+		console.Info("[goctl-env]: looking up %q", e.name)
+		if e.exists {
+			console.Success("[goctl-env]: %q is installed", e.name)
+			continue
+		}
+		console.Warning("[goctl-env]: %q is not found in PATH", e.name)
+		if install {
+			install := func() {
+				console.Info("[goctl-env]: preparing to install %q", e.name)
+				path, err := e.get(env.Get(env.GoctlCache))
+				if err != nil {
+					console.Error("[goctl-env]: an error interrupted the installation: %+v", err)
+					pending = false
+				} else {
+					console.Success("[goctl-env]: %q is already installed in %q", e.name, path)
+				}
+			}
+			if force {
+				install()
+				continue
+			}
+			console.Info("[goctl-env]: do you want to install %q [y: YES, n: No]", e.name)
+			for {
+				var in string
+				fmt.Scanln(&in)
+				var brk bool
+				switch {
+				case strings.EqualFold(in, "y"):
+					install()
+					brk = true
+				case strings.EqualFold(in, "n"):
+					pending = false
+					console.Info("[goctl-env]: %q installation is ignored", e.name)
+					brk = true
+				default:
+					console.Error("[goctl-env]: invalid input, input 'y' for yes, 'n' for no")
+				}
+				if brk {
+					break
+				}
+			}
+		} else {
+			pending = false
+		}
+	}
+	return nil
+}
diff --git a/tools/goctl/env/env.go b/tools/goctl/env/env.go
new file mode 100644
index 00000000..04b468e2
--- /dev/null
+++ b/tools/goctl/env/env.go
@@ -0,0 +1,17 @@
+package env
+
+import (
+	"fmt"
+
+	"github.com/urfave/cli"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/env"
+)
+
+func Action(c *cli.Context) error {
+	write := c.StringSlice("write")
+	if len(write) > 0 {
+		return env.WriteEnv(write)
+	}
+	fmt.Println(env.Print())
+	return nil
+}
diff --git a/tools/goctl/go.mod b/tools/goctl/go.mod
index f43d8f30..ec1544b6 100644
--- a/tools/goctl/go.mod
+++ b/tools/goctl/go.mod
@@ -1,6 +1,6 @@
 module github.com/zeromicro/go-zero/tools/goctl
 
-go 1.15
+go 1.16
 
 require (
 	github.com/DATA-DOG/go-sqlmock v1.5.0
@@ -12,6 +12,6 @@ require (
 	github.com/stretchr/testify v1.7.0
 	github.com/urfave/cli v1.22.5
 	github.com/zeromicro/antlr v0.0.1
-	github.com/zeromicro/ddl-parser v0.0.0-20210712021150-63520aca7348
-	github.com/zeromicro/go-zero v1.3.0
+	github.com/zeromicro/ddl-parser v1.0.3
+	github.com/zeromicro/go-zero v1.3.1
 )
diff --git a/tools/goctl/go.sum b/tools/goctl/go.sum
index a3bbc0a8..1554883f 100644
--- a/tools/goctl/go.sum
+++ b/tools/goctl/go.sum
@@ -36,7 +36,6 @@ github.com/ClickHouse/clickhouse-go v1.5.1/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHg
 github.com/DATA-DOG/go-sqlmock v1.5.0 h1:Shsta01QNfFxHCfpW6YH2STWB0MudeXXEWMr20OEh60=
 github.com/DATA-DOG/go-sqlmock v1.5.0/go.mod h1:f/Ixk793poVmq4qj/V1dPUg2JEAKC73Q5eFN3EC/SaM=
 github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ=
-github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0=
 github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE=
 github.com/Shopify/sarama v1.30.0/go.mod h1:zujlQQx1kzHsh4jfV1USnptCQrHAEZ2Hk8fTKCulPVs=
@@ -54,13 +53,15 @@ github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kd
 github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec h1:EEyRvzmpEUZ+I8WmD5cw/vY8EqhambkOqy5iFr0908A=
 github.com/antlr/antlr4/runtime/Go/antlr v0.0.0-20210521184019-c5ad59b459ec/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY=
 github.com/asaskevich/govalidator v0.0.0-20190424111038-f61b66f89f4a/go.mod h1:lB+ZfQJz7igIIfQNfa7Ml4HSf2uFQQRzpGGRXenZAgY=
+github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA=
 github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
 github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
 github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
 github.com/bkaradzic/go-lz4 v1.0.0/go.mod h1:0YdlkowM3VswSROI7qDxhRvJ3sLhlFrRRwjwegp5jy4=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash/v2 v2.1.2 h1:YRXhKfTDauu4ajMg1TPgFO5jnlC2HCbmLXMcTG5cbYE=
+github.com/cespare/xxhash/v2 v2.1.2/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
 github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI=
 github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI=
 github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU=
@@ -69,7 +70,6 @@ github.com/cloudflare/golz4 v0.0.0-20150217214814-ef862a3cdc58/go.mod h1:EOBUe0h
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
-github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
@@ -82,6 +82,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs
 github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
+github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM=
 github.com/docopt/docopt-go v0.0.0-20180111231733-ee0de3bc6815/go.mod h1:WwZ+bS3ebgob9U8Nd0kOddGdZWjyMGR8Wziv+TBNwSE=
 github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
@@ -97,7 +99,6 @@ github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.m
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
 github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/evanphx/json-patch v4.9.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
@@ -138,8 +139,8 @@ github.com/go-openapi/jsonreference v0.19.3/go.mod h1:rjx6GuL8TTa9VaixXglHmQmIL9
 github.com/go-openapi/spec v0.19.3/go.mod h1:FpwSN1ksY1eteniUU7X0N/BgJ7a4WvBFVA8Lj9mJglo=
 github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
 github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk=
-github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg=
-github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA=
+github.com/go-redis/redis/v8 v8.11.4 h1:kHoYkfZP6+pe04aFTnhDH6GDROa5yJdHJVNxV3F46Tg=
+github.com/go-redis/redis/v8 v8.11.4/go.mod h1:2Z2wHZXdQpCDXEGzqMockDpNyYvi2l4Pxt6RJr792+w=
 github.com/go-sql-driver/mysql v1.4.0/go.mod h1:zAC/RDZ24gD3HViQzih4MyKcchzm+sOG5ZlKdlhCg5w=
 github.com/go-sql-driver/mysql v1.6.0 h1:BCTh4TKNUYmOmMUcQ3IipzF5prigylS7XXjEkfCHuOE=
 github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
@@ -173,7 +174,6 @@ github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QD
 github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
 github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
-github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
 github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q=
 github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
@@ -272,7 +272,6 @@ github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8m
 github.com/mwitkow/go-conntrack v0.0.0-20161129095857-cc309e4a2223/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f/go.mod h1:qRWi+5nqEBWmkhHvq77mSJWrCKwh8bxhgT7d/eI7P4U=
 github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw=
-github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32 h1:W6apQkHrMkS0Muv8G/TipAy/FJl/rCYT0+EuS8+Z0z4=
 github.com/nbio/st v0.0.0-20140626010706-e9e8d9816f32/go.mod h1:9wM+0iRr9ahx58uYLpLIr5fm8diHn0JbqRycJi6w0Ms=
 github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
 github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE=
@@ -329,7 +328,6 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
 github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
 github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
 github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
-github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spaolacci/murmur3 v1.1.0 h1:7c1g84S4BPRrfL5Xrdp6fOJ206sU9y293DDHaoy0bLI=
 github.com/spaolacci/murmur3 v1.1.0/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/spf13/afero v1.2.2/go.mod h1:9ZxEEn6pIJ8Rxe320qSDBk6AsU0r9pR7Q4OcevTdifk=
@@ -358,13 +356,13 @@ github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da h1:NimzV1aGyq29m5u
 github.com/yuin/gopher-lua v0.0.0-20200816102855-ee81675732da/go.mod h1:E1AXubJBdNmFERAOucpDIxNzeGfLzg0mYh+UfMWdChA=
 github.com/zeromicro/antlr v0.0.1 h1:CQpIn/dc0pUjgGQ81y98s/NGOm2Hfru2NNio2I9mQgk=
 github.com/zeromicro/antlr v0.0.1/go.mod h1:nfpjEwFR6Q4xGDJMcZnCL9tEfQRgszMwu3rDz2Z+p5M=
-github.com/zeromicro/ddl-parser v0.0.0-20210712021150-63520aca7348 h1:OhxL9tn28gDeJVzreIUiE5oVxZCjL3tBJ0XBNw8p5R8=
-github.com/zeromicro/ddl-parser v0.0.0-20210712021150-63520aca7348/go.mod h1:ISU/8NuPyEpl9pa17Py9TBPetMjtsiHrb9f5XGiYbo8=
-github.com/zeromicro/go-zero v1.3.0 h1:Eyn36yBtR043sm4YKmxR6eS3UA/GtZDktQ+UqIJ3Lm0=
-github.com/zeromicro/go-zero v1.3.0/go.mod h1:Hy4o1VFAt32lXaQMbaBhoFeZjA/rJqJ4PTGNdGsURcc=
-go.etcd.io/etcd/api/v3 v3.5.1/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQcPvs=
-go.etcd.io/etcd/client/pkg/v3 v3.5.1/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
-go.etcd.io/etcd/client/v3 v3.5.1/go.mod h1:OnjH4M8OnAotwaB2l9bVgZzRFKru7/ZMoS46OtKyd3Q=
+github.com/zeromicro/ddl-parser v1.0.3 h1:hFecpbt0oPQMhHAbqG1tz78MUepHUnOkFJp1dvRBFyc=
+github.com/zeromicro/ddl-parser v1.0.3/go.mod h1:ISU/8NuPyEpl9pa17Py9TBPetMjtsiHrb9f5XGiYbo8=
+github.com/zeromicro/go-zero v1.3.1 h1:uVkELq9kosgRZBSERb+eG7+oY2E+BEpOJW5vZZ354Cs=
+github.com/zeromicro/go-zero v1.3.1/go.mod h1:JsgCzJSUcjZl487xtqWHzYFa7Wl4f5Gi3lcteOWgNRA=
+go.etcd.io/etcd/api/v3 v3.5.2/go.mod h1:5GB2vv4A4AOn3yk7MftYGHkUfGtDHnEraIjym4dYz5A=
+go.etcd.io/etcd/client/pkg/v3 v3.5.2/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
+go.etcd.io/etcd/client/v3 v3.5.2/go.mod h1:kOOaWFFgHygyT0WlSmL8TJiXmMysO/nNUlEsSsN6W4o=
 go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
 go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
@@ -379,12 +377,15 @@ go.opentelemetry.io/otel/trace v1.3.0 h1:doy8Hzb1RJ+I3yFhtDmwNc7tIyw1tNMOIsyPzp1
 go.opentelemetry.io/otel/trace v1.3.0/go.mod h1:c/VDhno8888bvQYmbYLqe41/Ldmr/KKunbvWM4/fEjk=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/automaxprocs v1.4.0 h1:CpDZl6aOlLhReez+8S3eEotD7Jx0Os++lemPlMULQP0=
 go.uber.org/automaxprocs v1.4.0/go.mod h1:/mTEdr7LvHhs0v7mjdxDreTz1OG5zdZGqgOnhWiR/+Q=
-go.uber.org/goleak v1.1.12 h1:gZAh5/EyT/HQwlpkCy6wTpqfH9H8Lz8zbm3dZh+OyzA=
+go.uber.org/goleak v1.1.11/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/goleak v1.1.12/go.mod h1:cwTWslyiVhfpKIDGSZEM2HlOvcqm+tG4zioyIeLoqMQ=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
+go.uber.org/multierr v1.8.0/go.mod h1:7EAYxJLBy9rStEaz58O2t4Uvip6FSURkq8/ppBp95ak=
 go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
+go.uber.org/zap v1.21.0/go.mod h1:wjWOCqI0f2ZZrJF/UufIOkiC8ii6tm1iqIsLo76RfJw=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
@@ -417,7 +418,6 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs=
 golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
-golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 h1:VLliZ0d+/avPrXXH+OakdXhpJuEoBZuwh1m2j7U6Iug=
 golang.org/x/lint v0.0.0-20210508222113-6edffad5e616/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
 golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE=
 golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o=
@@ -427,7 +427,6 @@ golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzB
 golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.4.2 h1:Gz96sIWK3OalVv/I/qNygP42zyoKp3xptRVCWRFEBvo=
 golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -460,8 +459,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
 golang.org/x/net v0.0.0-20210917221730-978cfadd31cf/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d h1:1n1fc535VhN8SYtD4cDUyNlfpAF2ROMM9+11equK3hs=
-golang.org/x/net v0.0.0-20220114011407-0dd24b26b47d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f h1:oA4XRj0qtSt8Yo1Zms0CUlsT3KG69V2UGQWPBxujDmc=
+golang.org/x/net v0.0.0-20220225172249-27dd8689420f/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -521,9 +520,11 @@ golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210603081109-ebe580a85c40/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220111092808-5a964db01320 h1:0jf+tOCoZ3LyutmCOWpVni1chK4VfFLhRsDK7MhqGRY=
-golang.org/x/sys v0.0.0-20220111092808-5a964db01320/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9 h1:nhht2DYV/Sn3qOayu8lM+cU1ii9sTLUeBQwQQfUHtrs=
+golang.org/x/sys v0.0.0-20220227234510-4e6760a101f9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -574,12 +575,10 @@ golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4f
 golang.org/x/tools v0.0.0-20210106214847-113979e3529a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
 golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/tools v0.1.2/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
-golang.org/x/tools v0.1.5 h1:ouewzE6p+/VEB31YYnTbEJdi8pFqKp4P4n85vwo3DHA=
 golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk=
 golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
 golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
 google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M=
@@ -617,7 +616,7 @@ google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfG
 google.golang.org/genproto v0.0.0-20200513103714-09dca8ec2884/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c=
 google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
 google.golang.org/genproto v0.0.0-20210602131652-f16073e35f0c/go.mod h1:UODoCrxHCcBojKKwX1terBiRUaqAsFqJiF615XL43r0=
-google.golang.org/genproto v0.0.0-20220112215332-a9c7c0acf9f2/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220228195345-15d65a4533f7/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -629,9 +628,8 @@ google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8
 google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
-google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 google.golang.org/grpc v1.41.0/go.mod h1:U3l9uK9J0sini8mHphKoXyaqDA/8VyGnDee1zzIUK6k=
-google.golang.org/grpc v1.43.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.44.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -643,7 +641,6 @@ google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpAD
 google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
 google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
-google.golang.org/protobuf v1.27.1 h1:SnqbnDw1V7RiZcXPx5MEeqPv2s79L9i7BJUlG/+RurQ=
 google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw=
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/tools/goctl/goctl.go b/tools/goctl/goctl.go
index b685f6c4..cccf38bd 100644
--- a/tools/goctl/goctl.go
+++ b/tools/goctl/goctl.go
@@ -22,6 +22,7 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/bug"
 	"github.com/zeromicro/go-zero/tools/goctl/completion"
 	"github.com/zeromicro/go-zero/tools/goctl/docker"
+	"github.com/zeromicro/go-zero/tools/goctl/env"
 	"github.com/zeromicro/go-zero/tools/goctl/internal/errorx"
 	"github.com/zeromicro/go-zero/tools/goctl/internal/version"
 	"github.com/zeromicro/go-zero/tools/goctl/kube"
@@ -47,6 +48,34 @@ var commands = []cli.Command{
 		Usage:  "upgrade goctl to latest version",
 		Action: upgrade.Upgrade,
 	},
+	{
+		Name:  "env",
+		Usage: "check or edit goctl environment",
+		Flags: []cli.Flag{
+			cli.StringSliceFlag{
+				Name:  "write, w",
+				Usage: "edit goctl environment",
+			},
+		},
+		Subcommands: []cli.Command{
+			{
+				Name:  "check",
+				Usage: "detect goctl env and dependency tools",
+				Flags: []cli.Flag{
+					cli.BoolFlag{
+						Name:  "install, i",
+						Usage: "install dependencies if not found",
+					},
+					cli.BoolFlag{
+						Name:  "force, f",
+						Usage: "silent installation of non-existent dependencies",
+					},
+				},
+				Action: env.Check,
+			},
+		},
+		Action: env.Action,
+	},
 	{
 		Name:        "migrate",
 		Usage:       "migrate from tal-tech to zeromicro",
@@ -82,6 +111,10 @@ var commands = []cli.Command{
 					"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
 					"https://github.com/zeromicro/go-zero-template directory structure",
 			},
+			cli.StringFlag{
+				Name:  "branch",
+				Usage: "the branch of the remote repo, it does work with --remote",
+			},
 		},
 		Action: apigen.ApiCommand,
 		Subcommands: []cli.Command{
@@ -101,6 +134,10 @@ var commands = []cli.Command{
 							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
 							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
+					cli.StringFlag{
+						Name:  "branch",
+						Usage: "the branch of the remote repo, it does work with --remote",
+					},
 					cli.StringFlag{
 						Name:  "style",
 						Usage: "the file naming format, see [https://github.com/zeromicro/go-zero/blob/master/tools/goctl/config/readme.md]",
@@ -123,6 +160,10 @@ var commands = []cli.Command{
 						Name:  "stdin",
 						Usage: "use stdin to input api doc content, press \"ctrl + d\" to send EOF",
 					},
+					cli.BoolFlag{
+						Name:  "declare",
+						Usage: "use to skip check api types already declare",
+					},
 				},
 				Action: format.GoFormatApi,
 			},
@@ -180,6 +221,10 @@ var commands = []cli.Command{
 							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
 							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
+					cli.StringFlag{
+						Name:  "branch",
+						Usage: "the branch of the remote repo, it does work with --remote",
+					},
 				},
 				Action: gogen.GoCommand,
 			},
@@ -237,6 +282,14 @@ var commands = []cli.Command{
 						Name:  "api",
 						Usage: "the api file",
 					},
+					cli.BoolFlag{
+						Name:  "legacy",
+						Usage: "legacy generator for flutter v1",
+					},
+					cli.StringFlag{
+						Name:  "hostname",
+						Usage: "hostname of the server",
+					},
 				},
 				Action: dartgen.DartCommand,
 			},
@@ -292,6 +345,10 @@ var commands = []cli.Command{
 				Name:  "go",
 				Usage: "the file that contains main function",
 			},
+			cli.BoolFlag{
+				Name:  "scratch",
+				Usage: "use scratch for the base docker image",
+			},
 			cli.IntFlag{
 				Name:  "port",
 				Usage: "the port to expose, default none",
@@ -308,10 +365,19 @@ var commands = []cli.Command{
 					"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
 					"https://github.com/zeromicro/go-zero-template directory structure",
 			},
+			cli.StringFlag{
+				Name:  "branch",
+				Usage: "the branch of the remote repo, it does work with --remote",
+			},
 			cli.StringFlag{
 				Name:  "version",
 				Usage: "the goctl builder golang image version",
 			},
+			cli.StringFlag{
+				Name:  "tz",
+				Usage: "the timezone of the container",
+				Value: "Asia/Shanghai",
+			},
 		},
 		Action: docker.DockerCommand,
 	},
@@ -408,6 +474,10 @@ var commands = []cli.Command{
 							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
 							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
+					cli.StringFlag{
+						Name:  "branch",
+						Usage: "the branch of the remote repo, it does work with --remote",
+					},
 					cli.StringFlag{
 						Name:  "serviceAccount",
 						Usage: "the ServiceAccount for the deployment",
@@ -445,6 +515,10 @@ var commands = []cli.Command{
 							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
 							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
+					cli.StringFlag{
+						Name:  "branch",
+						Usage: "the branch of the remote repo, it does work with --remote",
+					},
 				},
 				Action: rpc.RPCNew,
 			},
@@ -467,6 +541,10 @@ var commands = []cli.Command{
 							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
 							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
+					cli.StringFlag{
+						Name:  "branch",
+						Usage: "the branch of the remote repo, it does work with --remote",
+					},
 				},
 				Action: rpc.RPCTemplate,
 			},
@@ -477,22 +555,30 @@ var commands = []cli.Command{
 				Description: "for details, see https://go-zero.dev/cn/goctl-rpc.html",
 				Action:      rpc.ZRPC,
 				Flags: []cli.Flag{
-					cli.StringFlag{
+					cli.StringSliceFlag{
 						Name:   "go_out",
 						Hidden: true,
 					},
-					cli.StringFlag{
+					cli.StringSliceFlag{
 						Name:   "go-grpc_out",
 						Hidden: true,
 					},
-					cli.StringFlag{
+					cli.StringSliceFlag{
 						Name:   "go_opt",
 						Hidden: true,
 					},
-					cli.StringFlag{
+					cli.StringSliceFlag{
 						Name:   "go-grpc_opt",
 						Hidden: true,
 					},
+					cli.StringSliceFlag{
+						Name:   "plugin",
+						Hidden: true,
+					},
+					cli.StringSliceFlag{
+						Name:   "proto_path,I",
+						Hidden: true,
+					},
 					cli.StringFlag{
 						Name:  "zrpc_out",
 						Usage: "the zrpc output directory",
@@ -511,6 +597,10 @@ var commands = []cli.Command{
 							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
 							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
+					cli.StringFlag{
+						Name:  "branch",
+						Usage: "the branch of the remote repo, it does work with --remote",
+					},
 				},
 			},
 			{
@@ -553,6 +643,10 @@ var commands = []cli.Command{
 							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
 							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
+					cli.StringFlag{
+						Name:  "branch",
+						Usage: "the branch of the remote repo, it does work with --remote",
+					},
 				},
 				Action: rpc.RPC,
 			},
@@ -605,6 +699,10 @@ var commands = []cli.Command{
 									"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
 									"https://github.com/zeromicro/go-zero-template directory structure",
 							},
+							cli.StringFlag{
+								Name:  "branch",
+								Usage: "the branch of the remote repo, it does work with --remote",
+							},
 						},
 						Action: model.MysqlDDL,
 					},
@@ -647,6 +745,10 @@ var commands = []cli.Command{
 									"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
 									"https://github.com/zeromicro/go-zero-template directory structure",
 							},
+							cli.StringFlag{
+								Name:  "branch",
+								Usage: "the branch of the remote repo, it does work with --remote",
+							},
 						},
 						Action: model.MySqlDataSource,
 					},
@@ -662,7 +764,7 @@ var commands = []cli.Command{
 						Flags: []cli.Flag{
 							cli.StringFlag{
 								Name:  "url",
-								Usage: `the data source of database,like "postgres://root:password@127.0.0.1:54332/database?sslmode=disable"`,
+								Usage: `the data source of database,like "postgres://root:password@127.0.0.1:5432/database?sslmode=disable"`,
 							},
 							cli.StringFlag{
 								Name:  "table, t",
@@ -699,6 +801,10 @@ var commands = []cli.Command{
 									"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
 									"https://github.com/zeromicro/go-zero-template directory structure",
 							},
+							cli.StringFlag{
+								Name:  "branch",
+								Usage: "the branch of the remote repo, it does work with --remote",
+							},
 						},
 						Action: model.PostgreSqlDataSource,
 					},
@@ -735,6 +841,10 @@ var commands = []cli.Command{
 							"if they are, --remote has higher priority\n\tThe git repo directory must be consistent with the " +
 							"https://github.com/zeromicro/go-zero-template directory structure",
 					},
+					cli.StringFlag{
+						Name:  "branch",
+						Usage: "the branch of the remote repo, it does work with --remote",
+					},
 				},
 				Action: mongo.Action,
 			},
@@ -829,7 +939,7 @@ func main() {
 	app.Version = fmt.Sprintf("%s %s/%s", version.BuildVersion, runtime.GOOS, runtime.GOARCH)
 	app.Commands = commands
 
-	// cli already print error messages
+	// cli already print error messages.
 	if err := app.Run(os.Args); err != nil {
 		fmt.Println(aurora.Red(errorx.Wrap(err).Error()))
 		os.Exit(codeFailure)
diff --git a/tools/goctl/internal/errorx/errorx.go b/tools/goctl/internal/errorx/errorx.go
index c513b814..5e667904 100644
--- a/tools/goctl/internal/errorx/errorx.go
+++ b/tools/goctl/internal/errorx/errorx.go
@@ -2,14 +2,14 @@ package errorx
 
 import (
 	"fmt"
-	"runtime"
 	"strings"
 
-	"github.com/zeromicro/go-zero/tools/goctl/internal/version"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/env"
 )
 
-var errorFormat = `goctl: generation error: %+v
-goctl version: %s
+var errorFormat = `goctl error: %+v
+goctl env:
+%s
 %s`
 
 // GoctlError represents a goctl error.
@@ -20,8 +20,7 @@ type GoctlError struct {
 
 func (e *GoctlError) Error() string {
 	detail := wrapMessage(e.message...)
-	v := fmt.Sprintf("%s %s/%s", version.BuildVersion, runtime.GOOS, runtime.GOARCH)
-	return fmt.Sprintf(errorFormat, e.err, v, detail)
+	return fmt.Sprintf(errorFormat, e.err, env.Print(), detail)
 }
 
 // Wrap wraps an error with goctl version and message.
diff --git a/tools/goctl/internal/version/version.go b/tools/goctl/internal/version/version.go
index e8b6cc77..02b4aae5 100644
--- a/tools/goctl/internal/version/version.go
+++ b/tools/goctl/internal/version/version.go
@@ -6,7 +6,7 @@ import (
 )
 
 // BuildVersion is the version of goctl.
-const BuildVersion = "1.3.2"
+const BuildVersion = "1.3.3"
 
 var tag = map[string]int{"pre-alpha": 0, "alpha": 1, "pre-bata": 2, "beta": 3, "released": 4, "": 5}
 
diff --git a/tools/goctl/kube/kube.go b/tools/goctl/kube/kube.go
index adf97147..79575bff 100644
--- a/tools/goctl/kube/kube.go
+++ b/tools/goctl/kube/kube.go
@@ -44,8 +44,9 @@ func DeploymentCommand(c *cli.Context) error {
 	nodePort := c.Int("nodePort")
 	home := c.String("home")
 	remote := c.String("remote")
+	branch := c.String("branch")
 	if len(remote) > 0 {
-		repo, _ := util.CloneIntoGitHome(remote)
+		repo, _ := util.CloneIntoGitHome(remote, branch)
 		if len(repo) > 0 {
 			home = repo
 		}
diff --git a/tools/goctl/migrate/proxy.go b/tools/goctl/migrate/proxy.go
index b5218940..610f2bab 100644
--- a/tools/goctl/migrate/proxy.go
+++ b/tools/goctl/migrate/proxy.go
@@ -9,8 +9,10 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/rpc/execx"
 )
 
-var defaultProxy = "https://goproxy.cn"
-var defaultProxies = []string{defaultProxy}
+var (
+	defaultProxy   = "https://goproxy.cn"
+	defaultProxies = []string{defaultProxy}
+)
 
 func goProxy() []string {
 	wd, err := os.Getwd()
diff --git a/tools/goctl/model/mongo/mongo.go b/tools/goctl/model/mongo/mongo.go
index 6d807476..861c804f 100644
--- a/tools/goctl/model/mongo/mongo.go
+++ b/tools/goctl/model/mongo/mongo.go
@@ -20,8 +20,9 @@ func Action(ctx *cli.Context) error {
 	s := ctx.String("style")
 	home := ctx.String("home")
 	remote := ctx.String("remote")
+	branch := ctx.String("branch")
 	if len(remote) > 0 {
-		repo, _ := file.CloneIntoGitHome(remote)
+		repo, _ := file.CloneIntoGitHome(remote, branch)
 		if len(repo) > 0 {
 			home = repo
 		}
diff --git a/tools/goctl/model/sql/command/command.go b/tools/goctl/model/sql/command/command.go
index f313072e..6e484c6d 100644
--- a/tools/goctl/model/sql/command/command.go
+++ b/tools/goctl/model/sql/command/command.go
@@ -30,6 +30,8 @@ const (
 	flagDatabase = "database"
 	flagSchema   = "schema"
 	flagHome     = "home"
+	flagRemote   = "remote"
+	flagBranch   = "branch"
 )
 
 var errNotMatched = errors.New("sql not matched")
@@ -43,9 +45,10 @@ func MysqlDDL(ctx *cli.Context) error {
 	style := ctx.String(flagStyle)
 	database := ctx.String(flagDatabase)
 	home := ctx.String(flagHome)
-	remote := ctx.String("remote")
+	remote := ctx.String(flagRemote)
+	branch := ctx.String(flagBranch)
 	if len(remote) > 0 {
-		repo, _ := file.CloneIntoGitHome(remote)
+		repo, _ := file.CloneIntoGitHome(remote, branch)
 		if len(repo) > 0 {
 			home = repo
 		}
@@ -68,10 +71,11 @@ func MySqlDataSource(ctx *cli.Context) error {
 	cache := ctx.Bool(flagCache)
 	idea := ctx.Bool(flagIdea)
 	style := ctx.String(flagStyle)
-	home := ctx.String("home")
-	remote := ctx.String("remote")
+	home := ctx.String(flagHome)
+	remote := ctx.String(flagRemote)
+	branch := ctx.String(flagBranch)
 	if len(remote) > 0 {
-		repo, _ := file.CloneIntoGitHome(remote)
+		repo, _ := file.CloneIntoGitHome(remote, branch)
 		if len(repo) > 0 {
 			home = repo
 		}
@@ -97,10 +101,11 @@ func PostgreSqlDataSource(ctx *cli.Context) error {
 	idea := ctx.Bool(flagIdea)
 	style := ctx.String(flagStyle)
 	schema := ctx.String(flagSchema)
-	home := ctx.String("home")
-	remote := ctx.String("remote")
+	home := ctx.String(flagHome)
+	remote := ctx.String(flagRemote)
+	branch := ctx.String(flagBranch)
 	if len(remote) > 0 {
-		repo, _ := file.CloneIntoGitHome(remote)
+		repo, _ := file.CloneIntoGitHome(remote, branch)
 		if len(repo) > 0 {
 			home = repo
 		}
diff --git a/tools/goctl/model/sql/model/informationschemamodel.go b/tools/goctl/model/sql/model/infoschemamodel.go
similarity index 100%
rename from tools/goctl/model/sql/model/informationschemamodel.go
rename to tools/goctl/model/sql/model/infoschemamodel.go
diff --git a/tools/goctl/model/sql/model/postgresqlmodel.go b/tools/goctl/model/sql/model/postgresqlmodel.go
index ad20ee49..95b0c7cf 100644
--- a/tools/goctl/model/sql/model/postgresqlmodel.go
+++ b/tools/goctl/model/sql/model/postgresqlmodel.go
@@ -108,6 +108,7 @@ func (m *PostgreSqlModel) getColumns(schema, table string, in []*PostgreColumn)
 	if err != nil {
 		return nil, err
 	}
+
 	var list []*Column
 	for _, e := range in {
 		var dft interface{}
@@ -120,9 +121,15 @@ func (m *PostgreSqlModel) getColumns(schema, table string, in []*PostgreColumn)
 			isNullAble = "NO"
 		}
 
-		extra := "auto_increment"
-		if e.IdentityIncrement.Int32 != 1 {
-			extra = ""
+		var extra string
+		// when identity is true, the column is auto increment
+		if e.IdentityIncrement.Int32 == 1 {
+			extra = "auto_increment"
+		}
+
+		// when type is serial, it's auto_increment. and the default value is tablename_columnname_seq
+		if strings.Contains(e.ColumnDefault.String, table+"_"+e.Field.String+"_seq") {
+			extra = "auto_increment"
 		}
 
 		if len(index[e.Field.String]) > 0 {
@@ -172,6 +179,7 @@ func (m *PostgreSqlModel) getIndex(schema, table string) (map[string][]*DbIndex,
 	if err != nil {
 		return nil, err
 	}
+
 	index := make(map[string][]*DbIndex)
 	for _, e := range indexes {
 		if e.IsPrimary.Bool {
@@ -193,6 +201,7 @@ func (m *PostgreSqlModel) getIndex(schema, table string) (map[string][]*DbIndex,
 			SeqInIndex: int(e.IndexSort.Int32),
 		})
 	}
+
 	return index, nil
 }
 
diff --git a/tools/goctl/model/sql/template/delete.go b/tools/goctl/model/sql/template/delete.go
index b6dac7d8..373e6225 100644
--- a/tools/goctl/model/sql/template/delete.go
+++ b/tools/goctl/model/sql/template/delete.go
@@ -2,21 +2,21 @@ package template
 
 // Delete defines a delete template
 var Delete = `
-func (m *default{{.upperStartCamelObject}}Model) Delete({{.lowerStartCamelPrimaryKey}} {{.dataType}}) error {
-	{{if .withCache}}{{if .containsIndexCache}}data, err:=m.FindOne({{.lowerStartCamelPrimaryKey}})
+func (m *default{{.upperStartCamelObject}}Model) Delete(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) error {
+	{{if .withCache}}{{if .containsIndexCache}}data, err:=m.FindOneCtx(ctx, {{.lowerStartCamelPrimaryKey}})
 	if err!=nil{
 		return err
-	}{{end}}
+	}
 
-	{{.keys}}
-    _, err {{if .containsIndexCache}}={{else}}:={{end}} m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
+{{end}}	{{.keys}}
+    _, err {{if .containsIndexCache}}={{else}}:={{end}} m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
 		query := fmt.Sprintf("delete from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table)
-		return conn.Exec(query, {{.lowerStartCamelPrimaryKey}})
+		return conn.ExecCtx(ctx, query, {{.lowerStartCamelPrimaryKey}})
 	}, {{.keyValues}}){{else}}query := fmt.Sprintf("delete from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table)
-		_,err:=m.conn.Exec(query, {{.lowerStartCamelPrimaryKey}}){{end}}
+		_,err:=m.conn.ExecCtx(ctx, query, {{.lowerStartCamelPrimaryKey}}){{end}}
 	return err
 }
 `
 
 // DeleteMethod defines a delete template for interface method
-var DeleteMethod = `Delete({{.lowerStartCamelPrimaryKey}} {{.dataType}}) error`
+var DeleteMethod = `Delete(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) error`
diff --git a/tools/goctl/model/sql/template/find.go b/tools/goctl/model/sql/template/find.go
index c1962d65..b358d518 100644
--- a/tools/goctl/model/sql/template/find.go
+++ b/tools/goctl/model/sql/template/find.go
@@ -2,12 +2,12 @@ package template
 
 // FindOne defines find row by id.
 var FindOne = `
-func (m *default{{.upperStartCamelObject}}Model) FindOne({{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error) {
+func (m *default{{.upperStartCamelObject}}Model) FindOne(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error) {
 	{{if .withCache}}{{.cacheKey}}
 	var resp {{.upperStartCamelObject}}
-	err := m.QueryRow(&resp, {{.cacheKeyVariable}}, func(conn sqlx.SqlConn, v interface{}) error {
+	err := m.QueryRowCtx(ctx, &resp, {{.cacheKeyVariable}}, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) error {
 		query :=  fmt.Sprintf("select %s from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}} limit 1", {{.lowerStartCamelObject}}Rows, m.table)
-		return conn.QueryRow(v, query, {{.lowerStartCamelPrimaryKey}})
+		return conn.QueryRowCtx(ctx, v, query, {{.lowerStartCamelPrimaryKey}})
 	})
 	switch err {
 	case nil:
@@ -18,7 +18,7 @@ func (m *default{{.upperStartCamelObject}}Model) FindOne({{.lowerStartCamelPrima
 		return nil, err
 	}{{else}}query := fmt.Sprintf("select %s from %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}} limit 1", {{.lowerStartCamelObject}}Rows, m.table)
 	var resp {{.upperStartCamelObject}}
-	err := m.conn.QueryRow(&resp, query, {{.lowerStartCamelPrimaryKey}})
+	err := m.conn.QueryRowCtx(ctx, &resp, query, {{.lowerStartCamelPrimaryKey}})
 	switch err {
 	case nil:
 		return &resp, nil
@@ -35,9 +35,9 @@ var FindOneByField = `
 func (m *default{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}}) (*{{.upperStartCamelObject}}, error) {
 	{{if .withCache}}{{.cacheKey}}
 	var resp {{.upperStartCamelObject}}
-	err := m.QueryRowIndex(&resp, {{.cacheKeyVariable}}, m.formatPrimary, func(conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
+	err := m.QueryRowIndexCtx(ctx, &resp, {{.cacheKeyVariable}}, m.formatPrimary, func(ctx context.Context, conn sqlx.SqlConn, v interface{}) (i interface{}, e error) {
 		query := fmt.Sprintf("select %s from %s where {{.originalField}} limit 1", {{.lowerStartCamelObject}}Rows, m.table)
-		if err := conn.QueryRow(&resp, query, {{.lowerStartCamelField}}); err != nil {
+		if err := conn.QueryRowCtx(ctx, &resp, query, {{.lowerStartCamelField}}); err != nil {
 			return nil, err
 		}
 		return resp.{{.upperStartCamelPrimaryKey}}, nil
@@ -52,7 +52,7 @@ func (m *default{{.upperStartCamelObject}}Model) FindOneBy{{.upperField}}({{.in}
 	}
 }{{else}}var resp {{.upperStartCamelObject}}
 	query := fmt.Sprintf("select %s from %s where {{.originalField}} limit 1", {{.lowerStartCamelObject}}Rows, m.table )
-	err := m.conn.QueryRow(&resp, query, {{.lowerStartCamelField}})
+	err := m.conn.QueryRowCtx(ctx, &resp, query, {{.lowerStartCamelField}})
 	switch err {
 	case nil:
 		return &resp, nil
@@ -70,14 +70,14 @@ func (m *default{{.upperStartCamelObject}}Model) formatPrimary(primary interface
 	return fmt.Sprintf("%s%v", {{.primaryKeyLeft}}, primary)
 }
 
-func (m *default{{.upperStartCamelObject}}Model) queryPrimary(conn sqlx.SqlConn, v, primary interface{}) error {
+func (m *default{{.upperStartCamelObject}}Model) queryPrimary(ctx context.Context, conn sqlx.SqlConn, v, primary interface{}) error {
 	query := fmt.Sprintf("select %s from %s where {{.originalPrimaryField}} = {{if .postgreSql}}$1{{else}}?{{end}} limit 1", {{.lowerStartCamelObject}}Rows, m.table )
-	return conn.QueryRow(v, query, primary)
+	return conn.QueryRowCtx(ctx, v, query, primary)
 }
 `
 
 // FindOneMethod defines find row method.
-var FindOneMethod = `FindOne({{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error)`
+var FindOneMethod = `FindOne(ctx context.Context, {{.lowerStartCamelPrimaryKey}} {{.dataType}}) (*{{.upperStartCamelObject}}, error)`
 
 // FindOneByFieldMethod defines find row by field method.
-var FindOneByFieldMethod = `FindOneBy{{.upperField}}({{.in}}) (*{{.upperStartCamelObject}}, error) `
+var FindOneByFieldMethod = `FindOneBy{{.upperField}}(ctx context.Context, {{.in}}) (*{{.upperStartCamelObject}}, error) `
diff --git a/tools/goctl/model/sql/template/import.go b/tools/goctl/model/sql/template/import.go
index 8d90c1f2..a52e3be1 100644
--- a/tools/goctl/model/sql/template/import.go
+++ b/tools/goctl/model/sql/template/import.go
@@ -3,6 +3,7 @@ package template
 var (
 	// Imports defines a import template for model in cache case
 	Imports = `import (
+	"context"
 	"database/sql"
 	"fmt"
 	"strings"
@@ -17,6 +18,7 @@ var (
 `
 	// ImportsNoCache defines a import template for model in normal case
 	ImportsNoCache = `import (
+	"context"
 	"database/sql"
 	"fmt"
 	"strings"
diff --git a/tools/goctl/model/sql/template/insert.go b/tools/goctl/model/sql/template/insert.go
index 62a48c49..115ea7ff 100644
--- a/tools/goctl/model/sql/template/insert.go
+++ b/tools/goctl/model/sql/template/insert.go
@@ -2,18 +2,18 @@ package template
 
 // Insert defines a template for insert code in model
 var Insert = `
-func (m *default{{.upperStartCamelObject}}Model) Insert(data *{{.upperStartCamelObject}}) (sql.Result,error) {
+func (m *default{{.upperStartCamelObject}}Model) Insert(ctx context.Context, data *{{.upperStartCamelObject}}) (sql.Result,error) {
 	{{if .withCache}}{{if .containsIndexCache}}{{.keys}}
-    ret, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
+    ret, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
 		query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
-		return conn.Exec(query, {{.expressionValues}})
+		return conn.ExecCtx(ctx, query, {{.expressionValues}})
 	}, {{.keyValues}}){{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
-    ret,err:=m.ExecNoCache(query, {{.expressionValues}})
+    ret,err:=m.ExecNoCacheCtx(ctx, query, {{.expressionValues}})
 	{{end}}{{else}}query := fmt.Sprintf("insert into %s (%s) values ({{.expression}})", m.table, {{.lowerStartCamelObject}}RowsExpectAutoSet)
-    ret,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
+    ret,err:=m.conn.ExecCtx(ctx, query, {{.expressionValues}}){{end}}
 	return ret,err
 }
 `
 
 // InsertMethod defines an interface method template for insert code in model
-var InsertMethod = `Insert(data *{{.upperStartCamelObject}}) (sql.Result,error)`
+var InsertMethod = `Insert(ctx context.Context, data *{{.upperStartCamelObject}}) (sql.Result,error)`
diff --git a/tools/goctl/model/sql/template/update.go b/tools/goctl/model/sql/template/update.go
index b4792d56..62edaee9 100644
--- a/tools/goctl/model/sql/template/update.go
+++ b/tools/goctl/model/sql/template/update.go
@@ -2,16 +2,16 @@ package template
 
 // Update defines a template for generating update codes
 var Update = `
-func (m *default{{.upperStartCamelObject}}Model) Update(data *{{.upperStartCamelObject}}) error {
+func (m *default{{.upperStartCamelObject}}Model) Update(ctx context.Context, data *{{.upperStartCamelObject}}) error {
 	{{if .withCache}}{{.keys}}
-    _, err := m.Exec(func(conn sqlx.SqlConn) (result sql.Result, err error) {
+    _, err := m.ExecCtx(ctx, func(ctx context.Context, conn sqlx.SqlConn) (result sql.Result, err error) {
 		query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder)
-		return conn.Exec(query, {{.expressionValues}})
+		return conn.ExecCtx(ctx, query, {{.expressionValues}})
 	}, {{.keyValues}}){{else}}query := fmt.Sprintf("update %s set %s where {{.originalPrimaryKey}} = {{if .postgreSql}}$1{{else}}?{{end}}", m.table, {{.lowerStartCamelObject}}RowsWithPlaceHolder)
-    _,err:=m.conn.Exec(query, {{.expressionValues}}){{end}}
+    _,err:=m.conn.ExecCtx(ctx, query, {{.expressionValues}}){{end}}
 	return err
 }
 `
 
 // UpdateMethod defines an interface method template for generating update codes
-var UpdateMethod = `Update(data *{{.upperStartCamelObject}}) error`
+var UpdateMethod = `Update(ctx context.Context, data *{{.upperStartCamelObject}}) error`
diff --git a/tools/goctl/pkg/collection/sortedmap.go b/tools/goctl/pkg/collection/sortedmap.go
new file mode 100644
index 00000000..456170a5
--- /dev/null
+++ b/tools/goctl/pkg/collection/sortedmap.go
@@ -0,0 +1,210 @@
+package sortedmap
+
+import (
+	"container/list"
+	"errors"
+	"fmt"
+	"strings"
+
+	"github.com/zeromicro/go-zero/tools/goctl/util/stringx"
+)
+
+var (
+	ErrInvalidKVExpression = errors.New(`invalid key-value expression`)
+	ErrInvalidKVS          = errors.New("the length of kv must be a even number")
+)
+
+type KV []interface{}
+
+type SortedMap struct {
+	kv   *list.List
+	keys map[interface{}]*list.Element
+}
+
+func New() *SortedMap {
+	return &SortedMap{
+		kv:   list.New(),
+		keys: make(map[interface{}]*list.Element),
+	}
+}
+
+func (m *SortedMap) SetExpression(expression string) (key interface{}, value interface{}, err error) {
+	idx := strings.Index(expression, "=")
+	if idx == -1 {
+		return "", "", ErrInvalidKVExpression
+	}
+	key = expression[:idx]
+	if len(expression) == idx {
+		value = ""
+	} else {
+		value = expression[idx+1:]
+	}
+	if keys, ok := key.(string); ok && stringx.ContainsWhiteSpace(keys) {
+		return "", "", ErrInvalidKVExpression
+	}
+	if values, ok := value.(string); ok && stringx.ContainsWhiteSpace(values) {
+		return "", "", ErrInvalidKVExpression
+	}
+	if len(key.(string)) == 0 {
+		return "", "", ErrInvalidKVExpression
+	}
+
+	m.SetKV(key, value)
+	return
+}
+
+func (m *SortedMap) SetKV(key, value interface{}) {
+	e, ok := m.keys[key]
+	if !ok {
+		e = m.kv.PushBack(KV{
+			key, value,
+		})
+	} else {
+		e.Value.(KV)[1] = value
+	}
+	m.keys[key] = e
+}
+
+func (m *SortedMap) Set(kv KV) error {
+	if len(kv) == 0 {
+		return nil
+	}
+	if len(kv)%2 != 0 {
+		return ErrInvalidKVS
+	}
+	for idx := 0; idx < len(kv); idx += 2 {
+		m.SetKV(kv[idx], kv[idx+1])
+	}
+	return nil
+}
+
+func (m *SortedMap) Get(key interface{}) (interface{}, bool) {
+	e, ok := m.keys[key]
+	if !ok {
+		return nil, false
+	}
+	return e.Value.(KV)[1], true
+}
+
+func (m *SortedMap) GetOr(key interface{}, dft interface{}) interface{} {
+	e, ok := m.keys[key]
+	if !ok {
+		return dft
+	}
+	return e.Value.(KV)[1]
+}
+
+func (m *SortedMap) GetString(key interface{}) (string, bool) {
+	value, ok := m.Get(key)
+	if !ok {
+		return "", false
+	}
+	vs, ok := value.(string)
+	return vs, ok
+}
+
+func (m *SortedMap) GetStringOr(key interface{}, dft string) string {
+	value, ok := m.GetString(key)
+	if !ok {
+		return dft
+	}
+	return value
+}
+
+func (m *SortedMap) HasKey(key interface{}) bool {
+	_, ok := m.keys[key]
+	return ok
+}
+
+func (m *SortedMap) HasValue(value interface{}) bool {
+	var contains bool
+	m.RangeIf(func(key, v interface{}) bool {
+		if value == v {
+			contains = true
+			return false
+		}
+		return true
+	})
+	return contains
+}
+
+func (m *SortedMap) Keys() []interface{} {
+	keys := make([]interface{}, 0)
+	next := m.kv.Front()
+	for next != nil {
+		keys = append(keys, next.Value.(KV)[0])
+		next = next.Next()
+	}
+	return keys
+}
+
+func (m *SortedMap) Values() []interface{} {
+	keys := m.Keys()
+	values := make([]interface{}, len(keys))
+	for idx, key := range keys {
+		values[idx] = m.keys[key].Value.(KV)[1]
+	}
+	return values
+}
+
+func (m *SortedMap) Range(iterator func(key, value interface{})) {
+	next := m.kv.Front()
+	for next != nil {
+		value := next.Value.(KV)
+		iterator(value[0], value[1])
+		next = next.Next()
+	}
+}
+
+func (m *SortedMap) RangeIf(iterator func(key, value interface{}) bool) {
+	next := m.kv.Front()
+	for next != nil {
+		value := next.Value.(KV)
+		loop := iterator(value[0], value[1])
+		if !loop {
+			return
+		}
+		next = next.Next()
+	}
+}
+
+func (m *SortedMap) Remove(key interface{}) (value interface{}, ok bool) {
+	v, ok := m.keys[key]
+	if !ok {
+		return nil, false
+	}
+	value = v.Value.(KV)[1]
+	ok = true
+	m.kv.Remove(v)
+	delete(m.keys, key)
+	return
+}
+
+func (m *SortedMap) Insert(sm *SortedMap) {
+	sm.Range(func(key, value interface{}) {
+		m.SetKV(key, value)
+	})
+}
+
+func (m *SortedMap) Copy() *SortedMap {
+	sm := New()
+	m.Range(func(key, value interface{}) {
+		sm.SetKV(key, value)
+	})
+	return sm
+}
+
+func (m *SortedMap) Format() []string {
+	format := make([]string, 0)
+	m.Range(func(key, value interface{}) {
+		format = append(format, fmt.Sprintf("%s=%s", key, value))
+	})
+	return format
+}
+
+func (m *SortedMap) Reset() {
+	m.kv.Init()
+	for key := range m.keys {
+		delete(m.keys, key)
+	}
+}
diff --git a/tools/goctl/pkg/downloader/downloader.go b/tools/goctl/pkg/downloader/downloader.go
new file mode 100644
index 00000000..f17a9a20
--- /dev/null
+++ b/tools/goctl/pkg/downloader/downloader.go
@@ -0,0 +1,23 @@
+package downloader
+
+import (
+	"io"
+	"net/http"
+	"os"
+)
+
+func Download(url string, filename string) error {
+	resp, err := http.Get(url)
+	if err != nil {
+		return err
+	}
+	defer resp.Body.Close()
+
+	f, err := os.Create(filename)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+	_, err = io.Copy(f, resp.Body)
+	return err
+}
diff --git a/tools/goctl/pkg/env/env.go b/tools/goctl/pkg/env/env.go
new file mode 100644
index 00000000..0e97e9e6
--- /dev/null
+++ b/tools/goctl/pkg/env/env.go
@@ -0,0 +1,147 @@
+package env
+
+import (
+	"fmt"
+	"io/ioutil"
+	"log"
+	"path/filepath"
+	"runtime"
+	"strings"
+
+	"github.com/zeromicro/go-zero/tools/goctl/internal/version"
+	sortedmap "github.com/zeromicro/go-zero/tools/goctl/pkg/collection"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/protoc"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/protocgengo"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/protocgengogrpc"
+	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
+)
+
+var goctlEnv *sortedmap.SortedMap
+
+const (
+	GoctlOS                = "GOCTL_OS"
+	GoctlArch              = "GOCTL_ARCH"
+	GoctlHome              = "GOCTL_HOME"
+	GoctlDebug             = "GOCTL_DEBUG"
+	GoctlCache             = "GOCTL_CACHE"
+	GoctlVersion           = "GOCTL_VERSION"
+	ProtocVersion          = "PROTOC_VERSION"
+	ProtocGenGoVersion     = "PROTOC_GEN_GO_VERSION"
+	ProtocGenGoGRPCVersion = "PROTO_GEN_GO_GRPC_VERSION"
+
+	envFileDir = "env"
+)
+
+// init initializes the goctl environment variables, the environment variables of the function are set in order,
+// please do not change the logic order of the code.
+func init() {
+	defaultGoctlHome, err := pathx.GetDefaultGoctlHome()
+	if err != nil {
+		log.Fatalln(err)
+	}
+	goctlEnv = sortedmap.New()
+	goctlEnv.SetKV(GoctlOS, runtime.GOOS)
+	goctlEnv.SetKV(GoctlArch, runtime.GOARCH)
+	existsEnv := readEnv(defaultGoctlHome)
+	if existsEnv != nil {
+		goctlHome, ok := existsEnv.GetString(GoctlHome)
+		if ok && len(goctlHome) > 0 {
+			goctlEnv.SetKV(GoctlHome, goctlHome)
+		}
+		if debug := existsEnv.GetOr(GoctlDebug, "").(string); debug != "" {
+			if strings.EqualFold(debug, "true") || strings.EqualFold(debug, "false") {
+				goctlEnv.SetKV(GoctlDebug, debug)
+			}
+		}
+		if value := existsEnv.GetStringOr(GoctlCache, ""); value != "" {
+			goctlEnv.SetKV(GoctlCache, value)
+		}
+	}
+	if !goctlEnv.HasKey(GoctlHome) {
+		goctlEnv.SetKV(GoctlHome, defaultGoctlHome)
+	}
+	if !goctlEnv.HasKey(GoctlDebug) {
+		goctlEnv.SetKV(GoctlDebug, "False")
+	}
+
+	if !goctlEnv.HasKey(GoctlCache) {
+		cacheDir, _ := pathx.GetCacheDir()
+		goctlEnv.SetKV(GoctlCache, cacheDir)
+	}
+
+	goctlEnv.SetKV(GoctlVersion, version.BuildVersion)
+	protocVer, _ := protoc.Version()
+	goctlEnv.SetKV(ProtocVersion, protocVer)
+
+	protocGenGoVer, _ := protocgengo.Version()
+	goctlEnv.SetKV(ProtocGenGoVersion, protocGenGoVer)
+
+	protocGenGoGrpcVer, _ := protocgengogrpc.Version()
+	goctlEnv.SetKV(ProtocGenGoGRPCVersion, protocGenGoGrpcVer)
+}
+
+func Print() string {
+	return strings.Join(goctlEnv.Format(), "\n")
+}
+
+func Get(key string) string {
+	return GetOr(key, "")
+}
+
+func GetOr(key string, def string) string {
+	return goctlEnv.GetStringOr(key, def)
+}
+
+func readEnv(goctlHome string) *sortedmap.SortedMap {
+	envFile := filepath.Join(goctlHome, envFileDir)
+	data, err := ioutil.ReadFile(envFile)
+	if err != nil {
+		return nil
+	}
+	dataStr := string(data)
+	lines := strings.Split(dataStr, "\n")
+	sm := sortedmap.New()
+	for _, line := range lines {
+		_, _, err = sm.SetExpression(line)
+		if err != nil {
+			continue
+		}
+	}
+	return sm
+}
+
+func WriteEnv(kv []string) error {
+	defaultGoctlHome, err := pathx.GetDefaultGoctlHome()
+	if err != nil {
+		log.Fatalln(err)
+	}
+	data := sortedmap.New()
+	for _, e := range kv {
+		_, _, err := data.SetExpression(e)
+		if err != nil {
+			return err
+		}
+	}
+	data.RangeIf(func(key, value interface{}) bool {
+		switch key.(string) {
+		case GoctlHome, GoctlCache:
+			path := value.(string)
+			if !pathx.FileExists(path) {
+				err = fmt.Errorf("[writeEnv]: path %q is not exists", path)
+				return false
+			}
+		}
+		if goctlEnv.HasKey(key) {
+			goctlEnv.SetKV(key, value)
+			return true
+		} else {
+			err = fmt.Errorf("[writeEnv]: invalid key: %v", key)
+			return false
+		}
+	})
+	if err != nil {
+		return err
+	}
+	envFile := filepath.Join(defaultGoctlHome, envFileDir)
+	return ioutil.WriteFile(envFile, []byte(strings.Join(goctlEnv.Format(), "\n")), 0o777)
+}
diff --git a/tools/goctl/pkg/goctl/goctl.go b/tools/goctl/pkg/goctl/goctl.go
new file mode 100644
index 00000000..7729648a
--- /dev/null
+++ b/tools/goctl/pkg/goctl/goctl.go
@@ -0,0 +1,41 @@
+package goctl
+
+import (
+	"path/filepath"
+	"runtime"
+
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/golang"
+	"github.com/zeromicro/go-zero/tools/goctl/util/console"
+	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
+	"github.com/zeromicro/go-zero/tools/goctl/vars"
+)
+
+func Install(cacheDir, name string, installFn func(dest string) (string, error)) (string, error) {
+	goBin := golang.GoBin()
+	cacheFile := filepath.Join(cacheDir, name)
+	binFile := filepath.Join(goBin, name)
+
+	goos := runtime.GOOS
+	if goos == vars.OsWindows {
+		cacheFile = cacheFile + ".exe"
+		binFile = binFile + ".exe"
+	}
+	// read cache.
+	err := pathx.Copy(cacheFile, binFile)
+	if err == nil {
+		console.Info("%q installed from cache", name)
+		return binFile, nil
+	}
+
+	binFile, err = installFn(binFile)
+	if err != nil {
+		return "", err
+	}
+
+	// write cache.
+	err = pathx.Copy(binFile, cacheFile)
+	if err != nil {
+		console.Warning("write cache error: %+v", err)
+	}
+	return binFile, nil
+}
diff --git a/tools/goctl/pkg/golang/bin.go b/tools/goctl/pkg/golang/bin.go
new file mode 100644
index 00000000..672f3b97
--- /dev/null
+++ b/tools/goctl/pkg/golang/bin.go
@@ -0,0 +1,27 @@
+package golang
+
+import (
+	"go/build"
+	"os"
+	"path/filepath"
+
+	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
+)
+
+// GoBin returns a path of GOBIN.
+func GoBin() string {
+	def := build.Default
+	goroot := os.Getenv("GOPATH")
+	bin := filepath.Join(goroot, "bin")
+	if !pathx.FileExists(bin) {
+		gopath := os.Getenv("GOROOT")
+		bin = filepath.Join(gopath, "bin")
+	}
+	if !pathx.FileExists(bin) {
+		bin = os.Getenv("GOBIN")
+	}
+	if !pathx.FileExists(bin) {
+		bin = filepath.Join(def.GOPATH, "bin")
+	}
+	return bin
+}
diff --git a/tools/goctl/pkg/golang/install.go b/tools/goctl/pkg/golang/install.go
new file mode 100644
index 00000000..465b2e0f
--- /dev/null
+++ b/tools/goctl/pkg/golang/install.go
@@ -0,0 +1,17 @@
+package golang
+
+import (
+	"os"
+	"os/exec"
+)
+
+func Install(git string) error {
+	cmd := exec.Command("go", "install", git)
+	env := os.Environ()
+	env = append(env, "GO111MODULE=on", "GOPROXY=https://goproxy.cn,direct")
+	cmd.Env = env
+	cmd.Stdout = os.Stdout
+	cmd.Stderr = os.Stderr
+	err := cmd.Run()
+	return err
+}
diff --git a/tools/goctl/pkg/protoc/protoc.go b/tools/goctl/pkg/protoc/protoc.go
new file mode 100644
index 00000000..0db64de9
--- /dev/null
+++ b/tools/goctl/pkg/protoc/protoc.go
@@ -0,0 +1,79 @@
+package protoc
+
+import (
+	"archive/zip"
+	"fmt"
+	"os"
+	"path/filepath"
+	"runtime"
+	"strings"
+
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/downloader"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/goctl"
+	"github.com/zeromicro/go-zero/tools/goctl/rpc/execx"
+	"github.com/zeromicro/go-zero/tools/goctl/util/env"
+	"github.com/zeromicro/go-zero/tools/goctl/util/zipx"
+	"github.com/zeromicro/go-zero/tools/goctl/vars"
+)
+
+var url = map[string]string{
+	"linux_32":   "https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_32.zip",
+	"linux_64":   "https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-linux-x86_64.zip",
+	"darwin":     "https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-osx-x86_64.zip",
+	"windows_32": "https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-win32.zip",
+	"windows_64": "https://github.com/protocolbuffers/protobuf/releases/download/v3.19.4/protoc-3.19.4-win64.zip",
+}
+
+const (
+	Name        = "protoc"
+	ZipFileName = Name + ".zip"
+)
+
+func Install(cacheDir string) (string, error) {
+	return goctl.Install(cacheDir, Name, func(dest string) (string, error) {
+		goos := runtime.GOOS
+		tempFile := filepath.Join(os.TempDir(), ZipFileName)
+		bit := 32 << (^uint(0) >> 63)
+		var downloadUrl string
+		switch goos {
+		case vars.OsMac:
+			downloadUrl = url[vars.OsMac]
+		case vars.OsWindows:
+			downloadUrl = url[fmt.Sprintf("%s_%d", vars.OsWindows, bit)]
+		case vars.OsLinux:
+			downloadUrl = url[fmt.Sprintf("%s_%d", vars.OsLinux, bit)]
+		default:
+			return "", fmt.Errorf("unsupport OS: %q", goos)
+		}
+
+		err := downloader.Download(downloadUrl, tempFile)
+		if err != nil {
+			return "", err
+		}
+
+		return dest, zipx.Unpacking(tempFile, filepath.Dir(dest), func(f *zip.File) bool {
+			return filepath.Base(f.Name) == filepath.Base(dest)
+		})
+	})
+}
+
+func Exists() bool {
+	_, err := env.LookUpProtoc()
+	return err == nil
+}
+
+func Version() (string, error) {
+	path, err := env.LookUpProtoc()
+	if err != nil {
+		return "", err
+	}
+	version, err := execx.Run(path+" --version", "")
+	if err != nil {
+		return "", err
+	}
+	fields := strings.Fields(version)
+	if len(fields) > 1 {
+		return fields[1], nil
+	}
+	return "", nil
+}
diff --git a/tools/goctl/pkg/protocgengo/protocgengo.go b/tools/goctl/pkg/protocgengo/protocgengo.go
new file mode 100644
index 00000000..eaf1a67a
--- /dev/null
+++ b/tools/goctl/pkg/protocgengo/protocgengo.go
@@ -0,0 +1,56 @@
+package protocgengo
+
+import (
+	"strings"
+	"time"
+
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/goctl"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/golang"
+	"github.com/zeromicro/go-zero/tools/goctl/rpc/execx"
+	"github.com/zeromicro/go-zero/tools/goctl/util/env"
+)
+
+const (
+	Name = "protoc-gen-go"
+	url  = "google.golang.org/protobuf/cmd/protoc-gen-go@latest"
+)
+
+func Install(cacheDir string) (string, error) {
+	return goctl.Install(cacheDir, Name, func(dest string) (string, error) {
+		err := golang.Install(url)
+		return dest, err
+	})
+}
+
+func Exists() bool {
+	ver, err := Version()
+	if err != nil {
+		return false
+	}
+	return len(ver) > 0
+}
+
+// Version is used to get the version of the protoc-gen-go plugin. For older versions, protoc-gen-go does not support
+// version fetching, so if protoc-gen-go --version is executed, it will cause the process to block, so it is controlled
+// by a timer to prevent the older version process from blocking.
+func Version() (string, error) {
+	path, err := env.LookUpProtocGenGo()
+	if err != nil {
+		return "", err
+	}
+	versionC := make(chan string)
+	go func(c chan string) {
+		version, _ := execx.Run(path+" --version", "")
+		fields := strings.Fields(version)
+		if len(fields) > 1 {
+			c <- fields[1]
+		}
+	}(versionC)
+	t := time.NewTimer(time.Second)
+	select {
+	case <-t.C:
+		return "", nil
+	case version := <-versionC:
+		return version, nil
+	}
+}
diff --git a/tools/goctl/pkg/protocgengogrpc/protocgengogrpc.go b/tools/goctl/pkg/protocgengogrpc/protocgengogrpc.go
new file mode 100644
index 00000000..62b0f499
--- /dev/null
+++ b/tools/goctl/pkg/protocgengogrpc/protocgengogrpc.go
@@ -0,0 +1,44 @@
+package protocgengogrpc
+
+import (
+	"strings"
+
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/goctl"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/golang"
+	"github.com/zeromicro/go-zero/tools/goctl/rpc/execx"
+	"github.com/zeromicro/go-zero/tools/goctl/util/env"
+)
+
+const (
+	Name = "protoc-gen-go-grpc"
+	url  = "google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest"
+)
+
+func Install(cacheDir string) (string, error) {
+	return goctl.Install(cacheDir, Name, func(dest string) (string, error) {
+		err := golang.Install(url)
+		return dest, err
+	})
+}
+
+func Exists() bool {
+	_, err := env.LookUpProtocGenGoGrpc()
+	return err == nil
+}
+
+// Version is used to get the version of the protoc-gen-go-grpc plugin.
+func Version() (string, error) {
+	path, err := env.LookUpProtocGenGoGrpc()
+	if err != nil {
+		return "", err
+	}
+	version, err := execx.Run(path+" --version", "")
+	if err != nil {
+		return "", err
+	}
+	fields := strings.Fields(version)
+	if len(fields) > 1 {
+		return fields[1], nil
+	}
+	return "", nil
+}
diff --git a/tools/goctl/rpc/cli/cli.go b/tools/goctl/rpc/cli/cli.go
index a139056d..0229ff6a 100644
--- a/tools/goctl/rpc/cli/cli.go
+++ b/tools/goctl/rpc/cli/cli.go
@@ -33,8 +33,9 @@ func RPC(c *cli.Context) error {
 	goOptions := c.StringSlice("go_opt")
 	home := c.String("home")
 	remote := c.String("remote")
+	branch := c.String("branch")
 	if len(remote) > 0 {
-		repo, _ := util.CloneIntoGitHome(remote)
+		repo, _ := util.CloneIntoGitHome(remote, branch)
 		if len(repo) > 0 {
 			home = repo
 		}
@@ -89,8 +90,9 @@ func RPCNew(c *cli.Context) error {
 	style := c.String("style")
 	home := c.String("home")
 	remote := c.String("remote")
+	branch := c.String("branch")
 	if len(remote) > 0 {
-		repo, _ := util.CloneIntoGitHome(remote)
+		repo, _ := util.CloneIntoGitHome(remote, branch)
 		if len(repo) > 0 {
 			home = repo
 		}
@@ -124,8 +126,9 @@ func RPCTemplate(c *cli.Context) error {
 	protoFile := c.String("o")
 	home := c.String("home")
 	remote := c.String("remote")
+	branch := c.String("branch")
 	if len(remote) > 0 {
-		repo, _ := util.CloneIntoGitHome(remote)
+		repo, _ := util.CloneIntoGitHome(remote, branch)
 		if len(repo) > 0 {
 			home = repo
 		}
diff --git a/tools/goctl/rpc/cli/zrpc.go b/tools/goctl/rpc/cli/zrpc.go
index 37b6e3de..361bf0d3 100644
--- a/tools/goctl/rpc/cli/zrpc.go
+++ b/tools/goctl/rpc/cli/zrpc.go
@@ -2,12 +2,10 @@ package cli
 
 import (
 	"errors"
-	"fmt"
 	"os"
 	"path/filepath"
 	"strings"
 
-	"github.com/emicklei/proto"
 	"github.com/urfave/cli"
 	"github.com/zeromicro/go-zero/tools/goctl/rpc/generator"
 	"github.com/zeromicro/go-zero/tools/goctl/util"
@@ -15,7 +13,8 @@ import (
 )
 
 var (
-	errInvalidGrpcOutput = errors.New("ZRPC: missing grpc output")
+	errInvalidGrpcOutput = errors.New("ZRPC: missing --go-grpc_out")
+	errInvalidGoOutput   = errors.New("ZRPC: missing --go_out")
 	errInvalidZrpcOutput = errors.New("ZRPC: missing zrpc output, please use --zrpc_out to specify the output")
 	errInvalidInput      = errors.New("ZRPC: missing source")
 	errMultiInput        = errors.New("ZRPC: only one source is expected")
@@ -40,62 +39,44 @@ func ZRPC(c *cli.Context) error {
 	if err != nil {
 		return err
 	}
-	src := filepath.Dir(source)
-	goPackage, protoPkg, err := getGoPackage(source)
-	if err != nil {
-		return err
-	}
 
-	grpcOut := c.String("go-grpc_out")
-	goOut := c.String("go_out")
-	goOpt := c.String("go_opt")
-	grpcOpt := c.String("go-grpc_opt")
+	grpcOutList := c.StringSlice("go-grpc_out")
+	goOutList := c.StringSlice("go_out")
 	zrpcOut := c.String("zrpc_out")
 	style := c.String("style")
 	home := c.String("home")
 	remote := c.String("remote")
-	if len(remote) > 0 {
-		repo, _ := util.CloneIntoGitHome(remote)
-		if len(repo) > 0 {
-			home = repo
-		}
+	branch := c.String("branch")
+	if len(grpcOutList) == 0 {
+		return errInvalidGrpcOutput
 	}
-
-	if len(home) > 0 {
-		pathx.RegisterGoctlHome(home)
+	if len(goOutList) == 0 {
+		return errInvalidGoOutput
 	}
+	goOut := goOutList[len(goOutList)-1]
+	grpcOut := grpcOutList[len(grpcOutList)-1]
 	if len(goOut) == 0 {
 		return errInvalidGrpcOutput
 	}
 	if len(zrpcOut) == 0 {
 		return errInvalidZrpcOutput
 	}
-	if !filepath.IsAbs(zrpcOut) {
-		zrpcOut = filepath.Join(pwd, zrpcOut)
-	}
 
-	goOut = removePluginFlag(goOut)
-	goOut, err = parseOut(src, goOut, goOpt, goPackage)
-	if err != nil {
-		return err
+	if len(remote) > 0 {
+		repo, _ := util.CloneIntoGitHome(remote, branch)
+		if len(repo) > 0 {
+			home = repo
+		}
 	}
 
-	var isGooglePlugin = len(grpcOut) > 0
-	// If grpcOut is not empty means that user generates grpc code by
-	// https://google.golang.org/protobuf/cmd/protoc-gen-go and
-	// https://google.golang.org/grpc/cmd/protoc-gen-go-grpc,
-	// for details please see https://grpc.io/docs/languages/go/quickstart/
-	if isGooglePlugin {
-		grpcOut, err = parseOut(src, grpcOut, grpcOpt, goPackage)
-		if err != nil {
-			return err
-		}
-	} else {
-		// Else it means that user generates grpc code by
-		// https://github.com/golang/protobuf/tree/master/protoc-gen-go
-		grpcOut = goOut
+	if len(home) > 0 {
+		pathx.RegisterGoctlHome(home)
+	}
+	if !filepath.IsAbs(zrpcOut) {
+		zrpcOut = filepath.Join(pwd, zrpcOut)
 	}
 
+	isGooglePlugin := len(grpcOut) > 0
 	goOut, err = filepath.Abs(goOut)
 	if err != nil {
 		return err
@@ -109,23 +90,11 @@ func ZRPC(c *cli.Context) error {
 		return err
 	}
 
-	if isGooglePlugin && grpcOut != goOut {
-		return fmt.Errorf("the --go_out and --go-grpc_out must be the same")
-	}
-
-	if goOut == zrpcOut || grpcOut == zrpcOut {
-		recommendName := goPackage
-		if len(recommendName) == 0 {
-			recommendName = protoPkg
-		}
-		return fmt.Errorf("the zrpc and grpc output can not be the same, it is recommended to output grpc to the %q",
-			filepath.Join(goOut, recommendName))
-	}
-
 	var ctx generator.ZRpcContext
 	ctx.Src = source
-	ctx.ProtoGenGoDir = goOut
-	ctx.ProtoGenGrpcDir = grpcOut
+	ctx.GoOutput = goOut
+	ctx.GrpcOutput = grpcOut
+	ctx.IsGooglePlugin = isGooglePlugin
 	ctx.Output = zrpcOut
 	ctx.ProtocCmd = strings.Join(protocArgs, " ")
 	g, err := generator.NewDefaultRPCGenerator(style, generator.WithZRpcContext(&ctx))
@@ -136,52 +105,6 @@ func ZRPC(c *cli.Context) error {
 	return g.Generate(source, zrpcOut, nil)
 }
 
-// parseOut calculates the output place to grpc code, about to calculate logic for details
-// please see https://developers.google.com/protocol-buffers/docs/reference/go-generated#invocation.
-func parseOut(sourceDir, grpcOut, grpcOpt, goPackage string) (string, error) {
-	if !filepath.IsAbs(grpcOut) {
-		grpcOut = filepath.Join(sourceDir, grpcOut)
-	}
-	switch grpcOpt {
-	case "", optImport:
-		grpcOut = filepath.Join(grpcOut, goPackage)
-	case optSourceRelative:
-		grpcOut = filepath.Join(grpcOut)
-	default:
-		return "", fmt.Errorf("parseAndSetGrpcOut:  unknown path type %q: want %q or %q",
-			grpcOpt, optImport, optSourceRelative)
-	}
-
-	return grpcOut, nil
-}
-
-func getGoPackage(source string) (string, string, error) {
-	r, err := os.Open(source)
-	if err != nil {
-		return "", "", err
-	}
-	defer func() {
-		_ = r.Close()
-	}()
-
-	parser := proto.NewParser(r)
-	set, err := parser.Parse()
-	if err != nil {
-		return "", "", err
-	}
-
-	var goPackage, protoPkg string
-	proto.Walk(set, proto.WithOption(func(option *proto.Option) {
-		if option.Name == "go_package" {
-			goPackage = option.Constant.Source
-		}
-	}), proto.WithPackage(func(p *proto.Package) {
-		protoPkg = p.Name
-	}))
-
-	return goPackage, protoPkg, nil
-}
-
 func removeGoctlFlag(args []string) []string {
 	var ret []string
 	var step int
diff --git a/tools/goctl/rpc/generator/defaultgenerator.go b/tools/goctl/rpc/generator/defaultgenerator.go
index c9979369..8d20ddfa 100644
--- a/tools/goctl/rpc/generator/defaultgenerator.go
+++ b/tools/goctl/rpc/generator/defaultgenerator.go
@@ -1,8 +1,7 @@
 package generator
 
 import (
-	"os/exec"
-
+	"github.com/zeromicro/go-zero/tools/goctl/env"
 	"github.com/zeromicro/go-zero/tools/goctl/util/console"
 )
 
@@ -25,17 +24,5 @@ func NewDefaultGenerator() Generator {
 // Prepare provides environment detection generated by rpc service,
 // including go environment, protoc, whether protoc-gen-go is installed or not
 func (g *DefaultGenerator) Prepare() error {
-	_, err := exec.LookPath("go")
-	if err != nil {
-		return err
-	}
-
-	_, err = exec.LookPath("protoc")
-	if err != nil {
-		return err
-	}
-
-	_, err = exec.LookPath("protoc-gen-go")
-
-	return err
+	return env.Prepare(true, true)
 }
diff --git a/tools/goctl/rpc/generator/gen.go b/tools/goctl/rpc/generator/gen.go
index acb05967..2d6a4683 100644
--- a/tools/goctl/rpc/generator/gen.go
+++ b/tools/goctl/rpc/generator/gen.go
@@ -24,6 +24,9 @@ type ZRpcContext struct {
 	ProtocCmd       string
 	ProtoGenGrpcDir string
 	ProtoGenGoDir   string
+	IsGooglePlugin  bool
+	GoOutput        string
+	GrpcOutput      string
 	Output          string
 }
 
diff --git a/tools/goctl/rpc/generator/genmain.go b/tools/goctl/rpc/generator/genmain.go
index eafbe515..d7e59ade 100644
--- a/tools/goctl/rpc/generator/genmain.go
+++ b/tools/goctl/rpc/generator/genmain.go
@@ -36,10 +36,10 @@ func main() {
 	var c config.Config
 	conf.MustLoad(*configFile, &c)
 	ctx := svc.NewServiceContext(c)
-	srv := server.New{{.serviceNew}}Server(ctx)
+	svr := server.New{{.serviceNew}}Server(ctx)
 
 	s := zrpc.MustNewServer(c.RpcServerConf, func(grpcServer *grpc.Server) {
-		{{.pkg}}.Register{{.service}}Server(grpcServer, srv)
+		{{.pkg}}.Register{{.service}}Server(grpcServer, svr)
 
 		if c.Mode == service.DevMode || c.Mode == service.TestMode {
 			reflection.Register(grpcServer)
diff --git a/tools/goctl/rpc/generator/genpb.go b/tools/goctl/rpc/generator/genpb.go
index 7c58ef11..8e09b538 100644
--- a/tools/goctl/rpc/generator/genpb.go
+++ b/tools/goctl/rpc/generator/genpb.go
@@ -3,6 +3,8 @@ package generator
 import (
 	"bytes"
 	"errors"
+	"fmt"
+	"io/fs"
 	"os"
 	"path/filepath"
 	"strings"
@@ -19,7 +21,7 @@ const googleProtocGenGoErr = `--go_out: protoc-gen-go: plugins are not supported
 // but the commands and flags in protoc are not completely joined in goctl. At present, proto_path(-I) is introduced
 func (g *DefaultGenerator) GenPb(ctx DirContext, protoImportPath []string, proto parser.Proto, _ *conf.Config, c *ZRpcContext, goOptions ...string) error {
 	if c != nil {
-		return g.genPbDirect(c)
+		return g.genPbDirect(ctx, c)
 	}
 
 	// deprecated: use genPbDirect instead.
@@ -110,13 +112,74 @@ go get -u github.com/golang/protobuf/protoc-gen-go`)
 	return nil
 }
 
-func (g *DefaultGenerator) genPbDirect(c *ZRpcContext) error {
-	g.log.Debug(c.ProtocCmd)
+func (g *DefaultGenerator) genPbDirect(ctx DirContext, c *ZRpcContext) error {
+	g.log.Debug("[command]: %s", c.ProtocCmd)
 	pwd, err := os.Getwd()
 	if err != nil {
 		return err
 	}
 
 	_, err = execx.Run(c.ProtocCmd, pwd)
-	return err
+	if err != nil {
+		return err
+	}
+	return g.setPbDir(ctx, c)
+}
+
+func (g *DefaultGenerator) setPbDir(ctx DirContext, c *ZRpcContext) error {
+	pbDir, err := findPbFile(c.GoOutput, false)
+	if err != nil {
+		return err
+	}
+	if len(pbDir) == 0 {
+		return fmt.Errorf("pg.go is not found under %q", c.GoOutput)
+	}
+	grpcDir, err := findPbFile(c.GrpcOutput, true)
+	if err != nil {
+		return err
+	}
+	if len(grpcDir) == 0 {
+		return fmt.Errorf("_grpc.pb.go is not found in %q", c.GrpcOutput)
+	}
+	if pbDir != grpcDir {
+		return fmt.Errorf("the pb.go and _grpc.pb.go must under the same dir: "+
+			"\n pb.go: %s\n_grpc.pb.go: %s", pbDir, grpcDir)
+	}
+	if pbDir == c.Output {
+		return fmt.Errorf("the output of pb.go and _grpc.pb.go must not be the same "+
+			"with --zrpc_out:\npb output: %s\nzrpc out: %s", pbDir, c.Output)
+	}
+	ctx.SetPbDir(pbDir, grpcDir)
+	return nil
+}
+
+const (
+	pbSuffix   = "pb.go"
+	grpcSuffix = "_grpc.pb.go"
+)
+
+func findPbFile(current string, grpc bool) (string, error) {
+	fileSystem := os.DirFS(current)
+	var ret string
+	err := fs.WalkDir(fileSystem, ".", func(path string, d fs.DirEntry, err error) error {
+		if d.IsDir() {
+			return nil
+		}
+		if strings.HasSuffix(path, pbSuffix) {
+			if grpc {
+				if strings.HasSuffix(path, grpcSuffix) {
+					ret = path
+					return os.ErrExist
+				}
+			} else if !strings.HasSuffix(path, grpcSuffix) {
+				ret = path
+				return os.ErrExist
+			}
+		}
+		return nil
+	})
+	if err == os.ErrExist {
+		return filepath.Dir(filepath.Join(current, ret)), nil
+	}
+	return "", err
 }
diff --git a/tools/goctl/rpc/generator/mkdir.go b/tools/goctl/rpc/generator/mkdir.go
index 12ffbfbc..b5ea23dd 100644
--- a/tools/goctl/rpc/generator/mkdir.go
+++ b/tools/goctl/rpc/generator/mkdir.go
@@ -38,6 +38,7 @@ type (
 		GetProtoGo() Dir
 		GetMain() Dir
 		GetServiceName() stringx.String
+		SetPbDir(pbDir, grpcDir string)
 	}
 
 	// Dir defines a directory
@@ -50,6 +51,7 @@ type (
 	defaultDirContext struct {
 		inner       map[string]Dir
 		serviceName stringx.String
+		ctx         *ctx.ProjectContext
 	}
 )
 
@@ -134,11 +136,26 @@ func mkdir(ctx *ctx.ProjectContext, proto parser.Proto, _ *conf.Config, c *ZRpcC
 	}
 	serviceName := strings.TrimSuffix(proto.Name, filepath.Ext(proto.Name))
 	return &defaultDirContext{
+		ctx:         ctx,
 		inner:       inner,
 		serviceName: stringx.From(strings.ReplaceAll(serviceName, "-", "")),
 	}, nil
 }
 
+func (d *defaultDirContext) SetPbDir(pbDir, grpcDir string) {
+	d.inner[pb] = Dir{
+		Filename: pbDir,
+		Package:  filepath.ToSlash(filepath.Join(d.ctx.Path, strings.TrimPrefix(pbDir, d.ctx.Dir))),
+		Base:     filepath.Base(pbDir),
+	}
+
+	d.inner[protoGo] = Dir{
+		Filename: grpcDir,
+		Package:  filepath.ToSlash(filepath.Join(d.ctx.Path, strings.TrimPrefix(grpcDir, d.ctx.Dir))),
+		Base:     filepath.Base(grpcDir),
+	}
+}
+
 func (d *defaultDirContext) GetCall() Dir {
 	return d.inner[call]
 }
diff --git a/tools/goctl/util/env/env.go b/tools/goctl/util/env/env.go
index 8938dc34..93c95314 100644
--- a/tools/goctl/util/env/env.go
+++ b/tools/goctl/util/env/env.go
@@ -11,10 +11,11 @@ import (
 )
 
 const (
-	bin            = "bin"
-	binGo          = "go"
-	binProtoc      = "protoc"
-	binProtocGenGo = "protoc-gen-go"
+	bin                = "bin"
+	binGo              = "go"
+	binProtoc          = "protoc"
+	binProtocGenGo     = "protoc-gen-go"
+	binProtocGenGrpcGo = "protoc-gen-go-grpc"
 )
 
 // LookUpGo searches an executable go in the directories
@@ -46,6 +47,14 @@ func LookUpProtocGenGo() (string, error) {
 	return LookPath(xProtocGenGo)
 }
 
+// LookUpProtocGenGoGrpc searches an executable protoc-gen-go-grpc in the directories
+// named by the PATH environment variable.
+func LookUpProtocGenGoGrpc() (string, error) {
+	suffix := getExeSuffix()
+	xProtocGenGoGrpc := binProtocGenGrpcGo + suffix
+	return LookPath(xProtocGenGoGrpc)
+}
+
 // LookPath searches for an executable named file in the
 // directories named by the PATH environment variable,
 // for the os windows, the named file will be spliced with the
diff --git a/tools/goctl/util/git.go b/tools/goctl/util/git.go
index 0db1f5e6..5eed43e5 100644
--- a/tools/goctl/util/git.go
+++ b/tools/goctl/util/git.go
@@ -12,7 +12,7 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
 
-func CloneIntoGitHome(url string) (dir string, err error) {
+func CloneIntoGitHome(url string, branch string) (dir string, err error) {
 	gitHome, err := pathx.GetGitHome()
 	if err != nil {
 		return "", err
@@ -21,6 +21,9 @@ func CloneIntoGitHome(url string) (dir string, err error) {
 	ext := filepath.Ext(url)
 	repo := strings.TrimSuffix(filepath.Base(url), ext)
 	dir = filepath.Join(gitHome, repo)
+	if pathx.FileExists(dir) {
+		os.RemoveAll(dir)
+	}
 	path, err := env.LookPath("git")
 	if err != nil {
 		return "", err
@@ -28,7 +31,12 @@ func CloneIntoGitHome(url string) (dir string, err error) {
 	if !env.CanExec() {
 		return "", fmt.Errorf("os %q can not call 'exec' command", runtime.GOOS)
 	}
-	cmd := exec.Command(path, "clone", url, dir)
+	args := []string{"clone"}
+	if len(branch) > 0 {
+		args = append(args, "-b", branch)
+	}
+	args = append(args, url, dir)
+	cmd := exec.Command(path, args...)
 	cmd.Env = os.Environ()
 	cmd.Stdout = os.Stdout
 	cmd.Stderr = os.Stderr
diff --git a/tools/goctl/util/pathx/file.go b/tools/goctl/util/pathx/file.go
index 0608acab..5858ec51 100644
--- a/tools/goctl/util/pathx/file.go
+++ b/tools/goctl/util/pathx/file.go
@@ -3,6 +3,7 @@ package pathx
 import (
 	"bufio"
 	"fmt"
+	"io"
 	"io/ioutil"
 	"log"
 	"os"
@@ -13,22 +14,23 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/internal/version"
 )
 
-// NL defines a new line
+// NL defines a new line.
 const (
 	NL              = "\n"
 	goctlDir        = ".goctl"
 	gitDir          = ".git"
 	autoCompleteDir = ".auto_complete"
+	cacheDir        = "cache"
 )
 
 var goctlHome string
 
-// RegisterGoctlHome register goctl home path
+// RegisterGoctlHome register goctl home path.
 func RegisterGoctlHome(home string) {
 	goctlHome = home
 }
 
-// CreateIfNotExist creates a file if it is not exists
+// CreateIfNotExist creates a file if it is not exists.
 func CreateIfNotExist(file string) (*os.File, error) {
 	_, err := os.Stat(file)
 	if !os.IsNotExist(err) {
@@ -38,7 +40,7 @@ func CreateIfNotExist(file string) (*os.File, error) {
 	return os.Create(file)
 }
 
-// RemoveIfExist deletes the specified file if it is exists
+// RemoveIfExist deletes the specified file if it is exists.
 func RemoveIfExist(filename string) error {
 	if !FileExists(filename) {
 		return nil
@@ -47,7 +49,7 @@ func RemoveIfExist(filename string) error {
 	return os.Remove(filename)
 }
 
-// RemoveOrQuit deletes the specified file if read a permit command from stdin
+// RemoveOrQuit deletes the specified file if read a permit command from stdin.
 func RemoveOrQuit(filename string) error {
 	if !FileExists(filename) {
 		return nil
@@ -60,23 +62,40 @@ func RemoveOrQuit(filename string) error {
 	return os.Remove(filename)
 }
 
-// FileExists returns true if the specified file is exists
+// FileExists returns true if the specified file is exists.
 func FileExists(file string) bool {
 	_, err := os.Stat(file)
 	return err == nil
 }
 
-// FileNameWithoutExt returns a file name without suffix
+// FileNameWithoutExt returns a file name without suffix.
 func FileNameWithoutExt(file string) string {
 	return strings.TrimSuffix(file, filepath.Ext(file))
 }
 
-// GetGoctlHome returns the path value of the goctl home where Join $HOME with .goctl
-func GetGoctlHome() (string, error) {
+// GetGoctlHome returns the path value of the goctl, the default path is ~/.goctl, if the path has
+// been set by calling the RegisterGoctlHome method, the user-defined path refers to.
+func GetGoctlHome() (home string, err error) {
+	defer func() {
+		if err != nil {
+			return
+		}
+		info, err := os.Stat(home)
+		if err == nil && !info.IsDir() {
+			os.Rename(home, home+".old")
+			MkdirIfNotExist(home)
+		}
+	}()
 	if len(goctlHome) != 0 {
-		return goctlHome, nil
+		home = goctlHome
+		return
 	}
+	home, err = GetDefaultGoctlHome()
+	return
+}
 
+// GetDefaultGoctlHome returns the path value of the goctl home where Join $HOME with .goctl.
+func GetDefaultGoctlHome() (string, error) {
 	home, err := os.UserHomeDir()
 	if err != nil {
 		return "", err
@@ -104,7 +123,17 @@ func GetAutoCompleteHome() (string, error) {
 	return filepath.Join(goctlH, autoCompleteDir), nil
 }
 
-// GetTemplateDir returns the category path value in GoctlHome where could get it by GetGoctlHome
+// GetCacheDir returns the cache dit of goctl.
+func GetCacheDir() (string, error) {
+	goctlH, err := GetGoctlHome()
+	if err != nil {
+		return "", err
+	}
+
+	return filepath.Join(goctlH, cacheDir), nil
+}
+
+// GetTemplateDir returns the category path value in GoctlHome where could get it by GetGoctlHome.
 func GetTemplateDir(category string) (string, error) {
 	home, err := GetGoctlHome()
 	if err != nil {
@@ -112,7 +141,7 @@ func GetTemplateDir(category string) (string, error) {
 	}
 	if home == goctlHome {
 		// backward compatible, it will be removed in the feature
-		// backward compatible start
+		// backward compatible start.
 		beforeTemplateDir := filepath.Join(home, version.GetGoctlVersion(), category)
 		fs, _ := ioutil.ReadDir(beforeTemplateDir)
 		var hasContent bool
@@ -124,7 +153,7 @@ func GetTemplateDir(category string) (string, error) {
 		if hasContent {
 			return beforeTemplateDir, nil
 		}
-		// backward compatible end
+		// backward compatible end.
 
 		return filepath.Join(home, category), nil
 	}
@@ -132,7 +161,7 @@ func GetTemplateDir(category string) (string, error) {
 	return filepath.Join(home, version.GetGoctlVersion(), category), nil
 }
 
-// InitTemplates creates template files GoctlHome where could get it by GetGoctlHome
+// InitTemplates creates template files GoctlHome where could get it by GetGoctlHome.
 func InitTemplates(category string, templates map[string]string) error {
 	dir, err := GetTemplateDir(category)
 	if err != nil {
@@ -152,7 +181,7 @@ func InitTemplates(category string, templates map[string]string) error {
 	return nil
 }
 
-// CreateTemplate writes template into file even it is exists
+// CreateTemplate writes template into file even it is exists.
 func CreateTemplate(category, name, content string) error {
 	dir, err := GetTemplateDir(category)
 	if err != nil {
@@ -161,7 +190,7 @@ func CreateTemplate(category, name, content string) error {
 	return createTemplate(filepath.Join(dir, name), content, true)
 }
 
-// Clean deletes all templates and removes the parent directory
+// Clean deletes all templates and removes the parent directory.
 func Clean(category string) error {
 	dir, err := GetTemplateDir(category)
 	if err != nil {
@@ -170,7 +199,7 @@ func Clean(category string) error {
 	return os.RemoveAll(dir)
 }
 
-// LoadTemplate gets template content by the specified file
+// LoadTemplate gets template content by the specified file.
 func LoadTemplate(category, file, builtin string) (string, error) {
 	dir, err := GetTemplateDir(category)
 	if err != nil {
@@ -223,7 +252,7 @@ func createTemplate(file, content string, force bool) error {
 	return err
 }
 
-// MustTempDir creates a temporary directory
+// MustTempDir creates a temporary directory.
 func MustTempDir() string {
 	dir, err := ioutil.TempDir("", "")
 	if err != nil {
@@ -232,3 +261,25 @@ func MustTempDir() string {
 
 	return dir
 }
+
+func Copy(src, dest string) error {
+	f, err := os.Open(src)
+	if err != nil {
+		return err
+	}
+	defer f.Close()
+
+	dir := filepath.Dir(dest)
+	err = MkdirIfNotExist(dir)
+	if err != nil {
+		return err
+	}
+	w, err := os.Create(dest)
+	if err != nil {
+		return err
+	}
+	w.Chmod(os.ModePerm)
+	defer w.Close()
+	_, err = io.Copy(w, f)
+	return err
+}
diff --git a/tools/goctl/util/stringx/string.go b/tools/goctl/util/stringx/string.go
index ae148c2f..5f3f63cc 100644
--- a/tools/goctl/util/stringx/string.go
+++ b/tools/goctl/util/stringx/string.go
@@ -6,6 +6,8 @@ import (
 	"unicode"
 )
 
+var WhiteSpace = []rune{'\n', '\t', '\f', '\v', ' '}
+
 // String  provides for converting the source text into other spell case,like lower,snake,camel
 type String struct {
 	source string
@@ -114,3 +116,24 @@ func (s String) splitBy(fn func(r rune) bool, remove bool) []string {
 	}
 	return list
 }
+
+func ContainsAny(s string, runes ...rune) bool {
+	if len(runes) == 0 {
+		return true
+	}
+	tmp := make(map[rune]struct{}, len(runes))
+	for _, r := range runes {
+		tmp[r] = struct{}{}
+	}
+
+	for _, r := range s {
+		if _, ok := tmp[r]; ok {
+			return true
+		}
+	}
+	return false
+}
+
+func ContainsWhiteSpace(s string) bool {
+	return ContainsAny(s, WhiteSpace...)
+}
diff --git a/tools/goctl/util/zipx/zipx.go b/tools/goctl/util/zipx/zipx.go
new file mode 100644
index 00000000..2b1619dd
--- /dev/null
+++ b/tools/goctl/util/zipx/zipx.go
@@ -0,0 +1,51 @@
+package zipx
+
+import (
+	"archive/zip"
+	"io"
+	"os"
+	"path/filepath"
+
+	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
+)
+
+func Unpacking(name, destPath string, mapper func(f *zip.File) bool) error {
+	r, err := zip.OpenReader(name)
+	if err != nil {
+		return err
+	}
+	defer r.Close()
+
+	for _, file := range r.File {
+		ok := mapper(file)
+		if ok {
+			err = fileCopy(file, destPath)
+			if err != nil {
+				return err
+			}
+		}
+	}
+	return nil
+}
+
+func fileCopy(file *zip.File, destPath string) error {
+	rc, err := file.Open()
+	if err != nil {
+		return err
+	}
+	defer rc.Close()
+	filename := filepath.Join(destPath, filepath.Base(file.Name))
+	dir := filepath.Dir(filename)
+	err = pathx.MkdirIfNotExist(dir)
+	if err != nil {
+		return err
+	}
+
+	w, err := os.Create(filename)
+	if err != nil {
+		return err
+	}
+	defer w.Close()
+	_, err = io.Copy(w, rc)
+	return err
+}
diff --git a/zrpc/internal/serverinterceptors/authinterceptor.go b/zrpc/internal/serverinterceptors/authinterceptor.go
index 76c99f7e..3032ee0c 100644
--- a/zrpc/internal/serverinterceptors/authinterceptor.go
+++ b/zrpc/internal/serverinterceptors/authinterceptor.go
@@ -9,13 +9,13 @@ import (
 
 // StreamAuthorizeInterceptor returns a func that uses given authenticator in processing stream requests.
 func StreamAuthorizeInterceptor(authenticator *auth.Authenticator) grpc.StreamServerInterceptor {
-	return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo,
+	return func(svr interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo,
 		handler grpc.StreamHandler) error {
 		if err := authenticator.Authenticate(stream.Context()); err != nil {
 			return err
 		}
 
-		return handler(srv, stream)
+		return handler(svr, stream)
 	}
 }
 
diff --git a/zrpc/internal/serverinterceptors/breakerinterceptor.go b/zrpc/internal/serverinterceptors/breakerinterceptor.go
index 233bdeed..8e0f60b1 100644
--- a/zrpc/internal/serverinterceptors/breakerinterceptor.go
+++ b/zrpc/internal/serverinterceptors/breakerinterceptor.go
@@ -9,11 +9,11 @@ import (
 )
 
 // StreamBreakerInterceptor is an interceptor that acts as a circuit breaker.
-func StreamBreakerInterceptor(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo,
+func StreamBreakerInterceptor(svr interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo,
 	handler grpc.StreamHandler) (err error) {
 	breakerName := info.FullMethod
 	return breaker.DoWithAcceptable(breakerName, func() error {
-		return handler(srv, stream)
+		return handler(svr, stream)
 	}, codes.Acceptable)
 }
 
diff --git a/zrpc/internal/serverinterceptors/crashinterceptor.go b/zrpc/internal/serverinterceptors/crashinterceptor.go
index 391b49e3..2d45a3b1 100644
--- a/zrpc/internal/serverinterceptors/crashinterceptor.go
+++ b/zrpc/internal/serverinterceptors/crashinterceptor.go
@@ -11,17 +11,17 @@ import (
 )
 
 // StreamCrashInterceptor catches panics in processing stream requests and recovers.
-func StreamCrashInterceptor(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo,
+func StreamCrashInterceptor(svr interface{}, stream grpc.ServerStream, _ *grpc.StreamServerInfo,
 	handler grpc.StreamHandler) (err error) {
 	defer handleCrash(func(r interface{}) {
 		err = toPanicError(r)
 	})
 
-	return handler(srv, stream)
+	return handler(svr, stream)
 }
 
 // UnaryCrashInterceptor catches panics in processing unary requests and recovers.
-func UnaryCrashInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo,
+func UnaryCrashInterceptor(ctx context.Context, req interface{}, _ *grpc.UnaryServerInfo,
 	handler grpc.UnaryHandler) (resp interface{}, err error) {
 	defer handleCrash(func(r interface{}) {
 		err = toPanicError(r)
diff --git a/zrpc/internal/serverinterceptors/tracinginterceptor.go b/zrpc/internal/serverinterceptors/tracinginterceptor.go
index 5fb6f87b..1b2821de 100644
--- a/zrpc/internal/serverinterceptors/tracinginterceptor.go
+++ b/zrpc/internal/serverinterceptors/tracinginterceptor.go
@@ -41,12 +41,12 @@ func UnaryTracingInterceptor(ctx context.Context, req interface{}, info *grpc.Un
 }
 
 // StreamTracingInterceptor returns a grpc.StreamServerInterceptor for opentelemetry.
-func StreamTracingInterceptor(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo,
+func StreamTracingInterceptor(svr interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo,
 	handler grpc.StreamHandler) error {
 	ctx, span := startSpan(ss.Context(), info.FullMethod)
 	defer span.End()
 
-	if err := handler(srv, wrapServerStream(ctx, ss)); err != nil {
+	if err := handler(svr, wrapServerStream(ctx, ss)); err != nil {
 		s, ok := status.FromError(err)
 		if ok {
 			span.SetStatus(codes.Error, s.Message())
diff --git a/zrpc/proxy.go b/zrpc/proxy.go
index d02f7ff4..7bb997f8 100644
--- a/zrpc/proxy.go
+++ b/zrpc/proxy.go
@@ -12,20 +12,20 @@ import (
 
 // A RpcProxy is a rpc proxy.
 type RpcProxy struct {
-	backend     string
-	clients     map[string]Client
-	options     []internal.ClientOption
-	sharedCalls syncx.SingleFlight
-	lock        sync.Mutex
+	backend      string
+	clients      map[string]Client
+	options      []internal.ClientOption
+	singleFlight syncx.SingleFlight
+	lock         sync.Mutex
 }
 
 // NewProxy returns a RpcProxy.
 func NewProxy(backend string, opts ...internal.ClientOption) *RpcProxy {
 	return &RpcProxy{
-		backend:     backend,
-		clients:     make(map[string]Client),
-		options:     opts,
-		sharedCalls: syncx.NewSingleFlight(),
+		backend:      backend,
+		clients:      make(map[string]Client),
+		options:      opts,
+		singleFlight: syncx.NewSingleFlight(),
 	}
 }
 
@@ -33,7 +33,7 @@ func NewProxy(backend string, opts ...internal.ClientOption) *RpcProxy {
 func (p *RpcProxy) TakeConn(ctx context.Context) (*grpc.ClientConn, error) {
 	cred := auth.ParseCredential(ctx)
 	key := cred.App + "/" + cred.Token
-	val, err := p.sharedCalls.Do(key, func() (interface{}, error) {
+	val, err := p.singleFlight.Do(key, func() (interface{}, error) {
 		p.lock.Lock()
 		client, ok := p.clients[key]
 		p.lock.Unlock()


Test patch:
diff --git a/core/collection/fifo_test.go b/core/collection/fifo_test.go
index 3e58afef..6f1acfdf 100644
--- a/core/collection/fifo_test.go
+++ b/core/collection/fifo_test.go
@@ -61,3 +61,41 @@ func TestPutMore(t *testing.T) {
 		assert.Equal(t, string(element), string(body.([]byte)))
 	}
 }
+
+func TestPutMoreWithHeaderNotZero(t *testing.T) {
+	elements := [][]byte{
+		[]byte("hello"),
+		[]byte("world"),
+		[]byte("again"),
+	}
+	queue := NewQueue(4)
+	for i := range elements {
+		queue.Put(elements[i])
+	}
+
+	// take 1
+	body, ok := queue.Take()
+	assert.True(t, ok)
+	element, ok := body.([]byte)
+	assert.True(t, ok)
+	assert.Equal(t, element, []byte("hello"))
+
+	// put more
+	queue.Put([]byte("b4"))
+	queue.Put([]byte("b5")) // will store in elements[0]
+	queue.Put([]byte("b6")) // cause expansion
+
+	results := [][]byte{
+		[]byte("world"),
+		[]byte("again"),
+		[]byte("b4"),
+		[]byte("b5"),
+		[]byte("b6"),
+	}
+
+	for _, element := range results {
+		body, ok := queue.Take()
+		assert.True(t, ok)
+		assert.Equal(t, string(element), string(body.([]byte)))
+	}
+}
diff --git a/core/limit/periodlimit_test.go b/core/limit/periodlimit_test.go
index 8fc4403c..c23c14db 100644
--- a/core/limit/periodlimit_test.go
+++ b/core/limit/periodlimit_test.go
@@ -65,3 +65,13 @@ func testPeriodLimit(t *testing.T, opts ...PeriodOption) {
 	assert.Equal(t, 1, hitQuota)
 	assert.Equal(t, total-quota, overQuota)
 }
+
+func TestQuotaFull(t *testing.T) {
+	s, err := miniredis.Run()
+	assert.Nil(t, err)
+
+	l := NewPeriodLimit(1, 1, redis.New(s.Addr()), "periodlimit")
+	val, err := l.Take("first")
+	assert.Nil(t, err)
+	assert.Equal(t, HitQuota, val)
+}
diff --git a/core/logx/durationlogger_test.go b/core/logx/durationlogger_test.go
index 96eba091..4cebf448 100644
--- a/core/logx/durationlogger_test.go
+++ b/core/logx/durationlogger_test.go
@@ -3,6 +3,7 @@ package logx
 import (
 	"log"
 	"strings"
+	"sync/atomic"
 	"testing"
 	"time"
 
@@ -38,10 +39,10 @@ func TestWithDurationInfo(t *testing.T) {
 }
 
 func TestWithDurationInfoConsole(t *testing.T) {
-	old := encoding
-	encoding = plainEncodingType
+	old := atomic.LoadUint32(&encoding)
+	atomic.StoreUint32(&encoding, plainEncodingType)
 	defer func() {
-		encoding = old
+		atomic.StoreUint32(&encoding, old)
 	}()
 
 	var builder strings.Builder
diff --git a/core/logx/logs_test.go b/core/logx/logs_test.go
index 29d647c2..7ab86c52 100644
--- a/core/logx/logs_test.go
+++ b/core/logx/logs_test.go
@@ -145,10 +145,10 @@ func TestStructedLogInfoConsoleAny(t *testing.T) {
 	doTestStructedLogConsole(t, func(writer io.WriteCloser) {
 		infoLog = writer
 	}, func(v ...interface{}) {
-		old := encoding
-		encoding = plainEncodingType
+		old := atomic.LoadUint32(&encoding)
+		atomic.StoreUint32(&encoding, plainEncodingType)
 		defer func() {
-			encoding = old
+			atomic.StoreUint32(&encoding, old)
 		}()
 
 		Infov(v)
@@ -159,10 +159,10 @@ func TestStructedLogInfoConsoleAnyString(t *testing.T) {
 	doTestStructedLogConsole(t, func(writer io.WriteCloser) {
 		infoLog = writer
 	}, func(v ...interface{}) {
-		old := encoding
-		encoding = plainEncodingType
+		old := atomic.LoadUint32(&encoding)
+		atomic.StoreUint32(&encoding, plainEncodingType)
 		defer func() {
-			encoding = old
+			atomic.StoreUint32(&encoding, old)
 		}()
 
 		Infov(fmt.Sprint(v...))
@@ -173,10 +173,10 @@ func TestStructedLogInfoConsoleAnyError(t *testing.T) {
 	doTestStructedLogConsole(t, func(writer io.WriteCloser) {
 		infoLog = writer
 	}, func(v ...interface{}) {
-		old := encoding
-		encoding = plainEncodingType
+		old := atomic.LoadUint32(&encoding)
+		atomic.StoreUint32(&encoding, plainEncodingType)
 		defer func() {
-			encoding = old
+			atomic.StoreUint32(&encoding, old)
 		}()
 
 		Infov(errors.New(fmt.Sprint(v...)))
@@ -187,10 +187,10 @@ func TestStructedLogInfoConsoleAnyStringer(t *testing.T) {
 	doTestStructedLogConsole(t, func(writer io.WriteCloser) {
 		infoLog = writer
 	}, func(v ...interface{}) {
-		old := encoding
-		encoding = plainEncodingType
+		old := atomic.LoadUint32(&encoding)
+		atomic.StoreUint32(&encoding, plainEncodingType)
 		defer func() {
-			encoding = old
+			atomic.StoreUint32(&encoding, old)
 		}()
 
 		Infov(ValStringer{
@@ -203,10 +203,10 @@ func TestStructedLogInfoConsoleText(t *testing.T) {
 	doTestStructedLogConsole(t, func(writer io.WriteCloser) {
 		infoLog = writer
 	}, func(v ...interface{}) {
-		old := encoding
-		encoding = plainEncodingType
+		old := atomic.LoadUint32(&encoding)
+		atomic.StoreUint32(&encoding, plainEncodingType)
 		defer func() {
-			encoding = old
+			atomic.StoreUint32(&encoding, old)
 		}()
 
 		Info(fmt.Sprint(v...))
diff --git a/core/logx/syslog_test.go b/core/logx/syslog_test.go
index 01b285ee..d2a4fd29 100644
--- a/core/logx/syslog_test.go
+++ b/core/logx/syslog_test.go
@@ -29,9 +29,9 @@ func TestRedirector(t *testing.T) {
 }
 
 func captureOutput(f func()) string {
-	atomic.StoreUint32(&initialized, 1)
 	writer := new(mockWriter)
 	infoLog = writer
+	atomic.StoreUint32(&initialized, 1)
 
 	prevLevel := atomic.LoadUint32(&logLevel)
 	SetLevel(InfoLevel)
@@ -44,5 +44,9 @@ func captureOutput(f func()) string {
 func getContent(jsonStr string) string {
 	var entry logEntry
 	json.Unmarshal([]byte(jsonStr), &entry)
-	return entry.Content.(string)
+	val, ok := entry.Content.(string)
+	if ok {
+		return val
+	}
+	return ""
 }
diff --git a/core/logx/tracelogger_test.go b/core/logx/tracelogger_test.go
index 7da8d0dd..3fa4d2ed 100644
--- a/core/logx/tracelogger_test.go
+++ b/core/logx/tracelogger_test.go
@@ -83,10 +83,10 @@ func TestTraceInfo(t *testing.T) {
 }
 
 func TestTraceInfoConsole(t *testing.T) {
-	old := encoding
-	encoding = plainEncodingType
+	old := atomic.LoadUint32(&encoding)
+	atomic.StoreUint32(&encoding, jsonEncodingType)
 	defer func() {
-		encoding = old
+		atomic.StoreUint32(&encoding, old)
 	}()
 
 	var buf mockWriter
diff --git a/core/mapping/yamlunmarshaler_test.go b/core/mapping/yamlunmarshaler_test.go
index a44c6c36..3a4d5066 100644
--- a/core/mapping/yamlunmarshaler_test.go
+++ b/core/mapping/yamlunmarshaler_test.go
@@ -937,7 +937,6 @@ func TestUnmarshalYamlReaderError(t *testing.T) {
 	reader = strings.NewReader("chenquan")
 	err = UnmarshalYamlReader(reader, &v)
 	assert.ErrorIs(t, err, ErrUnsupportedType)
-
 }
 
 func TestUnmarshalYamlBadReader(t *testing.T) {
diff --git a/core/stores/mongoc/cachedcollection_test.go b/core/stores/mongoc/cachedcollection_test.go
index 6f839b13..f7eaa254 100644
--- a/core/stores/mongoc/cachedcollection_test.go
+++ b/core/stores/mongoc/cachedcollection_test.go
@@ -34,7 +34,7 @@ func TestCollection_Count(t *testing.T) {
 	assert.Nil(t, err)
 	defer clean()
 
-	cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
+	cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
 	c := newCollection(dummyConn{}, cach)
 	val, err := c.Count("any")
 	assert.Nil(t, err)
@@ -98,7 +98,7 @@ func TestStat(t *testing.T) {
 	assert.Nil(t, err)
 	defer clean()
 
-	cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
+	cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
 	c := newCollection(dummyConn{}, cach).(*cachedCollection)
 
 	for i := 0; i < 10; i++ {
@@ -121,7 +121,7 @@ func TestStatCacheFails(t *testing.T) {
 	defer log.SetOutput(os.Stdout)
 
 	r := redis.New("localhost:59999")
-	cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
+	cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
 	c := newCollection(dummyConn{}, cach)
 
 	for i := 0; i < 20; i++ {
@@ -142,7 +142,7 @@ func TestStatDbFails(t *testing.T) {
 	assert.Nil(t, err)
 	defer clean()
 
-	cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
+	cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
 	c := newCollection(dummyConn{}, cach).(*cachedCollection)
 
 	for i := 0; i < 20; i++ {
@@ -164,7 +164,7 @@ func TestStatFromMemory(t *testing.T) {
 	assert.Nil(t, err)
 	defer clean()
 
-	cach := cache.NewNode(r, sharedCalls, stats, mgo.ErrNotFound)
+	cach := cache.NewNode(r, singleFlight, stats, mgo.ErrNotFound)
 	c := newCollection(dummyConn{}, cach).(*cachedCollection)
 
 	var all sync.WaitGroup
diff --git a/core/stores/sqlx/utils_test.go b/core/stores/sqlx/utils_test.go
index af6c8067..6631e134 100644
--- a/core/stores/sqlx/utils_test.go
+++ b/core/stores/sqlx/utils_test.go
@@ -73,6 +73,30 @@ func TestFormat(t *testing.T) {
 			args:   []interface{}{"133", false},
 			hasErr: true,
 		},
+		{
+			name:   "oracle normal",
+			query:  "select name, age from users where bool=:1 and phone=:2",
+			args:   []interface{}{true, "133"},
+			expect: "select name, age from users where bool=1 and phone='133'",
+		},
+		{
+			name:   "oracle normal reverse",
+			query:  "select name, age from users where bool=:2 and phone=:1",
+			args:   []interface{}{"133", false},
+			expect: "select name, age from users where bool=0 and phone='133'",
+		},
+		{
+			name:   "oracle error not number",
+			query:  "select name, age from users where bool=:a and phone=:1",
+			args:   []interface{}{"133", false},
+			hasErr: true,
+		},
+		{
+			name:   "oracle error more args",
+			query:  "select name, age from users where bool=:2 and phone=:1 and nickname=:3",
+			args:   []interface{}{"133", false},
+			hasErr: true,
+		},
 	}
 
 	for _, test := range tests {
diff --git a/core/trace/utils_test.go b/core/trace/utils_test.go
index d5edeeb1..abb67775 100644
--- a/core/trace/utils_test.go
+++ b/core/trace/utils_test.go
@@ -90,14 +90,14 @@ func TestParseFullMethod(t *testing.T) {
 				semconv.RPCMethodKey.String("theMethod"),
 			},
 		}, {
-			fullMethod: "/pkg.srv",
-			name:       "pkg.srv",
+			fullMethod: "/pkg.svr",
+			name:       "pkg.svr",
 			attr:       []attribute.KeyValue(nil),
 		}, {
-			fullMethod: "/pkg.srv/",
-			name:       "pkg.srv/",
+			fullMethod: "/pkg.svr/",
+			name:       "pkg.svr/",
 			attr: []attribute.KeyValue{
-				semconv.RPCServiceKey.String("pkg.srv"),
+				semconv.RPCServiceKey.String("pkg.svr"),
 			},
 		},
 	}
diff --git a/rest/engine_test.go b/rest/engine_test.go
index 799309e6..ca540149 100644
--- a/rest/engine_test.go
+++ b/rest/engine_test.go
@@ -1,13 +1,17 @@
 package rest
 
 import (
+	"context"
 	"errors"
 	"net/http"
+	"net/http/httptest"
+	"sync/atomic"
 	"testing"
 	"time"
 
 	"github.com/stretchr/testify/assert"
 	"github.com/zeromicro/go-zero/core/conf"
+	"github.com/zeromicro/go-zero/core/logx"
 )
 
 func TestNewEngine(t *testing.T) {
@@ -190,6 +194,75 @@ func TestEngine_checkedTimeout(t *testing.T) {
 	}
 }
 
+func TestEngine_notFoundHandler(t *testing.T) {
+	logx.Disable()
+
+	ng := newEngine(RestConf{})
+	ts := httptest.NewServer(ng.notFoundHandler(nil))
+	defer ts.Close()
+
+	client := ts.Client()
+	err := func(ctx context.Context) error {
+		req, err := http.NewRequest("GET", ts.URL+"/bad", nil)
+		assert.Nil(t, err)
+		res, err := client.Do(req)
+		assert.Nil(t, err)
+		assert.Equal(t, http.StatusNotFound, res.StatusCode)
+		return res.Body.Close()
+	}(context.Background())
+
+	assert.Nil(t, err)
+}
+
+func TestEngine_notFoundHandlerNotNil(t *testing.T) {
+	logx.Disable()
+
+	ng := newEngine(RestConf{})
+	var called int32
+	ts := httptest.NewServer(ng.notFoundHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		atomic.AddInt32(&called, 1)
+	})))
+	defer ts.Close()
+
+	client := ts.Client()
+	err := func(ctx context.Context) error {
+		req, err := http.NewRequest("GET", ts.URL+"/bad", nil)
+		assert.Nil(t, err)
+		res, err := client.Do(req)
+		assert.Nil(t, err)
+		assert.Equal(t, http.StatusNotFound, res.StatusCode)
+		return res.Body.Close()
+	}(context.Background())
+
+	assert.Nil(t, err)
+	assert.Equal(t, int32(1), atomic.LoadInt32(&called))
+}
+
+func TestEngine_notFoundHandlerNotNilWriteHeader(t *testing.T) {
+	logx.Disable()
+
+	ng := newEngine(RestConf{})
+	var called int32
+	ts := httptest.NewServer(ng.notFoundHandler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		atomic.AddInt32(&called, 1)
+		w.WriteHeader(http.StatusExpectationFailed)
+	})))
+	defer ts.Close()
+
+	client := ts.Client()
+	err := func(ctx context.Context) error {
+		req, err := http.NewRequest("GET", ts.URL+"/bad", nil)
+		assert.Nil(t, err)
+		res, err := client.Do(req)
+		assert.Nil(t, err)
+		assert.Equal(t, http.StatusExpectationFailed, res.StatusCode)
+		return res.Body.Close()
+	}(context.Background())
+
+	assert.Nil(t, err)
+	assert.Equal(t, int32(1), atomic.LoadInt32(&called))
+}
+
 type mockedRouter struct{}
 
 func (m mockedRouter) ServeHTTP(writer http.ResponseWriter, request *http.Request) {
diff --git a/rest/handler/authhandler_test.go b/rest/handler/authhandler_test.go
index 1587b35f..e9496e10 100644
--- a/rest/handler/authhandler_test.go
+++ b/rest/handler/authhandler_test.go
@@ -90,26 +90,6 @@ func TestAuthHandler_NilError(t *testing.T) {
 	})
 }
 
-func TestAuthHandler_Flush(t *testing.T) {
-	resp := httptest.NewRecorder()
-	handler := newGuardedResponseWriter(resp)
-	handler.Flush()
-	assert.True(t, resp.Flushed)
-}
-
-func TestAuthHandler_Hijack(t *testing.T) {
-	resp := httptest.NewRecorder()
-	writer := newGuardedResponseWriter(resp)
-	assert.NotPanics(t, func() {
-		writer.Hijack()
-	})
-
-	writer = newGuardedResponseWriter(mockedHijackable{resp})
-	assert.NotPanics(t, func() {
-		writer.Hijack()
-	})
-}
-
 func buildToken(secretKey string, payloads map[string]interface{}, seconds int64) (string, error) {
 	now := time.Now().Unix()
 	claims := make(jwt.MapClaims)
diff --git a/rest/handler/tracinghandler_test.go b/rest/handler/tracinghandler_test.go
index b25edaea..94af55e0 100644
--- a/rest/handler/tracinghandler_test.go
+++ b/rest/handler/tracinghandler_test.go
@@ -6,6 +6,7 @@ import (
 	"net/http/httptest"
 	"testing"
 
+	"github.com/justinas/alice"
 	"github.com/stretchr/testify/assert"
 	ztrace "github.com/zeromicro/go-zero/core/trace"
 	"go.opentelemetry.io/otel"
@@ -21,28 +22,31 @@ func TestOtelHandler(t *testing.T) {
 		Sampler:  1.0,
 	})
 
-	ts := httptest.NewServer(
-		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
-			ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))
-			spanCtx := trace.SpanContextFromContext(ctx)
-			assert.Equal(t, true, spanCtx.IsValid())
-		}),
-	)
-	defer ts.Close()
-
-	client := ts.Client()
-	err := func(ctx context.Context) error {
-		ctx, span := otel.Tracer("httptrace/client").Start(ctx, "test")
-		defer span.End()
-
-		req, _ := http.NewRequest("GET", ts.URL, nil)
-		otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
-
-		res, err := client.Do(req)
-		assert.Equal(t, err, nil)
-		_ = res.Body.Close()
-		return nil
-	}(context.Background())
-
-	assert.Equal(t, err, nil)
+	for _, test := range []string{"", "bar"} {
+		t.Run(test, func(t *testing.T) {
+			h := alice.New(TracingHandler("foo", test)).Then(
+				http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+					ctx := otel.GetTextMapPropagator().Extract(r.Context(), propagation.HeaderCarrier(r.Header))
+					spanCtx := trace.SpanContextFromContext(ctx)
+					assert.True(t, spanCtx.IsValid())
+				}))
+			ts := httptest.NewServer(h)
+			defer ts.Close()
+
+			client := ts.Client()
+			err := func(ctx context.Context) error {
+				ctx, span := otel.Tracer("httptrace/client").Start(ctx, "test")
+				defer span.End()
+
+				req, _ := http.NewRequest("GET", ts.URL, nil)
+				otel.GetTextMapPropagator().Inject(ctx, propagation.HeaderCarrier(req.Header))
+
+				res, err := client.Do(req)
+				assert.Nil(t, err)
+				return res.Body.Close()
+			}(context.Background())
+
+			assert.Nil(t, err)
+		})
+	}
 }
diff --git a/rest/internal/cors/handlers_test.go b/rest/internal/cors/handlers_test.go
index 047fdb98..dea7bb4e 100644
--- a/rest/internal/cors/handlers_test.go
+++ b/rest/internal/cors/handlers_test.go
@@ -1,8 +1,6 @@
 package cors
 
 import (
-	"bufio"
-	"net"
 	"net/http"
 	"net/http/httptest"
 	"testing"
@@ -131,48 +129,3 @@ func TestCorsHandlerWithOrigins(t *testing.T) {
 		}
 	}
 }
-
-func TestGuardedResponseWriter_Flush(t *testing.T) {
-	req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
-	handler := NotAllowedHandler(func(w http.ResponseWriter) {
-		w.Header().Set("X-Test", "test")
-		w.WriteHeader(http.StatusServiceUnavailable)
-		_, err := w.Write([]byte("content"))
-		assert.Nil(t, err)
-
-		flusher, ok := w.(http.Flusher)
-		assert.True(t, ok)
-		flusher.Flush()
-	}, "foo.com")
-
-	resp := httptest.NewRecorder()
-	handler.ServeHTTP(resp, req)
-	assert.Equal(t, http.StatusServiceUnavailable, resp.Code)
-	assert.Equal(t, "test", resp.Header().Get("X-Test"))
-	assert.Equal(t, "content", resp.Body.String())
-}
-
-func TestGuardedResponseWriter_Hijack(t *testing.T) {
-	resp := httptest.NewRecorder()
-	writer := &guardedResponseWriter{
-		w: resp,
-	}
-	assert.NotPanics(t, func() {
-		writer.Hijack()
-	})
-
-	writer = &guardedResponseWriter{
-		w: mockedHijackable{resp},
-	}
-	assert.NotPanics(t, func() {
-		writer.Hijack()
-	})
-}
-
-type mockedHijackable struct {
-	*httptest.ResponseRecorder
-}
-
-func (m mockedHijackable) Hijack() (net.Conn, *bufio.ReadWriter, error) {
-	return nil, nil, nil
-}
diff --git a/rest/internal/log_test.go b/rest/internal/log_test.go
index 82d6f296..03a9ee2f 100644
--- a/rest/internal/log_test.go
+++ b/rest/internal/log_test.go
@@ -36,3 +36,8 @@ func TestError(t *testing.T) {
 	assert.True(t, strings.Contains(val, "third"))
 	assert.True(t, strings.Contains(val, "\n"))
 }
+
+func TestContextKey_String(t *testing.T) {
+	val := contextKey("foo")
+	assert.True(t, strings.Contains(val.String(), "foo"))
+}
diff --git a/rest/internal/response/headeronceresponsewriter_test.go b/rest/internal/response/headeronceresponsewriter_test.go
new file mode 100644
index 00000000..cbc8fe1d
--- /dev/null
+++ b/rest/internal/response/headeronceresponsewriter_test.go
@@ -0,0 +1,58 @@
+package response
+
+import (
+	"bufio"
+	"net"
+	"net/http"
+	"net/http/httptest"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestHeaderOnceResponseWriter_Flush(t *testing.T) {
+	req := httptest.NewRequest(http.MethodGet, "http://localhost", nil)
+	handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
+		cw := NewHeaderOnceResponseWriter(w)
+		cw.Header().Set("X-Test", "test")
+		cw.WriteHeader(http.StatusServiceUnavailable)
+		cw.WriteHeader(http.StatusExpectationFailed)
+		_, err := cw.Write([]byte("content"))
+		assert.Nil(t, err)
+
+		flusher, ok := cw.(http.Flusher)
+		assert.True(t, ok)
+		flusher.Flush()
+	})
+
+	resp := httptest.NewRecorder()
+	handler.ServeHTTP(resp, req)
+	assert.Equal(t, http.StatusServiceUnavailable, resp.Code)
+	assert.Equal(t, "test", resp.Header().Get("X-Test"))
+	assert.Equal(t, "content", resp.Body.String())
+}
+
+func TestHeaderOnceResponseWriter_Hijack(t *testing.T) {
+	resp := httptest.NewRecorder()
+	writer := &HeaderOnceResponseWriter{
+		w: resp,
+	}
+	assert.NotPanics(t, func() {
+		writer.Hijack()
+	})
+
+	writer = &HeaderOnceResponseWriter{
+		w: mockedHijackable{resp},
+	}
+	assert.NotPanics(t, func() {
+		writer.Hijack()
+	})
+}
+
+type mockedHijackable struct {
+	*httptest.ResponseRecorder
+}
+
+func (m mockedHijackable) Hijack() (net.Conn, *bufio.ReadWriter, error) {
+	return nil, nil, nil
+}
diff --git a/rest/internal/security/withcoderesponsewriter_test.go b/rest/internal/response/withcoderesponsewriter_test.go
similarity index 71%
rename from rest/internal/security/withcoderesponsewriter_test.go
rename to rest/internal/response/withcoderesponsewriter_test.go
index 3a627798..a4fcfae6 100644
--- a/rest/internal/security/withcoderesponsewriter_test.go
+++ b/rest/internal/response/withcoderesponsewriter_test.go
@@ -1,4 +1,4 @@
-package security
+package response
 
 import (
 	"net/http"
@@ -31,3 +31,20 @@ func TestWithCodeResponseWriter(t *testing.T) {
 	assert.Equal(t, "test", resp.Header().Get("X-Test"))
 	assert.Equal(t, "content", resp.Body.String())
 }
+
+func TestWithCodeResponseWriter_Hijack(t *testing.T) {
+	resp := httptest.NewRecorder()
+	writer := &WithCodeResponseWriter{
+		Writer: resp,
+	}
+	assert.NotPanics(t, func() {
+		writer.Hijack()
+	})
+
+	writer = &WithCodeResponseWriter{
+		Writer: mockedHijackable{resp},
+	}
+	assert.NotPanics(t, func() {
+		writer.Hijack()
+	})
+}
diff --git a/rest/internal/security/contentsecurity_test.go b/rest/internal/security/contentsecurity_test.go
index 7c10c2aa..467c17de 100644
--- a/rest/internal/security/contentsecurity_test.go
+++ b/rest/internal/security/contentsecurity_test.go
@@ -151,6 +151,8 @@ func TestContentSecurity(t *testing.T) {
 				return
 			}
 
+			encrypted := test.mode != "0"
+			assert.Equal(t, encrypted, header.Encrypted())
 			assert.Equal(t, test.code, VerifySignature(r, header, time.Minute))
 		})
 	}
diff --git a/rest/internal/starter_test.go b/rest/internal/starter_test.go
new file mode 100644
index 00000000..7e672d16
--- /dev/null
+++ b/rest/internal/starter_test.go
@@ -0,0 +1,33 @@
+package internal
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"strconv"
+	"strings"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestStartHttp(t *testing.T) {
+	svr := httptest.NewUnstartedServer(http.NotFoundHandler())
+	fields := strings.Split(svr.Listener.Addr().String(), ":")
+	port, err := strconv.Atoi(fields[1])
+	assert.Nil(t, err)
+	err = StartHttp(fields[0], port, http.NotFoundHandler(), func(svr *http.Server) {
+		svr.IdleTimeout = 0
+	})
+	assert.NotNil(t, err)
+}
+
+func TestStartHttps(t *testing.T) {
+	svr := httptest.NewTLSServer(http.NotFoundHandler())
+	fields := strings.Split(svr.Listener.Addr().String(), ":")
+	port, err := strconv.Atoi(fields[1])
+	assert.Nil(t, err)
+	err = StartHttps(fields[0], port, "", "", http.NotFoundHandler(), func(svr *http.Server) {
+		svr.IdleTimeout = 0
+	})
+	assert.NotNil(t, err)
+}
diff --git a/rest/server_test.go b/rest/server_test.go
index 220e4470..92537de6 100644
--- a/rest/server_test.go
+++ b/rest/server_test.go
@@ -56,22 +56,22 @@ Port: 54321
 	}
 
 	for _, test := range tests {
-		var srv *Server
+		var svr *Server
 		var err error
 		if test.fail {
 			_, err = NewServer(test.c, test.opts...)
 			assert.NotNil(t, err)
 			continue
 		} else {
-			srv = MustNewServer(test.c, test.opts...)
+			svr = MustNewServer(test.c, test.opts...)
 		}
 
-		srv.Use(ToMiddleware(func(next http.Handler) http.Handler {
+		svr.Use(ToMiddleware(func(next http.Handler) http.Handler {
 			return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 				next.ServeHTTP(w, r)
 			})
 		}))
-		srv.AddRoute(Route{
+		svr.AddRoute(Route{
 			Method:  http.MethodGet,
 			Path:    "/",
 			Handler: nil,
@@ -89,8 +89,8 @@ Port: 54321
 				}
 			}()
 
-			srv.Start()
-			srv.Stop()
+			svr.Start()
+			svr.Stop()
 		}()
 	}
 }
@@ -290,9 +290,9 @@ Port: 54321
 	}
 
 	for _, testCase := range testCases {
-		srv, err := NewServer(testCase.c, testCase.opts...)
+		svr, err := NewServer(testCase.c, testCase.opts...)
 		assert.Nil(t, err)
-		assert.Equal(t, srv.ngin.tlsConfig, testCase.res)
+		assert.Equal(t, svr.ngin.tlsConfig, testCase.res)
 	}
 }
 
@@ -304,11 +304,11 @@ Port: 54321
 	var cnf RestConf
 	assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
 	rt := router.NewRouter()
-	srv, err := NewServer(cnf, WithRouter(rt))
+	svr, err := NewServer(cnf, WithRouter(rt))
 	assert.Nil(t, err)
 
 	opt := WithCors("local")
-	opt(srv)
+	opt(svr)
 }
 
 func TestWithCustomCors(t *testing.T) {
@@ -319,7 +319,7 @@ Port: 54321
 	var cnf RestConf
 	assert.Nil(t, conf.LoadConfigFromYamlBytes([]byte(configYaml), &cnf))
 	rt := router.NewRouter()
-	srv, err := NewServer(cnf, WithRouter(rt))
+	svr, err := NewServer(cnf, WithRouter(rt))
 	assert.Nil(t, err)
 
 	opt := WithCustomCors(func(header http.Header) {
@@ -327,5 +327,5 @@ Port: 54321
 	}, func(w http.ResponseWriter) {
 		w.WriteHeader(http.StatusOK)
 	}, "local")
-	opt(srv)
+	opt(svr)
 }
diff --git a/tools/goctl/api/dartgen/util_test.go b/tools/goctl/api/dartgen/util_test.go
new file mode 100644
index 00000000..437b1960
--- /dev/null
+++ b/tools/goctl/api/dartgen/util_test.go
@@ -0,0 +1,71 @@
+package dartgen
+
+import (
+	"testing"
+
+	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
+)
+
+func Test_getPropertyFromMember(t *testing.T) {
+	tests := []struct {
+		name   string
+		member spec.Member
+		want   string
+	}{
+		{
+			name: "json tag should be ok",
+			member: spec.Member{
+				Tag:  "`json:\"foo\"`",
+				Name: "Foo",
+			},
+			want: "foo",
+		},
+		{
+			name: "form tag should be ok",
+			member: spec.Member{
+				Tag:  "`form:\"bar\"`",
+				Name: "Bar",
+			},
+			want: "bar",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			if got := getPropertyFromMember(tt.member); got != tt.want {
+				t.Errorf("getPropertyFromMember() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
+
+func Test_specTypeToDart(t *testing.T) {
+	tests := []struct {
+		name     string
+		specType spec.Type
+		want     string
+		wantErr  bool
+	}{
+		{
+			name:     "[]string should return List<String>",
+			specType: spec.ArrayType{RawName: "[]string", Value: spec.PrimitiveType{RawName: "string"}},
+			want:     "List<String>",
+		},
+		{
+			name:     "[]Foo should return List<Foo>",
+			specType: spec.ArrayType{RawName: "[]Foo", Value: spec.DefineStruct{RawName: "Foo"}},
+			want:     "List<Foo>",
+		},
+	}
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			got, err := specTypeToDart(tt.specType)
+			if (err != nil) != tt.wantErr {
+				t.Errorf("specTypeToDart() error = %v, wantErr %v", err, tt.wantErr)
+				return
+			}
+			if got != tt.want {
+				t.Errorf("specTypeToDart() = %v, want %v", got, tt.want)
+			}
+		})
+	}
+}
diff --git a/tools/goctl/api/format/format_test.go b/tools/goctl/api/format/format_test.go
index b3d2c64c..57bda187 100644
--- a/tools/goctl/api/format/format_test.go
+++ b/tools/goctl/api/format/format_test.go
@@ -13,6 +13,7 @@ type Request struct {
 }
 type Response struct {
   Message string ` + "`" + `json:"message"` + "`" + `
+  Students []Student ` + "`" + `json:"students"` + "`" + `
 }
 service A-api {
 @server(
@@ -26,7 +27,8 @@ handler: GreetHandler
 	Name string ` + "`" + `path:"name,options=you|me"` + "`" + `
 }
 type Response {
-	Message string ` + "`" + `json:"message"` + "`" + `
+	Message  string    ` + "`" + `json:"message"` + "`" + `
+	Students []Student ` + "`" + `json:"students"` + "`" + `
 }
 service A-api {
 	@server(
@@ -37,7 +39,9 @@ service A-api {
 )
 
 func TestFormat(t *testing.T) {
-	r, err := apiFormat(notFormattedStr)
+	r, err := apiFormat(notFormattedStr, true)
 	assert.Nil(t, err)
 	assert.Equal(t, formattedStr, r)
+	_, err = apiFormat(notFormattedStr, false)
+	assert.Errorf(t, err, " line 7:13 can not found declaration 'Student' in context")
 }
diff --git a/tools/goctl/api/parser/g4/gen/api/baseparser_test.go b/tools/goctl/api/parser/g4/gen/api/baseparser_test.go
index d50ec323..9cd29ca7 100644
--- a/tools/goctl/api/parser/g4/gen/api/baseparser_test.go
+++ b/tools/goctl/api/parser/g4/gen/api/baseparser_test.go
@@ -9,3 +9,24 @@ import (
 func TestMatch(t *testing.T) {
 	assert.False(t, matchRegex("v1ddd", versionRegex))
 }
+
+func TestImportRegex(t *testing.T) {
+	tests := []struct {
+		value   string
+		matched bool
+	}{
+		{`"bar.api"`, true},
+		{`"foo/bar.api"`, true},
+		{`"/foo/bar.api"`, true},
+		{`"../foo/bar.api"`, true},
+		{`"../../foo/bar.api"`, true},
+
+		{`"bar..api"`, false},
+		{`"//bar.api"`, false},
+	}
+	for _, tt := range tests {
+		t.Run(tt.value, func(t *testing.T) {
+			assert.Equal(t, tt.matched, matchRegex(tt.value, importValueRegex))
+		})
+	}
+}
diff --git a/tools/goctl/api/parser/g4/gen/api/file_splitor_test.go b/tools/goctl/api/parser/g4/gen/api/file_splitor_test.go
index ab7f0682..541d6cbe 100644
--- a/tools/goctl/api/parser/g4/gen/api/file_splitor_test.go
+++ b/tools/goctl/api/parser/g4/gen/api/file_splitor_test.go
@@ -8,6 +8,7 @@ import (
 	"bufio"
 	"bytes"
 	"fmt"
+	"go/format"
 	"io/ioutil"
 	"log"
 	"os"
@@ -61,7 +62,12 @@ import "github.com/zeromicro/antlr"
 			}
 		}
 
-		err = ioutil.WriteFile(fp, buffer.Bytes(), os.ModePerm)
+		src, err := format.Source(buffer.Bytes())
+		if err != nil {
+			fmt.Printf("%+v\n", err)
+			break
+		}
+		err = ioutil.WriteFile(fp, src, os.ModePerm)
 		if err != nil {
 			fmt.Printf("%+v\n", err)
 		}
diff --git a/tools/goctl/model/sql/test/sqlconn.go b/tools/goctl/model/sql/test/sqlconn.go
index 2a1a55cb..b02e3854 100644
--- a/tools/goctl/model/sql/test/sqlconn.go
+++ b/tools/goctl/model/sql/test/sqlconn.go
@@ -3,6 +3,7 @@
 package mocksql
 
 import (
+	"context"
 	"database/sql"
 
 	"github.com/zeromicro/go-zero/core/stores/sqlx"
@@ -29,12 +30,22 @@ func (conn *MockConn) Exec(query string, args ...interface{}) (sql.Result, error
 	return exec(conn.db, query, args...)
 }
 
+// ExecCtx executes sql and returns the result
+func (conn *MockConn) ExecCtx(_ context.Context, query string, args ...interface{}) (sql.Result, error) {
+	return exec(conn.db, query, args...)
+}
+
 // Prepare executes sql by sql.DB
 func (conn *MockConn) Prepare(query string) (sqlx.StmtSession, error) {
 	st, err := conn.db.Prepare(query)
 	return statement{stmt: st}, err
 }
 
+// PrepareCtx executes sql by sql.DB
+func (conn *MockConn) PrepareCtx(_ context.Context, query string) (sqlx.StmtSession, error) {
+	return conn.Prepare(query)
+}
+
 // QueryRow executes sql and returns a query row
 func (conn *MockConn) QueryRow(v interface{}, q string, args ...interface{}) error {
 	return query(conn.db, func(rows *sql.Rows) error {
@@ -42,6 +53,11 @@ func (conn *MockConn) QueryRow(v interface{}, q string, args ...interface{}) err
 	}, q, args...)
 }
 
+// QueryRowCtx executes sql and returns a query row
+func (conn *MockConn) QueryRowCtx(_ context.Context, v interface{}, query string, args ...interface{}) error {
+	return conn.QueryRow(v, query, args...)
+}
+
 // QueryRowPartial executes sql and returns a partial query row
 func (conn *MockConn) QueryRowPartial(v interface{}, q string, args ...interface{}) error {
 	return query(conn.db, func(rows *sql.Rows) error {
@@ -49,6 +65,11 @@ func (conn *MockConn) QueryRowPartial(v interface{}, q string, args ...interface
 	}, q, args...)
 }
 
+// QueryRowPartialCtx executes sql and returns a partial query row
+func (conn *MockConn) QueryRowPartialCtx(_ context.Context, v interface{}, query string, args ...interface{}) error {
+	return conn.QueryRowPartial(v, query, args...)
+}
+
 // QueryRows executes sql and returns  query rows
 func (conn *MockConn) QueryRows(v interface{}, q string, args ...interface{}) error {
 	return query(conn.db, func(rows *sql.Rows) error {
@@ -56,6 +77,11 @@ func (conn *MockConn) QueryRows(v interface{}, q string, args ...interface{}) er
 	}, q, args...)
 }
 
+// QueryRowsCtx executes sql and returns  query rows
+func (conn *MockConn) QueryRowsCtx(_ context.Context, v interface{}, query string, args ...interface{}) error {
+	return conn.QueryRows(v, query, args...)
+}
+
 // QueryRowsPartial executes sql and returns partial query rows
 func (conn *MockConn) QueryRowsPartial(v interface{}, q string, args ...interface{}) error {
 	return query(conn.db, func(rows *sql.Rows) error {
@@ -63,6 +89,11 @@ func (conn *MockConn) QueryRowsPartial(v interface{}, q string, args ...interfac
 	}, q, args...)
 }
 
+// QueryRowsPartialCtx executes sql and returns partial query rows
+func (conn *MockConn) QueryRowsPartialCtx(_ context.Context, v interface{}, query string, args ...interface{}) error {
+	return conn.QueryRowsPartial(v, query, args...)
+}
+
 // RawDB returns the underlying sql.DB.
 func (conn *MockConn) RawDB() (*sql.DB, error) {
 	return conn.db, nil
@@ -73,6 +104,11 @@ func (conn *MockConn) Transact(func(session sqlx.Session) error) error {
 	return nil
 }
 
+// TransactCtx is the implemention of sqlx.SqlConn, nothing to do
+func (conn *MockConn) TransactCtx(ctx context.Context, fn func(context.Context, sqlx.Session) error) error {
+	return nil
+}
+
 func (s statement) Close() error {
 	return s.stmt.Close()
 }
@@ -81,26 +117,46 @@ func (s statement) Exec(args ...interface{}) (sql.Result, error) {
 	return execStmt(s.stmt, args...)
 }
 
+func (s statement) ExecCtx(_ context.Context, args ...interface{}) (sql.Result, error) {
+	return s.Exec(args...)
+}
+
 func (s statement) QueryRow(v interface{}, args ...interface{}) error {
 	return queryStmt(s.stmt, func(rows *sql.Rows) error {
 		return unmarshalRow(v, rows, true)
 	}, args...)
 }
 
+func (s statement) QueryRowCtx(_ context.Context, v interface{}, args ...interface{}) error {
+	return s.QueryRow(v, args...)
+}
+
 func (s statement) QueryRowPartial(v interface{}, args ...interface{}) error {
 	return queryStmt(s.stmt, func(rows *sql.Rows) error {
 		return unmarshalRow(v, rows, false)
 	}, args...)
 }
 
+func (s statement) QueryRowPartialCtx(_ context.Context, v interface{}, args ...interface{}) error {
+	return s.QueryRowPartial(v, args...)
+}
+
 func (s statement) QueryRows(v interface{}, args ...interface{}) error {
 	return queryStmt(s.stmt, func(rows *sql.Rows) error {
 		return unmarshalRows(v, rows, true)
 	}, args...)
 }
 
+func (s statement) QueryRowsCtx(_ context.Context, v interface{}, args ...interface{}) error {
+	return s.QueryRows(v, args...)
+}
+
 func (s statement) QueryRowsPartial(v interface{}, args ...interface{}) error {
 	return queryStmt(s.stmt, func(rows *sql.Rows) error {
 		return unmarshalRows(v, rows, false)
 	}, args...)
 }
+
+func (s statement) QueryRowsPartialCtx(_ context.Context, v interface{}, args ...interface{}) error {
+	return s.QueryRowsPartial(v, args...)
+}
diff --git a/tools/goctl/pkg/collection/sortedmap_test.go b/tools/goctl/pkg/collection/sortedmap_test.go
new file mode 100644
index 00000000..788804bd
--- /dev/null
+++ b/tools/goctl/pkg/collection/sortedmap_test.go
@@ -0,0 +1,235 @@
+package sortedmap
+
+import (
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func Test_SortedMap(t *testing.T) {
+	sm := New()
+	t.Run("SetExpression", func(t *testing.T) {
+		_, _, err := sm.SetExpression("")
+		assert.ErrorIs(t, err, ErrInvalidKVExpression)
+		_, _, err = sm.SetExpression("foo")
+		assert.ErrorIs(t, err, ErrInvalidKVExpression)
+		_, _, err = sm.SetExpression("foo= ")
+		assert.ErrorIs(t, err, ErrInvalidKVExpression)
+		_, _, err = sm.SetExpression(" foo=")
+		assert.ErrorIs(t, err, ErrInvalidKVExpression)
+		_, _, err = sm.SetExpression("foo =")
+		assert.ErrorIs(t, err, ErrInvalidKVExpression)
+		_, _, err = sm.SetExpression("=")
+		assert.ErrorIs(t, err, ErrInvalidKVExpression)
+		_, _, err = sm.SetExpression("=bar")
+		assert.ErrorIs(t, err, ErrInvalidKVExpression)
+		key, value, err := sm.SetExpression("foo=bar")
+		assert.Nil(t, err)
+		assert.Equal(t, "foo", key)
+		assert.Equal(t, "bar", value)
+		key, value, err = sm.SetExpression("foo=")
+		assert.Nil(t, err)
+		assert.Equal(t, value, sm.GetOr(key, ""))
+		sm.Reset()
+	})
+
+	t.Run("SetKV", func(t *testing.T) {
+		sm.SetKV("foo", "bar")
+		assert.Equal(t, "bar", sm.GetOr("foo", ""))
+		sm.SetKV("foo", "bar-changed")
+		assert.Equal(t, "bar-changed", sm.GetOr("foo", ""))
+		sm.Reset()
+	})
+
+	t.Run("Set", func(t *testing.T) {
+		err := sm.Set(KV{})
+		assert.Nil(t, err)
+		err = sm.Set(KV{"foo"})
+		assert.ErrorIs(t, ErrInvalidKVS, err)
+		err = sm.Set(KV{"foo", "bar", "bar", "foo"})
+		assert.Nil(t, err)
+		assert.Equal(t, "bar", sm.GetOr("foo", ""))
+		assert.Equal(t, "foo", sm.GetOr("bar", ""))
+		sm.Reset()
+	})
+
+	t.Run("Get", func(t *testing.T) {
+		_, ok := sm.Get("foo")
+		assert.False(t, ok)
+		sm.SetKV("foo", "bar")
+		value, ok := sm.Get("foo")
+		assert.True(t, ok)
+		assert.Equal(t, "bar", value)
+		sm.Reset()
+	})
+
+	t.Run("GetString", func(t *testing.T) {
+		_, ok := sm.GetString("foo")
+		assert.False(t, ok)
+		sm.SetKV("foo", "bar")
+		value, ok := sm.GetString("foo")
+		assert.True(t, ok)
+		assert.Equal(t, "bar", value)
+		sm.Reset()
+	})
+
+	t.Run("GetStringOr", func(t *testing.T) {
+		value := sm.GetStringOr("foo", "bar")
+		assert.Equal(t, "bar", value)
+		sm.SetKV("foo", "foo")
+		value = sm.GetStringOr("foo", "bar")
+		assert.Equal(t, "foo", value)
+		sm.Reset()
+	})
+
+	t.Run("GetOr", func(t *testing.T) {
+		value := sm.GetOr("foo", "bar")
+		assert.Equal(t, "bar", value)
+		sm.SetKV("foo", "foo")
+		value = sm.GetOr("foo", "bar")
+		assert.Equal(t, "foo", value)
+		sm.Reset()
+	})
+
+	t.Run("HasKey", func(t *testing.T) {
+		ok := sm.HasKey("foo")
+		assert.False(t, ok)
+		sm.SetKV("foo", "")
+		assert.True(t, sm.HasKey("foo"))
+		sm.Reset()
+	})
+
+	t.Run("HasValue", func(t *testing.T) {
+		assert.False(t, sm.HasValue("bar"))
+		sm.SetKV("foo", "bar")
+		assert.True(t, sm.HasValue("bar"))
+		sm.Reset()
+	})
+
+	t.Run("Keys", func(t *testing.T) {
+		keys := sm.Keys()
+		assert.Equal(t, 0, len(keys))
+		expected := []string{"foo1", "foo2", "foo3"}
+		for _, key := range expected {
+			sm.SetKV(key, "")
+		}
+		keys = sm.Keys()
+		var actual []string
+		for _, key := range keys {
+			actual = append(actual, key.(string))
+		}
+
+		assert.Equal(t, expected, actual)
+		sm.Reset()
+	})
+
+	t.Run("Values", func(t *testing.T) {
+		values := sm.Values()
+		assert.Equal(t, 0, len(values))
+		expected := []string{"foo1", "foo2", "foo3"}
+		for _, key := range expected {
+			sm.SetKV(key, key)
+		}
+		values = sm.Values()
+		var actual []string
+		for _, value := range values {
+			actual = append(actual, value.(string))
+		}
+
+		assert.Equal(t, expected, actual)
+		sm.Reset()
+	})
+
+	t.Run("Range", func(t *testing.T) {
+		var keys, values []string
+		sm.Range(func(key, value interface{}) {
+			keys = append(keys, key.(string))
+			values = append(values, value.(string))
+		})
+		assert.Len(t, keys, 0)
+		assert.Len(t, values, 0)
+
+		expected := []string{"foo1", "foo2", "foo3"}
+		for _, key := range expected {
+			sm.SetKV(key, key)
+		}
+		sm.Range(func(key, value interface{}) {
+			keys = append(keys, key.(string))
+			values = append(values, value.(string))
+		})
+		assert.Equal(t, expected, keys)
+		assert.Equal(t, expected, values)
+		sm.Reset()
+	})
+
+	t.Run("RangeIf", func(t *testing.T) {
+		var keys, values []string
+		sm.RangeIf(func(key, value interface{}) bool {
+			keys = append(keys, key.(string))
+			values = append(values, value.(string))
+			return true
+		})
+		assert.Len(t, keys, 0)
+		assert.Len(t, values, 0)
+
+		expected := []string{"foo1", "foo2", "foo3"}
+		for _, key := range expected {
+			sm.SetKV(key, key)
+		}
+		sm.RangeIf(func(key, value interface{}) bool {
+			keys = append(keys, key.(string))
+			values = append(values, value.(string))
+			if key.(string) == "foo1" {
+				return false
+			}
+			return true
+		})
+		assert.Equal(t, []string{"foo1"}, keys)
+		assert.Equal(t, []string{"foo1"}, values)
+		sm.Reset()
+	})
+
+	t.Run("Remove", func(t *testing.T) {
+		_, ok := sm.Remove("foo")
+		assert.False(t, ok)
+		sm.SetKV("foo", "bar")
+		value, ok := sm.Remove("foo")
+		assert.True(t, ok)
+		assert.Equal(t, "bar", value)
+		assert.False(t, sm.HasKey("foo"))
+		assert.False(t, sm.HasValue("bar"))
+		sm.Reset()
+	})
+
+	t.Run("Insert", func(t *testing.T) {
+		data := New()
+		data.SetKV("foo", "bar")
+		sm.SetKV("foo1", "bar1")
+		sm.Insert(data)
+		assert.True(t, sm.HasKey("foo"))
+		assert.True(t, sm.HasValue("bar"))
+		sm.Reset()
+	})
+
+	t.Run("Copy", func(t *testing.T) {
+		sm.SetKV("foo", "bar")
+		data := sm.Copy()
+		assert.True(t, data.HasKey("foo"))
+		assert.True(t, data.HasValue("bar"))
+		sm.SetKV("foo", "bar1")
+		assert.True(t, data.HasKey("foo"))
+		assert.True(t, data.HasValue("bar"))
+		sm.Reset()
+	})
+
+	t.Run("Format", func(t *testing.T) {
+		format := sm.Format()
+		assert.Equal(t, []string{}, format)
+		sm.SetKV("foo1", "bar1")
+		sm.SetKV("foo2", "bar2")
+		sm.SetKV("foo3", "")
+		format = sm.Format()
+		assert.Equal(t, []string{"foo1=bar1", "foo2=bar2", "foo3="}, format)
+		sm.Reset()
+	})
+}
diff --git a/tools/goctl/rpc/cli/zrpc_test.go b/tools/goctl/rpc/cli/zrpc_test.go
index e809f841..051556ad 100644
--- a/tools/goctl/rpc/cli/zrpc_test.go
+++ b/tools/goctl/rpc/cli/zrpc_test.go
@@ -23,7 +23,7 @@ func Test_GetSourceProto(t *testing.T) {
 		return
 	}
 
-	var testData = []test{
+	testData := []test{
 		{
 			source:   []string{"a.proto"},
 			expected: filepath.Join(pwd, "a.proto"),
@@ -54,7 +54,7 @@ func Test_GetSourceProto(t *testing.T) {
 }
 
 func Test_RemoveGoctlFlag(t *testing.T) {
-	var testData = []test{
+	testData := []test{
 		{
 			source:   strings.Fields("protoc foo.proto --go_out=. --go_opt=bar --zrpc_out=. --style go-zero --home=foo"),
 			expected: "protoc foo.proto --go_out=. --go_opt=bar",
@@ -79,6 +79,10 @@ func Test_RemoveGoctlFlag(t *testing.T) {
 			source:   strings.Fields(`protoc foo.proto --go_opt=. --zrpc_out="bar" --style=goZero  --home=bar`),
 			expected: "protoc foo.proto --go_opt=.",
 		},
+		{
+			source:   strings.Fields(`protoc --go_opt=. --go-grpc_out=. --zrpc_out=. foo.proto`),
+			expected: "protoc --go_opt=. --go-grpc_out=. foo.proto",
+		},
 	}
 	for _, e := range testData {
 		cmd := strings.Join(removeGoctlFlag(e.source), " ")
@@ -87,7 +91,7 @@ func Test_RemoveGoctlFlag(t *testing.T) {
 }
 
 func Test_RemovePluginFlag(t *testing.T) {
-	var testData = []test{
+	testData := []test{
 		{
 			source:   strings.Fields("plugins=grpc:."),
 			expected: ".",
diff --git a/tools/goctl/rpc/generator/genpb_test.go b/tools/goctl/rpc/generator/genpb_test.go
new file mode 100644
index 00000000..708853b4
--- /dev/null
+++ b/tools/goctl/rpc/generator/genpb_test.go
@@ -0,0 +1,197 @@
+package generator
+
+import (
+	"io/ioutil"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
+)
+
+func Test_findPbFile(t *testing.T) {
+	dir := t.TempDir()
+	protoFile := filepath.Join(dir, "greet.proto")
+	err := ioutil.WriteFile(protoFile, []byte(`
+syntax = "proto3";
+
+package greet;
+option go_package="./greet";
+
+message Req{}
+message Resp{}
+service Greeter {
+  rpc greet(Req) returns (Resp);
+}
+`), 0666)
+	if err != nil {
+		t.Log(err)
+		return
+	}
+	t.Run("", func(t *testing.T) {
+		output := t.TempDir()
+		grpc := filepath.Join(output, "grpc")
+		err := pathx.MkdirIfNotExist(grpc)
+		if err != nil {
+			t.Log(err)
+			return
+		}
+		cmd := exec.Command("protoc", "-I="+filepath.Dir(protoFile), "--go_out="+output, "--go-grpc_out="+grpc, filepath.Base(protoFile))
+		cmd.Dir = output
+		cmd.Stdout = os.Stdout
+		cmd.Stderr = os.Stderr
+		err = cmd.Run()
+		if err != nil {
+			t.Log(err)
+			return
+		}
+		pbDir, err := findPbFile(output, false)
+		assert.Nil(t, err)
+		pbGo := filepath.Join(pbDir, "greet.pb.go")
+		assert.True(t, pathx.FileExists(pbGo))
+
+		grpcDir, err := findPbFile(output, true)
+		assert.Nil(t, err)
+		grpcGo := filepath.Join(grpcDir, "greet_grpc.pb.go")
+		assert.True(t, pathx.FileExists(grpcGo))
+	})
+
+	t.Run("", func(t *testing.T) {
+		output := t.TempDir()
+		redirect := filepath.Join(output, "pb")
+		grpc := filepath.Join(output, "grpc")
+		err := pathx.MkdirIfNotExist(grpc)
+		if err != nil {
+			t.Log(err)
+			return
+		}
+		cmd := exec.Command("protoc", "-I="+filepath.Dir(protoFile), "--go_out="+output,
+			"--go-grpc_out="+grpc, filepath.Base(protoFile), "--go_opt=M"+filepath.Base(protoFile)+"="+redirect)
+		cmd.Dir = output
+		cmd.Stdout = os.Stdout
+		cmd.Stderr = os.Stderr
+		err = cmd.Run()
+		if err != nil {
+			t.Log(err)
+			return
+		}
+		pbDir, err := findPbFile(output, false)
+		assert.Nil(t, err)
+		pbGo := filepath.Join(pbDir, "greet.pb.go")
+		assert.True(t, pathx.FileExists(pbGo))
+
+		grpcDir, err := findPbFile(output, true)
+		assert.Nil(t, err)
+		grpcGo := filepath.Join(grpcDir, "greet_grpc.pb.go")
+		assert.True(t, pathx.FileExists(grpcGo))
+	})
+
+	t.Run("", func(t *testing.T) {
+		output := t.TempDir()
+		pbeRedirect := filepath.Join(output, "redirect")
+		grpc := filepath.Join(output, "grpc")
+		grpcRedirect := filepath.Join(grpc, "redirect")
+		err := pathx.MkdirIfNotExist(grpc)
+		if err != nil {
+			t.Log(err)
+			return
+		}
+		cmd := exec.Command("protoc", "-I="+filepath.Dir(protoFile), "--go_out="+output,
+			"--go-grpc_out="+grpc, filepath.Base(protoFile), "--go_opt=M"+filepath.Base(protoFile)+"="+pbeRedirect,
+			"--go-grpc_opt=M"+filepath.Base(protoFile)+"="+grpcRedirect)
+		cmd.Dir = output
+		cmd.Stdout = os.Stdout
+		cmd.Stderr = os.Stderr
+		err = cmd.Run()
+		if err != nil {
+			t.Log(err)
+			return
+		}
+		pbDir, err := findPbFile(output, false)
+		assert.Nil(t, err)
+		pbGo := filepath.Join(pbDir, "greet.pb.go")
+		assert.True(t, pathx.FileExists(pbGo))
+
+		grpcDir, err := findPbFile(output, true)
+		assert.Nil(t, err)
+		grpcGo := filepath.Join(grpcDir, "greet_grpc.pb.go")
+		assert.True(t, pathx.FileExists(grpcGo))
+	})
+
+	t.Run("", func(t *testing.T) {
+		output := t.TempDir()
+		pbeRedirect := filepath.Join(output, "redirect")
+		grpc := filepath.Join(output, "grpc")
+		grpcRedirect := filepath.Join(grpc, "redirect")
+		err := pathx.MkdirIfNotExist(grpc)
+		if err != nil {
+			t.Log(err)
+			return
+		}
+		cmd := exec.Command("protoc", "-I="+filepath.Dir(protoFile), "--go_out="+output,
+			"--go-grpc_out="+grpc, filepath.Base(protoFile), "--go_opt=M"+filepath.Base(protoFile)+"="+pbeRedirect,
+			"--go-grpc_opt=M"+filepath.Base(protoFile)+"="+grpcRedirect, "--go_opt=paths=import", "--go-grpc_opt=paths=source_relative")
+		cmd.Dir = output
+		cmd.Stdout = os.Stdout
+		cmd.Stderr = os.Stderr
+		err = cmd.Run()
+		if err != nil {
+			t.Log(err)
+			return
+		}
+		pbDir, err := findPbFile(output, false)
+		assert.Nil(t, err)
+		pbGo := filepath.Join(pbDir, "greet.pb.go")
+		assert.True(t, pathx.FileExists(pbGo))
+
+		grpcDir, err := findPbFile(output, true)
+		assert.Nil(t, err)
+		grpcGo := filepath.Join(grpcDir, "greet_grpc.pb.go")
+		assert.True(t, pathx.FileExists(grpcGo))
+	})
+
+	t.Run("", func(t *testing.T) {
+		output := t.TempDir()
+		pbeRedirect := filepath.Join(output, "redirect")
+		grpc := filepath.Join(output, "grpc")
+		grpcRedirect := filepath.Join(grpc, "redirect")
+		err := pathx.MkdirIfNotExist(grpc)
+		if err != nil {
+			t.Log(err)
+			return
+		}
+		err = pathx.MkdirIfNotExist(pbeRedirect)
+		if err != nil {
+			t.Log(err)
+			return
+		}
+		err = pathx.MkdirIfNotExist(grpcRedirect)
+		if err != nil {
+			t.Log(err)
+			return
+		}
+		cmd := exec.Command("protoc", "-I="+filepath.Dir(protoFile), "--go_out="+output,
+			"--go-grpc_out="+grpc, filepath.Base(protoFile), "--go_opt=M"+filepath.Base(protoFile)+"="+pbeRedirect,
+			"--go-grpc_opt=M"+filepath.Base(protoFile)+"="+grpcRedirect, "--go_opt=paths=import", "--go-grpc_opt=paths=source_relative",
+			"--go_out="+pbeRedirect, "--go-grpc_out="+grpcRedirect)
+		cmd.Dir = output
+		cmd.Stdout = os.Stdout
+		cmd.Stderr = os.Stderr
+		err = cmd.Run()
+		if err != nil {
+			t.Log(err)
+			return
+		}
+		pbDir, err := findPbFile(output, false)
+		assert.Nil(t, err)
+		pbGo := filepath.Join(pbDir, "greet.pb.go")
+		assert.True(t, pathx.FileExists(pbGo))
+
+		grpcDir, err := findPbFile(output, true)
+		assert.Nil(t, err)
+		grpcGo := filepath.Join(grpcDir, "greet_grpc.pb.go")
+		assert.True(t, pathx.FileExists(grpcGo))
+	})
+}
diff --git a/tools/goctl/util/pathx/file_test.go b/tools/goctl/util/pathx/file_test.go
index 7aa81642..00690a17 100644
--- a/tools/goctl/util/pathx/file_test.go
+++ b/tools/goctl/util/pathx/file_test.go
@@ -74,3 +74,35 @@ func TestGetGitHome(t *testing.T) {
 	expected := filepath.Join(homeDir, goctlDir, gitDir)
 	assert.Equal(t, expected, actual)
 }
+
+func TestGetGoctlHome(t *testing.T) {
+	t.Run("goctl_is_file", func(t *testing.T) {
+		tmpFile := filepath.Join(t.TempDir(), "a.tmp")
+		backupTempFile := tmpFile + ".old"
+		err := ioutil.WriteFile(tmpFile, nil, 0666)
+		if err != nil {
+			return
+		}
+		RegisterGoctlHome(tmpFile)
+		home, err := GetGoctlHome()
+		if err != nil {
+			return
+		}
+		info, err := os.Stat(home)
+		assert.Nil(t, err)
+		assert.True(t, info.IsDir())
+
+		_, err = os.Stat(backupTempFile)
+		assert.Nil(t, err)
+	})
+
+	t.Run("goctl_is_dir", func(t *testing.T) {
+		RegisterGoctlHome("")
+		dir := t.TempDir()
+		RegisterGoctlHome(dir)
+		home, err := GetGoctlHome()
+		assert.Nil(t, err)
+		assert.Equal(t, dir, home)
+	})
+
+}
diff --git a/zrpc/internal/server_test.go b/zrpc/internal/server_test.go
index 4dcd0380..3a11254d 100644
--- a/zrpc/internal/server_test.go
+++ b/zrpc/internal/server_test.go
@@ -23,7 +23,7 @@ func TestBaseRpcServer_AddStreamInterceptors(t *testing.T) {
 	server := newBaseRpcServer("foo", &rpcServerOptions{metrics: metrics})
 	server.SetName("bar")
 	var vals []int
-	f := func(srv interface{}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
+	f := func(_ interface{}, _ grpc.ServerStream, _ *grpc.StreamServerInfo, _ grpc.StreamHandler) error {
 		vals = append(vals, 1)
 		return nil
 	}
diff --git a/zrpc/internal/serverinterceptors/authinterceptor_test.go b/zrpc/internal/serverinterceptors/authinterceptor_test.go
index 261cdb11..7d7d44b8 100644
--- a/zrpc/internal/serverinterceptors/authinterceptor_test.go
+++ b/zrpc/internal/serverinterceptors/authinterceptor_test.go
@@ -65,7 +65,7 @@ func TestStreamAuthorizeInterceptor(t *testing.T) {
 			})
 			ctx := metadata.NewIncomingContext(context.Background(), md)
 			stream := mockedStream{ctx: ctx}
-			err = interceptor(nil, stream, nil, func(srv interface{}, stream grpc.ServerStream) error {
+			err = interceptor(nil, stream, nil, func(_ interface{}, _ grpc.ServerStream) error {
 				return nil
 			})
 			if test.hasError {
diff --git a/zrpc/internal/serverinterceptors/breakerinterceptor_test.go b/zrpc/internal/serverinterceptors/breakerinterceptor_test.go
index 50dc110b..61f68c71 100644
--- a/zrpc/internal/serverinterceptors/breakerinterceptor_test.go
+++ b/zrpc/internal/serverinterceptors/breakerinterceptor_test.go
@@ -13,8 +13,7 @@ import (
 func TestStreamBreakerInterceptor(t *testing.T) {
 	err := StreamBreakerInterceptor(nil, nil, &grpc.StreamServerInfo{
 		FullMethod: "any",
-	}, func(
-		srv interface{}, stream grpc.ServerStream) error {
+	}, func(_ interface{}, _ grpc.ServerStream) error {
 		return status.New(codes.DeadlineExceeded, "any").Err()
 	})
 	assert.NotNil(t, err)
@@ -23,7 +22,7 @@ func TestStreamBreakerInterceptor(t *testing.T) {
 func TestUnaryBreakerInterceptor(t *testing.T) {
 	_, err := UnaryBreakerInterceptor(context.Background(), nil, &grpc.UnaryServerInfo{
 		FullMethod: "any",
-	}, func(ctx context.Context, req interface{}) (interface{}, error) {
+	}, func(_ context.Context, _ interface{}) (interface{}, error) {
 		return nil, status.New(codes.DeadlineExceeded, "any").Err()
 	})
 	assert.NotNil(t, err)
diff --git a/zrpc/internal/serverinterceptors/crashinterceptor_test.go b/zrpc/internal/serverinterceptors/crashinterceptor_test.go
index b464c9b9..1f5970c9 100644
--- a/zrpc/internal/serverinterceptors/crashinterceptor_test.go
+++ b/zrpc/internal/serverinterceptors/crashinterceptor_test.go
@@ -15,7 +15,7 @@ func init() {
 
 func TestStreamCrashInterceptor(t *testing.T) {
 	err := StreamCrashInterceptor(nil, nil, nil, func(
-		srv interface{}, stream grpc.ServerStream) error {
+		svr interface{}, stream grpc.ServerStream) error {
 		panic("mock panic")
 	})
 	assert.NotNil(t, err)
diff --git a/zrpc/internal/serverinterceptors/tracinginterceptor_test.go b/zrpc/internal/serverinterceptors/tracinginterceptor_test.go
index a1c9676a..727a4b50 100644
--- a/zrpc/internal/serverinterceptors/tracinginterceptor_test.go
+++ b/zrpc/internal/serverinterceptors/tracinginterceptor_test.go
@@ -101,7 +101,7 @@ func TestStreamTracingInterceptor_GrpcFormat(t *testing.T) {
 	stream := mockedServerStream{ctx: ctx}
 	err := StreamTracingInterceptor(nil, &stream, &grpc.StreamServerInfo{
 		FullMethod: "/foo",
-	}, func(srv interface{}, stream grpc.ServerStream) error {
+	}, func(svr interface{}, stream grpc.ServerStream) error {
 		defer wg.Done()
 		atomic.AddInt32(&run, 1)
 		return nil
@@ -138,7 +138,7 @@ func TestStreamTracingInterceptor_FinishWithGrpcError(t *testing.T) {
 			stream := mockedServerStream{ctx: ctx}
 			err := StreamTracingInterceptor(nil, &stream, &grpc.StreamServerInfo{
 				FullMethod: "/foo",
-			}, func(srv interface{}, stream grpc.ServerStream) error {
+			}, func(svr interface{}, stream grpc.ServerStream) error {
 				defer wg.Done()
 				return test.err
 			})
@@ -175,7 +175,7 @@ func TestStreamTracingInterceptor_WithError(t *testing.T) {
 			stream := mockedServerStream{ctx: ctx}
 			err := StreamTracingInterceptor(nil, &stream, &grpc.StreamServerInfo{
 				FullMethod: "/foo",
-			}, func(srv interface{}, stream grpc.ServerStream) error {
+			}, func(svr interface{}, stream grpc.ServerStream) error {
 				defer wg.Done()
 				return test.err
 			})
diff --git a/zrpc/proxy_test.go b/zrpc/proxy_test.go
index 97a24fe4..935914a4 100644
--- a/zrpc/proxy_test.go
+++ b/zrpc/proxy_test.go
@@ -64,3 +64,9 @@ func TestProxy(t *testing.T) {
 		})
 	}
 }
+
+func TestRpcProxy_TakeConnNewClientFailed(t *testing.T) {
+	proxy := NewProxy("foo", WithDialOption(grpc.WithInsecure()), WithDialOption(grpc.WithBlock()))
+	_, err := proxy.TakeConn(context.Background())
+	assert.NotNil(t, err)
+}
diff --git a/zrpc/server_test.go b/zrpc/server_test.go
index 5dad1b99..f8df2aec 100644
--- a/zrpc/server_test.go
+++ b/zrpc/server_test.go
@@ -36,7 +36,7 @@ func TestServer_setupInterceptors(t *testing.T) {
 
 func TestServer(t *testing.T) {
 	SetServerSlowThreshold(time.Second)
-	srv := MustNewServer(RpcServerConf{
+	svr := MustNewServer(RpcServerConf{
 		ServiceConf: service.ServiceConf{
 			Log: logx.LogConf{
 				ServiceName: "foo",
@@ -52,11 +52,11 @@ func TestServer(t *testing.T) {
 		CpuThreshold:  0,
 	}, func(server *grpc.Server) {
 	})
-	srv.AddOptions(grpc.ConnectionTimeout(time.Hour))
-	srv.AddUnaryInterceptors(serverinterceptors.UnaryCrashInterceptor)
-	srv.AddStreamInterceptors(serverinterceptors.StreamCrashInterceptor)
-	go srv.Start()
-	srv.Stop()
+	svr.AddOptions(grpc.ConnectionTimeout(time.Hour))
+	svr.AddUnaryInterceptors(serverinterceptors.UnaryCrashInterceptor)
+	svr.AddStreamInterceptors(serverinterceptors.StreamCrashInterceptor)
+	go svr.Start()
+	svr.Stop()
 }
 
 func TestServerError(t *testing.T) {
@@ -79,7 +79,7 @@ func TestServerError(t *testing.T) {
 }
 
 func TestServer_HasEtcd(t *testing.T) {
-	srv := MustNewServer(RpcServerConf{
+	svr := MustNewServer(RpcServerConf{
 		ServiceConf: service.ServiceConf{
 			Log: logx.LogConf{
 				ServiceName: "foo",
@@ -94,11 +94,26 @@ func TestServer_HasEtcd(t *testing.T) {
 		Redis: redis.RedisKeyConf{},
 	}, func(server *grpc.Server) {
 	})
-	srv.AddOptions(grpc.ConnectionTimeout(time.Hour))
-	srv.AddUnaryInterceptors(serverinterceptors.UnaryCrashInterceptor)
-	srv.AddStreamInterceptors(serverinterceptors.StreamCrashInterceptor)
-	go srv.Start()
-	srv.Stop()
+	svr.AddOptions(grpc.ConnectionTimeout(time.Hour))
+	svr.AddUnaryInterceptors(serverinterceptors.UnaryCrashInterceptor)
+	svr.AddStreamInterceptors(serverinterceptors.StreamCrashInterceptor)
+	go svr.Start()
+	svr.Stop()
+}
+
+func TestServer_StartFailed(t *testing.T) {
+	svr := MustNewServer(RpcServerConf{
+		ServiceConf: service.ServiceConf{
+			Log: logx.LogConf{
+				ServiceName: "foo",
+				Mode:        "console",
+			},
+		},
+		ListenOn: "localhost:aaa",
+	}, func(server *grpc.Server) {
+	})
+
+	assert.Panics(t, svr.Start)
 }
 
 type mockedServer struct {