Repository: zeromicro/go-zero
Pull request #2049 (2022-06-25 15:22:48)

feat: add method to jsonx
Create a new method MarshalToString to jsonx

kevwan: Don't use `unsafe`.
vfmh: Can I use the string function directly

Patch:
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 00000000..b444581e
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,11 @@
+# To get started with Dependabot version updates, you'll need to specify which
+# package ecosystems to update and where the package manifests are located.
+# Please see the documentation for all configuration options:
+# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
+
+version: 2
+updates:
+  - package-ecosystem: "gomod" # See documentation for possible values
+    directory: "/" # Location of package manifests
+    schedule:
+      interval: "daily"
diff --git a/core/discov/internal/registry.go b/core/discov/internal/registry.go
index 77949e2e..ad37fda3 100644
--- a/core/discov/internal/registry.go
+++ b/core/discov/internal/registry.go
@@ -191,9 +191,11 @@ func (c *cluster) handleWatchEvents(key string, events []*clientv3.Event) {
 				})
 			}
 		case clientv3.EventTypeDelete:
+			c.lock.Lock()
 			if vals, ok := c.values[key]; ok {
 				delete(vals, string(ev.Kv.Key))
 			}
+			c.lock.Unlock()
 			for _, l := range listeners {
 				l.OnDelete(KV{
 					Key: string(ev.Kv.Key),
diff --git a/core/jsonx/json.go b/core/jsonx/json.go
index 176e30a0..34cf92ea 100644
--- a/core/jsonx/json.go
+++ b/core/jsonx/json.go
@@ -53,3 +53,12 @@ func unmarshalUseNumber(decoder *json.Decoder, v interface{}) error {
 func formatError(v string, err error) error {
 	return fmt.Errorf("string: `%s`, error: `%w`", v, err)
 }
+
+// MarshalToString marshals v into string.
+func MarshalToString(v interface{}) (string, error) {
+	data, err := Marshal(v)
+	if err != nil {
+		return "", formatError(string(data), err)
+	}
+	return string(data), nil
+}
diff --git a/core/mapping/unmarshaler.go b/core/mapping/unmarshaler.go
index 57e05979..497a9db8 100644
--- a/core/mapping/unmarshaler.go
+++ b/core/mapping/unmarshaler.go
@@ -534,8 +534,10 @@ func (u *Unmarshaler) fillSliceValue(slice reflect.Value, index int,
 	baseKind reflect.Kind, value interface{}) error {
 	ithVal := slice.Index(index)
 	switch v := value.(type) {
-	case json.Number:
+	case fmt.Stringer:
 		return setValue(baseKind, ithVal, v.String())
+	case string:
+		return setValue(baseKind, ithVal, v)
 	default:
 		// don't need to consider the difference between int, int8, int16, int32, int64,
 		// uint, uint8, uint16, uint32, uint64, because they're handled as json.Number.
diff --git a/core/mapping/utils.go b/core/mapping/utils.go
index fee4e99c..a6fff1a1 100644
--- a/core/mapping/utils.go
+++ b/core/mapping/utils.go
@@ -143,6 +143,23 @@ func doParseKeyAndOptions(field reflect.StructField, value string) (string, *fie
 	return key, &fieldOpts, nil
 }
 
+// ensureValue ensures nested members not to be nil.
+// If pointer value is nil, set to a new value.
+func ensureValue(v reflect.Value) reflect.Value {
+	for {
+		if v.Kind() != reflect.Ptr {
+			break
+		}
+
+		if v.IsNil() {
+			v.Set(reflect.New(v.Type().Elem()))
+		}
+		v = v.Elem()
+	}
+
+	return v
+}
+
 func implicitValueRequiredStruct(tag string, tp reflect.Type) (bool, error) {
 	numFields := tp.NumField()
 	for i := 0; i < numFields; i++ {
@@ -478,6 +495,7 @@ func setValue(kind reflect.Kind, value reflect.Value, str string) error {
 		return errValueNotSettable
 	}
 
+	value = ensureValue(value)
 	v, err := convertType(kind, str)
 	if err != nil {
 		return err
diff --git a/core/mr/mapreduce.go b/core/mr/mapreduce.go
index 23d70bd0..5e78e846 100644
--- a/core/mr/mapreduce.go
+++ b/core/mr/mapreduce.go
@@ -376,9 +376,7 @@ type onceChan struct {
 }
 
 func (oc *onceChan) write(val interface{}) {
-	if atomic.AddInt32(&oc.wrote, 1) > 1 {
-		return
+	if atomic.CompareAndSwapInt32(&oc.wrote, 0, 1) {
+		oc.channel <- val
 	}
-
-	oc.channel <- val
 }
diff --git a/go.mod b/go.mod
index ff4927b1..adcf8ae5 100644
--- a/go.mod
+++ b/go.mod
@@ -3,31 +3,31 @@ module github.com/zeromicro/go-zero
 go 1.16
 
 require (
-	github.com/ClickHouse/clickhouse-go/v2 v2.0.15
+	github.com/ClickHouse/clickhouse-go/v2 v2.2.0
 	github.com/DATA-DOG/go-sqlmock v1.5.0
-	github.com/alicebob/miniredis/v2 v2.21.0
+	github.com/alicebob/miniredis/v2 v2.22.0
 	github.com/fatih/color v1.13.0
 	github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8
 	github.com/go-redis/redis/v8 v8.11.5
 	github.com/go-sql-driver/mysql v1.6.0
-	github.com/golang-jwt/jwt/v4 v4.4.1
+	github.com/golang-jwt/jwt/v4 v4.4.2
 	github.com/golang/mock v1.6.0
 	github.com/google/uuid v1.3.0
 	github.com/justinas/alice v1.2.0
 	github.com/lib/pq v1.10.6
 	github.com/olekukonko/tablewriter v0.0.5
-	github.com/pelletier/go-toml/v2 v2.0.1
+	github.com/pelletier/go-toml/v2 v2.0.2
 	github.com/prometheus/client_golang v1.12.2
 	github.com/spaolacci/murmur3 v1.1.0
-	github.com/stretchr/testify v1.7.2
+	github.com/stretchr/testify v1.8.0
 	go.etcd.io/etcd/api/v3 v3.5.4
 	go.etcd.io/etcd/client/v3 v3.5.4
 	go.mongodb.org/mongo-driver v1.9.1
-	go.opentelemetry.io/otel v1.7.0
+	go.opentelemetry.io/otel v1.8.0
 	go.opentelemetry.io/otel/exporters/jaeger v1.7.0
-	go.opentelemetry.io/otel/exporters/zipkin v1.7.0
-	go.opentelemetry.io/otel/sdk v1.7.0
-	go.opentelemetry.io/otel/trace v1.7.0
+	go.opentelemetry.io/otel/exporters/zipkin v1.8.0
+	go.opentelemetry.io/otel/sdk v1.8.0
+	go.opentelemetry.io/otel/trace v1.8.0
 	go.uber.org/automaxprocs v1.5.1
 	go.uber.org/goleak v1.1.12
 	golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a
diff --git a/go.sum b/go.sum
index 611f1d61..f2b2edef 100644
--- a/go.sum
+++ b/go.sum
@@ -42,8 +42,8 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03
 github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo=
 github.com/ClickHouse/clickhouse-go v1.5.4 h1:cKjXeYLNWVJIx2J1K6H2CqyRmfwVJVY1OV1coaaFcI0=
 github.com/ClickHouse/clickhouse-go v1.5.4/go.mod h1:EaI/sW7Azgz9UATzd5ZdZHRUhHgv5+JMS9NSr2smCJI=
-github.com/ClickHouse/clickhouse-go/v2 v2.0.15 h1:lLAZliqrZEygkxosLaW1qHyeTb4Ho7fVCZ0WKCpLocU=
-github.com/ClickHouse/clickhouse-go/v2 v2.0.15/go.mod h1:Z21o82zD8FFqefOQDg93c0XITlxGbTsWQuRm588Azkk=
+github.com/ClickHouse/clickhouse-go/v2 v2.2.0 h1:dj00TDKY+xwuTJdbpspCSmTLFyWzRJerTHwaBxut1C0=
+github.com/ClickHouse/clickhouse-go/v2 v2.2.0/go.mod h1:8f2XZUi7XoeU+uPIytSi1cvx8fmJxi7vIgqpvYTF1+o=
 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=
@@ -59,8 +59,8 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
 github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a h1:HbKu58rmZpUGpz5+4FfNmIU+FmZg2P3Xaj2v2bfNWmk=
 github.com/alicebob/gopher-json v0.0.0-20200520072559-a9ecdc9d1d3a/go.mod h1:SGnFV6hVsYE877CKEZ6tDNTjaSXYUk6QqoIK6PrAtcc=
-github.com/alicebob/miniredis/v2 v2.21.0 h1:CdmwIlKUWFBDS+4464GtQiQ0R1vpzOgu4Vnd74rBL7M=
-github.com/alicebob/miniredis/v2 v2.21.0/go.mod h1:XNqvJdQJv5mSuVMc0ynneafpnL/zv52acZ6kqeS0t88=
+github.com/alicebob/miniredis/v2 v2.22.0 h1:lIHHiSkEyS1MkKHCHzN+0mWrA4YdbGdimE5iZ2sHSzo=
+github.com/alicebob/miniredis/v2 v2.22.0/go.mod h1:XNqvJdQJv5mSuVMc0ynneafpnL/zv52acZ6kqeS0t88=
 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 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
@@ -159,8 +159,8 @@ github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5x
 github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
-github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
-github.com/golang-jwt/jwt/v4 v4.4.1/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
+github.com/golang-jwt/jwt/v4 v4.4.2 h1:rcc4lwaZgFMCZ5jxF9ABolDcIHdBytAFgqFPbSJQAYs=
+github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
 github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
 github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
@@ -209,8 +209,9 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
 github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 github.com/google/go-cmp v0.5.6/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.7 h1:81/ik6ipDQS2aGcBfIN5dHDB36BwrStyeAQquSYCV4o=
 github.com/google/go-cmp v0.5.7/go.mod h1:n+brtR0CgQNWTVd5ZUFpTBC8YFBDLK/h/bpaJ8/DtOE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.1.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg=
 github.com/google/gofuzz v1.2.0 h1:xRy4A+RhZaiKjJ1bPfwQ8sedCA+YS2YcCHW6ec7JMi0=
@@ -347,14 +348,14 @@ github.com/openzipkin/zipkin-go v0.4.0/go.mod h1:4c3sLeE8xjNqehmF5RpAFLPLJxXscc0
 github.com/paulmach/orb v0.7.1 h1:Zha++Z5OX/l168sqHK3k4z18LDvr+YAO/VjK0ReQ9rU=
 github.com/paulmach/orb v0.7.1/go.mod h1:FWRlTgl88VI1RBx/MkrwWDRhQ96ctqMCh8boXhmqB/A=
 github.com/paulmach/protoscan v0.2.1/go.mod h1:SpcSwydNLrxUGSDvXvO0P7g7AuhJ7lcKfDlhJCDw2gY=
-github.com/pelletier/go-toml/v2 v2.0.1 h1:8e3L2cCQzLFi2CR4g7vGFuFxX7Jl1kKX8gW+iV0GUKU=
-github.com/pelletier/go-toml/v2 v2.0.1/go.mod h1:r9LEWfGN8R5k0VXJ+0BkIe7MYkRdwZOjgMj2KwnJFUo=
+github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw=
+github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI=
 github.com/peterbourgon/diskv v2.0.1+incompatible/go.mod h1:uqqh8zWWbv1HBMNONnaR/tNboyR3/BZd58JJSHlUSCU=
 github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
 github.com/pierrec/lz4 v2.6.1+incompatible h1:9UY3+iC23yxF0UfGaYrGplQ+79Rg+h/q9FV9ix19jjM=
 github.com/pierrec/lz4 v2.6.1+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
-github.com/pierrec/lz4/v4 v4.1.14 h1:+fL8AQEZtz/ijeNnpduH0bROTu0O3NZAlPjQxGn8LwE=
-github.com/pierrec/lz4/v4 v4.1.14/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
+github.com/pierrec/lz4/v4 v4.1.15 h1:MO0/ucJhngq7299dKLwIMtgTfbkoSPF6AoMYDd8Q4q0=
+github.com/pierrec/lz4/v4 v4.1.15/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4=
 github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -411,8 +412,9 @@ github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
 github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
 github.com/stoewer/go-strcase v1.2.0/go.mod h1:IBiWB2sKIp3wVVQ3Y035++gc+knqhUQag1KpM8ahLw8=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
-github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
 github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
+github.com/stretchr/objx v0.4.0 h1:M2gUjqZET1qApGOWNSnZ49BAIMX4F/1plDv3+l31EJ4=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
 github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
 github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
 github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
@@ -420,8 +422,10 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
-github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s=
 github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals=
+github.com/stretchr/testify v1.7.5/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
 github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
 github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
 github.com/tklauser/go-sysconf v0.3.10/go.mod h1:C8XykCvCb+Gn0oNCWPIlcb0RuglQTYaQ2hGm7jmxEFk=
@@ -456,16 +460,19 @@ go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
 go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
 go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
-go.opentelemetry.io/otel v1.7.0 h1:Z2lA3Tdch0iDcrhJXDIlC94XE+bxok1F9B+4Lz/lGsM=
 go.opentelemetry.io/otel v1.7.0/go.mod h1:5BdUoMIz5WEs0vt0CUEMtSSaTSHBBVwrhnz7+nrD5xk=
+go.opentelemetry.io/otel v1.8.0 h1:zcvBFizPbpa1q7FehvFiHbQwGzmPILebO0tyqIR5Djg=
+go.opentelemetry.io/otel v1.8.0/go.mod h1:2pkj+iMj0o03Y+cW6/m8Y4WkRdYN3AvCXCnzRMp9yvM=
 go.opentelemetry.io/otel/exporters/jaeger v1.7.0 h1:wXgjiRldljksZkZrldGVe6XrG9u3kYDyQmkZwmm5dI0=
 go.opentelemetry.io/otel/exporters/jaeger v1.7.0/go.mod h1:PwQAOqBgqbLQRKlj466DuD2qyMjbtcPpfPfj+AqbSBs=
-go.opentelemetry.io/otel/exporters/zipkin v1.7.0 h1:X0FZj+kaIdLi29UiyrEGDhRTYsEXj9GdEW5Y39UQFEE=
-go.opentelemetry.io/otel/exporters/zipkin v1.7.0/go.mod h1:9YBXeOMFLQGwNEjsxMRiWPGoJX83usGMhbCmxUbNe5I=
-go.opentelemetry.io/otel/sdk v1.7.0 h1:4OmStpcKVOfvDOgCt7UriAPtKolwIhxpnSNI/yK+1B0=
+go.opentelemetry.io/otel/exporters/zipkin v1.8.0 h1:PIAiDdROZzATAFfxr5ASYuSOG0JIJxRq3GwlpJGbSYQ=
+go.opentelemetry.io/otel/exporters/zipkin v1.8.0/go.mod h1:0uYAyCuGT67MFV9Z/Mmx93wGuugHw0FbxMc74fs3LNo=
 go.opentelemetry.io/otel/sdk v1.7.0/go.mod h1:uTEOTwaqIVuTGiJN7ii13Ibp75wJmYUDe374q6cZwUU=
-go.opentelemetry.io/otel/trace v1.7.0 h1:O37Iogk1lEkMRXewVtZ1BBTVn5JEp8GrJvP92bJqC6o=
+go.opentelemetry.io/otel/sdk v1.8.0 h1:xwu69/fNuwbSHWe/0PGS888RmjWY181OmcXDQKu7ZQk=
+go.opentelemetry.io/otel/sdk v1.8.0/go.mod h1:uPSfc+yfDH2StDM/Rm35WE8gXSNdvCg023J6HeGNO0c=
 go.opentelemetry.io/otel/trace v1.7.0/go.mod h1:fzLSB9nqR2eXzxPXb2JW9IKE+ScyXA48yyE4TNvoHqU=
+go.opentelemetry.io/otel/trace v1.8.0 h1:cSy0DF9eGI5WIfNwZ1q2iUyGj00tGzP24dE1lOlHrfY=
+go.opentelemetry.io/otel/trace v1.8.0/go.mod h1:0Bt3PXY8w+3pheS3hQUt+wow8b1ojPaTBoTCh2zIFI4=
 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 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE=
@@ -717,7 +724,6 @@ 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=
diff --git a/readme-cn.md b/readme-cn.md
index 6e8a3918..a97cb9f2 100644
--- a/readme-cn.md
+++ b/readme-cn.md
@@ -116,7 +116,7 @@ GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/zeromicro
 
 1. 安装 goctl 工具
 
-    `goctl` 读作 `go control`，不要读成 `go C-T-L`。`goctl` 的意思是不要被代码控制，而是要去控制它。其中的 `go` 不是指 `golang`。在设计 `goctl` 之初，我就希望通过 ` 她 ` 来解放我们的双手👈
+    `goctl` 读作 `go control`，不要读成 `go C-T-L`。`goctl` 的意思是不要被代码控制，而是要去控制它。其中的 `go` 不是指 `golang`。在设计 `goctl` 之初，我就希望通过 `工具` 来解放我们的双手👈
 
     ```shell
     # Go 1.15 及之前版本
diff --git a/readme.md b/readme.md
index c3f0c011..ac8d3842 100644
--- a/readme.md
+++ b/readme.md
@@ -261,5 +261,4 @@ If you like or are using this project to learn or start your solution, please gi
 
 ## Buy me a coffee
 
-<a href="https://www.buymeacoffee.com/kevwan"><img src="https://img.buymeacoffee.com/button-api/?text=Buy me a coffee&emoji=&slug=kevwan&button_colour=40DCA5&font_colour=ffffff&font_family=Cookie&outline_colour=000000&coffee_colour=FFDD00" /></a>
-
+<a href="https://www.buymeacoffee.com/kevwan" target="_blank"><img src="https://cdn.buymeacoffee.com/buttons/v2/default-yellow.png" alt="Buy Me A Coffee" style="height: 60px !important;width: 217px !important;" ></a>
diff --git a/rest/internal/starter.go b/rest/internal/starter.go
index 0f45f989..56198380 100644
--- a/rest/internal/starter.go
+++ b/rest/internal/starter.go
@@ -39,7 +39,7 @@ func start(host string, port int, handler http.Handler, run func(svr *http.Serve
 	}
 
 	waitForCalled := proc.AddWrapUpListener(func() {
-		if e := server.Shutdown(context.Background()); err != nil {
+		if e := server.Shutdown(context.Background()); e != nil {
 			logx.Error(e)
 		}
 	})
diff --git a/tools/goctl/api/gogen/gen.go b/tools/goctl/api/gogen/gen.go
index 02c5186f..55a07e95 100644
--- a/tools/goctl/api/gogen/gen.go
+++ b/tools/goctl/api/gogen/gen.go
@@ -18,6 +18,7 @@ import (
 	"github.com/zeromicro/go-zero/tools/goctl/api/parser"
 	apiutil "github.com/zeromicro/go-zero/tools/goctl/api/util"
 	"github.com/zeromicro/go-zero/tools/goctl/config"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/golang"
 	"github.com/zeromicro/go-zero/tools/goctl/util"
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
@@ -85,7 +86,7 @@ func DoGenProject(apiFile, dir, style string) error {
 	}
 
 	logx.Must(pathx.MkdirIfNotExist(dir))
-	rootPkg, err := getParentPackage(dir)
+	rootPkg, err := golang.GetParentPackage(dir)
 	if err != nil {
 		return err
 	}
diff --git a/tools/goctl/api/gogen/genhandlers.go b/tools/goctl/api/gogen/genhandlers.go
index d073bf98..0231fc60 100644
--- a/tools/goctl/api/gogen/genhandlers.go
+++ b/tools/goctl/api/gogen/genhandlers.go
@@ -8,7 +8,7 @@ import (
 
 	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
 	"github.com/zeromicro/go-zero/tools/goctl/config"
-	"github.com/zeromicro/go-zero/tools/goctl/internal/version"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/golang"
 	"github.com/zeromicro/go-zero/tools/goctl/util"
 	"github.com/zeromicro/go-zero/tools/goctl/util/format"
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
@@ -30,7 +30,6 @@ type handlerInfo struct {
 	Call           string
 	HasResp        bool
 	HasRequest     bool
-	After1_1_10    bool
 }
 
 func genHandler(dir, rootPkg string, cfg *config.Config, group spec.Group, route spec.Route) error {
@@ -42,7 +41,7 @@ func genHandler(dir, rootPkg string, cfg *config.Config, group spec.Group, route
 		handler = strings.Title(handler)
 		logicName = pkgName
 	}
-	parentPkg, err := getParentPackage(dir)
+	parentPkg, err := golang.GetParentPackage(dir)
 	if err != nil {
 		return err
 	}
@@ -92,19 +91,14 @@ func genHandlers(dir, rootPkg string, cfg *config.Config, api *spec.ApiSpec) err
 }
 
 func genHandlerImports(group spec.Group, route spec.Route, parentPkg string) string {
-	var imports []string
-	imports = append(imports, fmt.Sprintf("\"%s\"",
-		pathx.JoinPackages(parentPkg, getLogicFolderPath(group, route))))
-	imports = append(imports, fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, contextDir)))
+	imports := []string{
+		fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, getLogicFolderPath(group, route))),
+		fmt.Sprintf("\"%s\"", pathx.JoinPackages(parentPkg, contextDir)),
+	}
 	if len(route.RequestTypeName()) > 0 {
 		imports = append(imports, fmt.Sprintf("\"%s\"\n", pathx.JoinPackages(parentPkg, typesDir)))
 	}
-
-	currentVersion := version.GetGoctlVersion()
-	// todo(anqiansong): This will be removed after a certain number of production versions of goctl (probably 5)
-	if !version.IsVersionGreaterThan(currentVersion, "1.1.10") {
-		imports = append(imports, fmt.Sprintf("\"%s/rest/httpx\"", vars.ProjectOpenSourceURL))
-	}
+	imports = append(imports, fmt.Sprintf("\"%s/rest/httpx\"", vars.ProjectOpenSourceURL))
 
 	return strings.Join(imports, "\n\t")
 }
@@ -114,6 +108,7 @@ func getHandlerBaseName(route spec.Route) (string, error) {
 	handler = strings.TrimSpace(handler)
 	handler = strings.TrimSuffix(handler, "handler")
 	handler = strings.TrimSuffix(handler, "Handler")
+
 	return handler, nil
 }
 
@@ -128,6 +123,7 @@ func getHandlerFolderPath(group spec.Group, route spec.Route) string {
 
 	folder = strings.TrimPrefix(folder, "/")
 	folder = strings.TrimSuffix(folder, "/")
+
 	return path.Join(handlerDir, folder)
 }
 
diff --git a/tools/goctl/api/gogen/util.go b/tools/goctl/api/gogen/util.go
index cb4ac877..a8798ebd 100644
--- a/tools/goctl/api/gogen/util.go
+++ b/tools/goctl/api/gogen/util.go
@@ -3,16 +3,14 @@ package gogen
 import (
 	"bytes"
 	"fmt"
-	goformat "go/format"
 	"io"
-	"path/filepath"
 	"strings"
 	"text/template"
 
 	"github.com/zeromicro/go-zero/core/collection"
 	"github.com/zeromicro/go-zero/tools/goctl/api/spec"
 	"github.com/zeromicro/go-zero/tools/goctl/api/util"
-	"github.com/zeromicro/go-zero/tools/goctl/util/ctx"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/golang"
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
 
@@ -54,38 +52,11 @@ func genFile(c fileGenConfig) error {
 		return err
 	}
 
-	code := formatCode(buffer.String())
+	code := golang.FormatCode(buffer.String())
 	_, err = fp.WriteString(code)
 	return err
 }
 
-func getParentPackage(dir string) (string, error) {
-	abs, err := filepath.Abs(dir)
-	if err != nil {
-		return "", err
-	}
-
-	projectCtx, err := ctx.Prepare(abs)
-	if err != nil {
-		return "", err
-	}
-
-	// fix https://github.com/zeromicro/go-zero/issues/1058
-	wd := projectCtx.WorkDir
-	d := projectCtx.Dir
-	same, err := pathx.SameFile(wd, d)
-	if err != nil {
-		return "", err
-	}
-
-	trim := strings.TrimPrefix(projectCtx.WorkDir, projectCtx.Dir)
-	if same {
-		trim = strings.TrimPrefix(strings.ToLower(projectCtx.WorkDir), strings.ToLower(projectCtx.Dir))
-	}
-
-	return filepath.ToSlash(filepath.Join(projectCtx.Path, trim)), nil
-}
-
 func writeProperty(writer io.Writer, name, tag, comment string, tp spec.Type, indent int) error {
 	util.WriteIndent(writer, indent)
 	var err error
@@ -136,15 +107,6 @@ func getMiddleware(api *spec.ApiSpec) []string {
 	return result.KeysStr()
 }
 
-func formatCode(code string) string {
-	ret, err := goformat.Source([]byte(code))
-	if err != nil {
-		return code
-	}
-
-	return string(ret)
-}
-
 func responseGoTypeName(r spec.Route, pkg ...string) string {
 	if r.ResponseType == nil {
 		return ""
diff --git a/tools/goctl/api/gogen/vars.go b/tools/goctl/api/gogen/vars.go
index f7f94cf7..c55142da 100644
--- a/tools/goctl/api/gogen/vars.go
+++ b/tools/goctl/api/gogen/vars.go
@@ -1,13 +1,13 @@
 package gogen
 
 const (
-	interval      = "internal/"
+	internal      = "internal/"
 	typesPacket   = "types"
-	configDir     = interval + "config"
-	contextDir    = interval + "svc"
-	handlerDir    = interval + "handler"
-	logicDir      = interval + "logic"
-	middlewareDir = interval + "middleware"
-	typesDir      = interval + typesPacket
+	configDir     = internal + "config"
+	contextDir    = internal + "svc"
+	handlerDir    = internal + "handler"
+	logicDir      = internal + "logic"
+	middlewareDir = internal + "middleware"
+	typesDir      = internal + typesPacket
 	groupProperty = "group"
 )
diff --git a/tools/goctl/internal/version/version.go b/tools/goctl/internal/version/version.go
index 317d9cde..52c1583b 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.8"
+const BuildVersion = "1.3.9"
 
 var tag = map[string]int{"pre-alpha": 0, "alpha": 1, "pre-bata": 2, "beta": 3, "released": 4, "": 5}
 
diff --git a/tools/goctl/model/mongo/readme.md b/tools/goctl/model/mongo/readme.md
index 3317cce3..e8ed8be8 100644
--- a/tools/goctl/model/mongo/readme.md
+++ b/tools/goctl/model/mongo/readme.md
@@ -16,7 +16,7 @@ mongo的生成不同于mysql，mysql可以从scheme_information库中读取到
 
 ## 使用示例
 为 User 生成 mongo model
-```bahs
+```bash
 $ goctl model mongo -t User -c --dir .
 ```
 
diff --git a/tools/goctl/model/sql/template/update.go b/tools/goctl/model/sql/template/update.go
index d1fdbca1..83469519 100644
--- a/tools/goctl/model/sql/template/update.go
+++ b/tools/goctl/model/sql/template/update.go
@@ -20,5 +20,5 @@ func (m *default{{.upperStartCamelObject}}Model) Update(ctx context.Context, {{i
 `
 
 	// UpdateMethod defines an interface method template for generating update codes
-	UpdateMethod = `Update(ctx context.Context, newData *{{.upperStartCamelObject}}) error`
+	UpdateMethod = `Update(ctx context.Context, {{if .containsIndexCache}}newData{{else}}data{{end}} *{{.upperStartCamelObject}}) error`
 )
diff --git a/tools/goctl/pkg/golang/format.go b/tools/goctl/pkg/golang/format.go
new file mode 100644
index 00000000..d7af3c24
--- /dev/null
+++ b/tools/goctl/pkg/golang/format.go
@@ -0,0 +1,12 @@
+package golang
+
+import goformat "go/format"
+
+func FormatCode(code string) string {
+	ret, err := goformat.Source([]byte(code))
+	if err != nil {
+		return code
+	}
+
+	return string(ret)
+}
diff --git a/tools/goctl/pkg/golang/path.go b/tools/goctl/pkg/golang/path.go
new file mode 100644
index 00000000..1f0a2bb1
--- /dev/null
+++ b/tools/goctl/pkg/golang/path.go
@@ -0,0 +1,36 @@
+package golang
+
+import (
+	"path/filepath"
+	"strings"
+
+	"github.com/zeromicro/go-zero/tools/goctl/util/ctx"
+	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
+)
+
+func GetParentPackage(dir string) (string, error) {
+	abs, err := filepath.Abs(dir)
+	if err != nil {
+		return "", err
+	}
+
+	projectCtx, err := ctx.Prepare(abs)
+	if err != nil {
+		return "", err
+	}
+
+	// fix https://github.com/zeromicro/go-zero/issues/1058
+	wd := projectCtx.WorkDir
+	d := projectCtx.Dir
+	same, err := pathx.SameFile(wd, d)
+	if err != nil {
+		return "", err
+	}
+
+	trim := strings.TrimPrefix(projectCtx.WorkDir, projectCtx.Dir)
+	if same {
+		trim = strings.TrimPrefix(strings.ToLower(projectCtx.WorkDir), strings.ToLower(projectCtx.Dir))
+	}
+
+	return filepath.ToSlash(filepath.Join(projectCtx.Path, trim)), nil
+}
diff --git a/tools/goctl/quickstart/idl/apilogic.tpl b/tools/goctl/quickstart/idl/apilogic.tpl
index 6cc01fbf..9605f952 100644
--- a/tools/goctl/quickstart/idl/apilogic.tpl
+++ b/tools/goctl/quickstart/idl/apilogic.tpl
@@ -4,9 +4,10 @@ import (
 	"context"
 
 	"github.com/zeromicro/go-zero/core/logx"
-	"greet/api/internal/svc"
-	"greet/api/internal/types"{{if .callRPC}}
-	"greet/rpc/greet"{{end}}
+
+	"{{.svcPkg}}"
+	"{{.typesPkg}}"{{if .callRPC}}
+	"{{.rpcClientPkg}}"{{end}}
 )
 
 type PingLogic struct {
diff --git a/tools/goctl/quickstart/idl/svc.tpl b/tools/goctl/quickstart/idl/svc.tpl
index 36cac723..4d7b96e6 100644
--- a/tools/goctl/quickstart/idl/svc.tpl
+++ b/tools/goctl/quickstart/idl/svc.tpl
@@ -1,22 +1,22 @@
 package svc
 
 import (
+	"{{.configPkg}}"{{if .callRPC}}
 	"github.com/zeromicro/go-zero/zrpc"
-	"greet/api/internal/config"
-	"greet/rpc/greet"
+	"{{.rpcClientPkg}}"{{end}}
 )
 
 type ServiceContext struct {
-	Config   config.Config
-	GreetRpc greet.Greet
+	Config   config.Config{{if .callRPC}}
+	GreetRpc greet.Greet{{end}}
 }
 
 func NewServiceContext(c config.Config) *ServiceContext {
-	client := zrpc.MustNewClient(zrpc.RpcClientConf{
+	{{if .callRPC}}client := zrpc.MustNewClient(zrpc.RpcClientConf{
 		Target: "127.0.0.1:8080",
-	})
+	}){{end}}
 	return &ServiceContext{
 		Config:   c,
-		GreetRpc: greet.NewGreet(client),
+		{{if .callRPC}}GreetRpc: greet.NewGreet(client),{{end}}
 	}
 }
diff --git a/tools/goctl/quickstart/micro.go b/tools/goctl/quickstart/micro.go
index 68e995dd..08a10c0a 100644
--- a/tools/goctl/quickstart/micro.go
+++ b/tools/goctl/quickstart/micro.go
@@ -17,8 +17,7 @@ var (
 	protocContent string
 	//go:embed idl/rpc.yaml
 	rpcEtcContent string
-
-	zRPCWorkDir string
+	zrpcWorkDir   string
 )
 
 type serviceImpl struct {
@@ -32,12 +31,12 @@ func (s serviceImpl) Start() {
 func (s serviceImpl) Stop() {}
 
 func initRPCProto() error {
-	zRPCWorkDir = filepath.Join(projectDir, "rpc")
-	if err := pathx.MkdirIfNotExist(zRPCWorkDir); err != nil {
+	zrpcWorkDir = filepath.Join(projectDir, "rpc")
+	if err := pathx.MkdirIfNotExist(zrpcWorkDir); err != nil {
 		return err
 	}
 
-	protoFilename := filepath.Join(zRPCWorkDir, protoName)
+	protoFilename := filepath.Join(zrpcWorkDir, protoName)
 	rpcBytes := []byte(protocContent)
 	return ioutil.WriteFile(protoFilename, rpcBytes, 0666)
 }
@@ -54,8 +53,8 @@ func (m micro) mustStartRPCProject() {
 	logx.Must(initRPCProto())
 	log.Debug(">> Generating quickstart zRPC project...")
 	arg := "goctl rpc protoc " + protoName + " --go_out=. --go-grpc_out=. --zrpc_out=. --verbose"
-	execCommand(zRPCWorkDir, arg)
-	etcFile := filepath.Join(zRPCWorkDir, "etc", "greet.yaml")
+	execCommand(zrpcWorkDir, arg)
+	etcFile := filepath.Join(zrpcWorkDir, "etc", "greet.yaml")
 	logx.Must(ioutil.WriteFile(etcFile, []byte(rpcEtcContent), 0666))
 }
 
@@ -65,7 +64,7 @@ func (m micro) start() {
 	sg := service.NewServiceGroup()
 	sg.Add(serviceImpl{func() {
 		log.Debug(">> Ready to start a zRPC server...")
-		goStart(zRPCWorkDir)
+		goStart(zrpcWorkDir)
 	}})
 	sg.Add(serviceImpl{func() {
 		mono.start()
diff --git a/tools/goctl/quickstart/mono.go b/tools/goctl/quickstart/mono.go
index 9331a390..30f5deda 100644
--- a/tools/goctl/quickstart/mono.go
+++ b/tools/goctl/quickstart/mono.go
@@ -7,6 +7,7 @@ import (
 
 	"github.com/zeromicro/go-zero/core/logx"
 	"github.com/zeromicro/go-zero/tools/goctl/api/gogen"
+	"github.com/zeromicro/go-zero/tools/goctl/pkg/golang"
 	"github.com/zeromicro/go-zero/tools/goctl/util"
 	"github.com/zeromicro/go-zero/tools/goctl/util/pathx"
 )
@@ -22,9 +23,11 @@ var (
 	apiEtcContent string
 
 	apiWorkDir string
+	rpcWorkDir string
 )
 
 func initAPIFlags() error {
+	rpcWorkDir = filepath.Join(projectDir, "rpc")
 	apiWorkDir = filepath.Join(projectDir, "api")
 	if err := pathx.MkdirIfNotExist(apiWorkDir); err != nil {
 		return err
@@ -58,15 +61,36 @@ func (m mono) createAPIProject() {
 	etcFile := filepath.Join(apiWorkDir, "etc", "greet.yaml")
 	logx.Must(ioutil.WriteFile(etcFile, []byte(apiEtcContent), 0666))
 	logicFile := filepath.Join(apiWorkDir, "internal", "logic", "pinglogic.go")
+	svcFile := filepath.Join(apiWorkDir, "internal", "svc", "servicecontext.go")
+	configPath := filepath.Join(apiWorkDir, "internal", "config")
+	svcPath := filepath.Join(apiWorkDir, "internal", "svc")
+	typesPath := filepath.Join(apiWorkDir, "internal", "types")
+	svcPkg, err := golang.GetParentPackage(svcPath)
+	logx.Must(err)
+	typesPkg, err := golang.GetParentPackage(typesPath)
+	logx.Must(err)
+	configPkg, err := golang.GetParentPackage(configPath)
+	logx.Must(err)
 
-	logx.Must(util.With("logic").Parse(apiLogicContent).SaveTo(map[string]bool{
-		"callRPC": m.callRPC,
-	}, logicFile, true))
-
+	var rpcClientPkg string
 	if m.callRPC {
-		svcFile := filepath.Join(apiWorkDir, "internal", "svc", "servicecontext.go")
-		logx.Must(ioutil.WriteFile(svcFile, []byte(svcContent), 0666))
+		rpcClientPath := filepath.Join(rpcWorkDir, "greet")
+		rpcClientPkg, err = golang.GetParentPackage(rpcClientPath)
+		logx.Must(err)
 	}
+
+	logx.Must(util.With("logic").Parse(apiLogicContent).SaveTo(map[string]interface{}{
+		"svcPkg":       svcPkg,
+		"typesPkg":     typesPkg,
+		"rpcClientPkg": rpcClientPkg,
+		"callRPC":      m.callRPC,
+	}, logicFile, true))
+
+	logx.Must(util.With("svc").Parse(svcContent).SaveTo(map[string]interface{}{
+		"rpcClientPkg": rpcClientPkg,
+		"configPkg":    configPkg,
+		"callRPC":      m.callRPC,
+	}, svcFile, true))
 }
 
 func (m mono) start() {
diff --git a/tools/goctl/util/pathx/path.go b/tools/goctl/util/pathx/path.go
index 06a1638d..8b5ced37 100644
--- a/tools/goctl/util/pathx/path.go
+++ b/tools/goctl/util/pathx/path.go
@@ -51,8 +51,8 @@ func PathFromGoSrc() (string, error) {
 	return dir[len(parent)+1:], nil
 }
 
-// FindGoModPath returns the path in project where has file go.mod, it maybe return empty string if
-// there is no go.mod file in project
+// FindGoModPath returns the path in project where has file go.mod,
+// it returns empty string if there is no go.mod file in project.
 func FindGoModPath(dir string) (string, bool) {
 	absDir, err := filepath.Abs(dir)
 	if err != nil {


Test patch:
diff --git a/core/jsonx/json_test.go b/core/jsonx/json_test.go
index 48221e36..d11ce992 100644
--- a/core/jsonx/json_test.go
+++ b/core/jsonx/json_test.go
@@ -85,3 +85,16 @@ func TestUnmarshalFromReaderError(t *testing.T) {
 	err := UnmarshalFromReader(strings.NewReader(s), &v)
 	assert.NotNil(t, err)
 }
+
+func TestMarshalToString(t *testing.T) {
+	var v = struct {
+		Name string `json:"name"`
+		Age  int    `json:"age"`
+	}{
+		Name: "John",
+		Age:  30,
+	}
+	toString, err := MarshalToString(v)
+	assert.Nil(t, err)
+	assert.Equal(t, `{"name":"John","age":30}`, toString)
+}
diff --git a/core/mapping/unmarshaler_test.go b/core/mapping/unmarshaler_test.go
index 3c113ed1..2efed625 100644
--- a/core/mapping/unmarshaler_test.go
+++ b/core/mapping/unmarshaler_test.go
@@ -2681,7 +2681,7 @@ func TestUnmarshalJsonReaderMultiArray(t *testing.T) {
 	assert.Equal(t, 2, len(res.B))
 }
 
-func TestUnmarshalJsonReaderPtrMultiArray(t *testing.T) {
+func TestUnmarshalJsonReaderPtrMultiArrayString(t *testing.T) {
 	payload := `{"a": "133", "b": [["add", "cccd"], ["eeee"]]}`
 	var res struct {
 		A string      `json:"a"`
@@ -2694,6 +2694,32 @@ func TestUnmarshalJsonReaderPtrMultiArray(t *testing.T) {
 	assert.Equal(t, 2, len(res.B[0]))
 }
 
+func TestUnmarshalJsonReaderPtrMultiArrayString_Int(t *testing.T) {
+	payload := `{"a": "133", "b": [[11, 22], [33]]}`
+	var res struct {
+		A string      `json:"a"`
+		B [][]*string `json:"b"`
+	}
+	reader := strings.NewReader(payload)
+	err := UnmarshalJsonReader(reader, &res)
+	assert.Nil(t, err)
+	assert.Equal(t, 2, len(res.B))
+	assert.Equal(t, 2, len(res.B[0]))
+}
+
+func TestUnmarshalJsonReaderPtrMultiArrayInt(t *testing.T) {
+	payload := `{"a": "133", "b": [[11, 22], [33]]}`
+	var res struct {
+		A string   `json:"a"`
+		B [][]*int `json:"b"`
+	}
+	reader := strings.NewReader(payload)
+	err := UnmarshalJsonReader(reader, &res)
+	assert.Nil(t, err)
+	assert.Equal(t, 2, len(res.B))
+	assert.Equal(t, 2, len(res.B[0]))
+}
+
 func TestUnmarshalJsonReaderPtrArray(t *testing.T) {
 	payload := `{"a": "133", "b": ["add", "cccd", "eeee"]}`
 	var res struct {
@@ -2706,6 +2732,30 @@ func TestUnmarshalJsonReaderPtrArray(t *testing.T) {
 	assert.Equal(t, 3, len(res.B))
 }
 
+func TestUnmarshalJsonReaderPtrArray_Int(t *testing.T) {
+	payload := `{"a": "133", "b": [11, 22, 33]}`
+	var res struct {
+		A string    `json:"a"`
+		B []*string `json:"b"`
+	}
+	reader := strings.NewReader(payload)
+	err := UnmarshalJsonReader(reader, &res)
+	assert.Nil(t, err)
+	assert.Equal(t, 3, len(res.B))
+}
+
+func TestUnmarshalJsonReaderPtrInt(t *testing.T) {
+	payload := `{"a": "133", "b": [11, 22, 33]}`
+	var res struct {
+		A string    `json:"a"`
+		B []*string `json:"b"`
+	}
+	reader := strings.NewReader(payload)
+	err := UnmarshalJsonReader(reader, &res)
+	assert.Nil(t, err)
+	assert.Equal(t, 3, len(res.B))
+}
+
 func TestUnmarshalJsonWithoutKey(t *testing.T) {
 	payload := `{"A": "1", "B": "2"}`
 	var res struct {
diff --git a/core/mapping/utils_test.go b/core/mapping/utils_test.go
index fd5194cd..dec08584 100644
--- a/core/mapping/utils_test.go
+++ b/core/mapping/utils_test.go
@@ -15,7 +15,7 @@ type Foo struct {
 	StrWithTagAndOption string `key:"stringwithtag,string"`
 }
 
-func TestDeferInt(t *testing.T) {
+func TestDerefInt(t *testing.T) {
 	i := 1
 	s := "hello"
 	number := struct {
@@ -60,6 +60,51 @@ func TestDeferInt(t *testing.T) {
 	}
 }
 
+func TestDerefValInt(t *testing.T) {
+	i := 1
+	s := "hello"
+	number := struct {
+		f float64
+	}{
+		f: 6.4,
+	}
+	cases := []struct {
+		t      reflect.Value
+		expect reflect.Kind
+	}{
+		{
+			t:      reflect.ValueOf(i),
+			expect: reflect.Int,
+		},
+		{
+			t:      reflect.ValueOf(&i),
+			expect: reflect.Int,
+		},
+		{
+			t:      reflect.ValueOf(s),
+			expect: reflect.String,
+		},
+		{
+			t:      reflect.ValueOf(&s),
+			expect: reflect.String,
+		},
+		{
+			t:      reflect.ValueOf(number.f),
+			expect: reflect.Float64,
+		},
+		{
+			t:      reflect.ValueOf(&number.f),
+			expect: reflect.Float64,
+		},
+	}
+
+	for _, each := range cases {
+		t.Run(each.t.String(), func(t *testing.T) {
+			assert.Equal(t, each.expect, ensureValue(each.t).Kind())
+		})
+	}
+}
+
 func TestParseKeyAndOptionWithoutTag(t *testing.T) {
 	var foo Foo
 	rte := reflect.TypeOf(&foo).Elem()
diff --git a/rest/internal/encoding/parser_test.go b/rest/internal/encoding/parser_test.go
index 70e001f4..b04b3f72 100644
--- a/rest/internal/encoding/parser_test.go
+++ b/rest/internal/encoding/parser_test.go
@@ -38,3 +38,14 @@ func TestParseHeadersMulti(t *testing.T) {
 	assert.Equal(t, 1, val.Baz)
 	assert.True(t, val.Qux)
 }
+
+func TestParseHeadersArrayInt(t *testing.T) {
+	var val struct {
+		Foo []int `header:"foo"`
+	}
+	r := httptest.NewRequest(http.MethodGet, "/any", nil)
+	r.Header.Set("foo", "1")
+	r.Header.Add("foo", "2")
+	assert.Nil(t, ParseHeaders(r.Header, &val))
+	assert.Equal(t, []int{1, 2}, val.Foo)
+}
\ No newline at end of file